20260804:大屏技术
This commit is contained in:
parent
0a3051d4a7
commit
24a52eb33c
@ -8,10 +8,11 @@ TARGET_PORT = 8888 # 请根据实际情况修改
|
|||||||
|
|
||||||
# 元数组 modbus 指令
|
# 元数组 modbus 指令
|
||||||
MODBUS_DATA_HEX = (
|
MODBUS_DATA_HEX = (
|
||||||
"7e80D4F8E47AFB347e",
|
"7e80D920B9A6DB717e",
|
||||||
"01031c42310b2f41a3000040ae48850000000042b166ea3fa487e43e3fefd65e78",
|
"0103204135317841ab8e39bf27bccd3f1fb19ac2b231bbc0b6f82040ae3207c3f9f80efa23",
|
||||||
"02031c422a945a41b4000040a3f8210000000042a7fd174081531ec08dec058dc0",
|
"020320413b9fa441acd34dc0024a003fa2a59ac2af34a0c18e17ea413167dbc3f9c7fe6d40",
|
||||||
"03031c4223b88041970000409c54a30000000042aa4f04bfa6c19fc095539b4f18"
|
"0303204139565641ab21ddbff6af333fe26800c2aec626c18685244176ee82c3f9bd7568c8",
|
||||||
|
"040320413a0d3e41a534d3bf461ccd403d7e00c2ade1e4c0d819c441ce9ca7c3f9a4d260bf"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 重要-四川远德MQTT推送 (倾角计)
|
# 重要-四川远德MQTT推送 (倾角计)
|
||||||
|
|||||||
@ -1,790 +0,0 @@
|
|||||||
# Big Screen Transformation Implementation Plan
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
This plan outlines the implementation of project management functionality for the "岩安测控大数据中心" (Yan'an Control Big Data Center). The transformation includes adding business project entities, enhancing the map mode with project visualization, implementing personnel-based device viewing, and completing analysis mode integration.
|
|
||||||
|
|
||||||
## Current Architecture Understanding
|
|
||||||
|
|
||||||
### Backend Structure (RuoYi Framework)
|
|
||||||
- **Entities**: `fastbee-iot-service/src/main/java/com/fastbee/iot/domain`
|
|
||||||
- **Services**: `fastbee-iot-service/src/main/java/com/fastbee/iot/service`
|
|
||||||
- **Controllers**: `fastbee-open-api/src/main/java/com/fastbee/controller`
|
|
||||||
- **Mappers**: `fastbee-iot-service/src/main/resources/mapper/iot`
|
|
||||||
- **BaseEntity**: Extends from RuoYi framework with standard fields (createBy, createTime, updateBy, updateTime, remark)
|
|
||||||
|
|
||||||
### Frontend Structure
|
|
||||||
- **Vue Pages**: `yack-web/src/views`
|
|
||||||
- **API Files**: `yack-web/src/api`
|
|
||||||
- **BigScreen**: `yack-web/src/views/bigScreen/home.vue` (already implemented dual mode)
|
|
||||||
- **Router**: `yack-web/src/router/index.js`
|
|
||||||
|
|
||||||
### Database Schema (Existing)
|
|
||||||
- `iot_group`: Device groups (group_id, group_name, user_id)
|
|
||||||
- `iot_device_group`: Device-group relationship (device_id, group_id)
|
|
||||||
- `iot_device`: Devices with longitude, latitude, tenant_id
|
|
||||||
- `iot_device_user`: Device-user relationship (device_id, user_id)
|
|
||||||
- `sys_user`: User information
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
|
|
||||||
### Phase 1: Database Schema Design
|
|
||||||
|
|
||||||
#### 1.1 Create Project Table (iot_project)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE `iot_project` (
|
|
||||||
`project_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
|
||||||
`project_name` VARCHAR(100) NOT NULL COMMENT '项目名称',
|
|
||||||
`project_code` VARCHAR(50) DEFAULT NULL COMMENT '项目编码',
|
|
||||||
`tenant_id` BIGINT(20) NOT NULL COMMENT '租户ID',
|
|
||||||
`tenant_name` VARCHAR(30) DEFAULT '' COMMENT '租户名称',
|
|
||||||
`responsible_user_id` BIGINT(20) DEFAULT NULL COMMENT '负责人ID',
|
|
||||||
`responsible_user_name` VARCHAR(30) DEFAULT '' COMMENT '负责人姓名',
|
|
||||||
`longitude` DOUBLE(11,6) DEFAULT NULL COMMENT '项目经度',
|
|
||||||
`latitude` DOUBLE(11,6) DEFAULT NULL COMMENT '项目纬度',
|
|
||||||
`address` VARCHAR(255) DEFAULT NULL COMMENT '项目地址',
|
|
||||||
`project_status` TINYINT(1) DEFAULT 1 COMMENT '项目状态(1-预警,2-运行中,3-完结)',
|
|
||||||
`start_time` DATETIME DEFAULT NULL COMMENT '开始时间',
|
|
||||||
`end_time` DATETIME DEFAULT NULL COMMENT '结束时间',
|
|
||||||
`project_desc` VARCHAR(500) DEFAULT NULL COMMENT '项目描述',
|
|
||||||
`img_url` VARCHAR(255) DEFAULT NULL COMMENT '项目图片',
|
|
||||||
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0存在 2删除)',
|
|
||||||
`create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
|
||||||
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
|
|
||||||
`update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
|
||||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
|
||||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
|
||||||
PRIMARY KEY (`project_id`),
|
|
||||||
KEY `idx_tenant_id` (`tenant_id`),
|
|
||||||
KEY `idx_responsible_user` (`responsible_user_id`),
|
|
||||||
KEY `idx_project_status` (`project_status`),
|
|
||||||
KEY `idx_create_time` (`create_time`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='业务项目表';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.2 Create Project-Group Relationship Table (iot_project_group)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE `iot_project_group` (
|
|
||||||
`project_id` BIGINT(20) NOT NULL COMMENT '项目ID',
|
|
||||||
`group_id` BIGINT(20) NOT NULL COMMENT '设备分组ID',
|
|
||||||
PRIMARY KEY (`project_id`, `group_id`),
|
|
||||||
KEY `idx_project_id` (`project_id`),
|
|
||||||
KEY `idx_group_id` (`group_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='项目-设备分组关系表';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 1.3 Project Status Enum
|
|
||||||
|
|
||||||
Define constants for project status:
|
|
||||||
- 1 = WARNING (预警项目)
|
|
||||||
- 2 = RUNNING (运行中项目)
|
|
||||||
- 3 = COMPLETED (完结项目)
|
|
||||||
|
|
||||||
### Phase 2: Backend Implementation
|
|
||||||
|
|
||||||
#### 2.1 Entity Classes
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Project.java`
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.fastbee.iot.domain;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import com.fastbee.common.core.domain.BaseEntity;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 业务项目对象 iot_project
|
|
||||||
*/
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@ApiModel(value = "Project", description = "业务项目对象 iot_project")
|
|
||||||
@Data
|
|
||||||
@TableName("iot_project")
|
|
||||||
public class Project extends BaseEntity {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目ID")
|
|
||||||
private Long projectId;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目名称")
|
|
||||||
private String projectName;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目编码")
|
|
||||||
private String projectCode;
|
|
||||||
|
|
||||||
@ApiModelProperty("租户ID")
|
|
||||||
private Long tenantId;
|
|
||||||
|
|
||||||
@ApiModelProperty("租户名称")
|
|
||||||
private String tenantName;
|
|
||||||
|
|
||||||
@ApiModelProperty("负责人ID")
|
|
||||||
private Long responsibleUserId;
|
|
||||||
|
|
||||||
@ApiModelProperty("负责人姓名")
|
|
||||||
private String responsibleUserName;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目经度")
|
|
||||||
private BigDecimal longitude;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目纬度")
|
|
||||||
private BigDecimal latitude;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目地址")
|
|
||||||
private String address;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目状态(1-预警,2-运行中,3-完结)")
|
|
||||||
private Integer projectStatus;
|
|
||||||
|
|
||||||
@ApiModelProperty("开始时间")
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private Date startTime;
|
|
||||||
|
|
||||||
@ApiModelProperty("结束时间")
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private Date endTime;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目描述")
|
|
||||||
private String projectDesc;
|
|
||||||
|
|
||||||
@ApiModelProperty("项目图片")
|
|
||||||
private String imgUrl;
|
|
||||||
|
|
||||||
@ApiModelProperty("删除标志")
|
|
||||||
private String delFlag;
|
|
||||||
|
|
||||||
@ApiModelProperty("关联分组ID列表")
|
|
||||||
private List<Long> groupIds;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProjectGroup.java`
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.fastbee.iot.domain;
|
|
||||||
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目-分组关系对象 iot_project_group
|
|
||||||
*/
|
|
||||||
@ApiModel(value = "ProjectGroup", description = "项目-设备分组关系对象 iot_project_group")
|
|
||||||
@Data
|
|
||||||
public class ProjectGroup {
|
|
||||||
@ApiModelProperty("项目ID")
|
|
||||||
private Long projectId;
|
|
||||||
|
|
||||||
@ApiModelProperty("分组ID")
|
|
||||||
private Long groupId;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 Service Interface and Implementation
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProjectService.java`
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.fastbee.iot.service;
|
|
||||||
|
|
||||||
import com.fastbee.iot.domain.Project;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目管理Service接口
|
|
||||||
*/
|
|
||||||
public interface IProjectService {
|
|
||||||
/**
|
|
||||||
* 查询项目
|
|
||||||
*/
|
|
||||||
public Project selectProjectById(Long projectId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询项目列表
|
|
||||||
*/
|
|
||||||
public List<Project> selectProjectList(Project project);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据负责人查询项目
|
|
||||||
*/
|
|
||||||
public List<Project> selectProjectsByUserId(Long userId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据租户查询项目
|
|
||||||
*/
|
|
||||||
public List<Project> selectProjectsByTenantId(Long tenantId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询项目关联的分组ID数组
|
|
||||||
*/
|
|
||||||
public Long[] selectGroupIdsByProjectId(Long projectId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增项目
|
|
||||||
*/
|
|
||||||
public int insertProject(Project project);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改项目
|
|
||||||
*/
|
|
||||||
public int updateProject(Project project);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新项目关联的分组
|
|
||||||
*/
|
|
||||||
public int updateProjectGroups(Long projectId, Long[] groupIds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除项目
|
|
||||||
*/
|
|
||||||
public int deleteProjectById(Long projectId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除项目
|
|
||||||
*/
|
|
||||||
public int deleteProjectByIds(Long[] projectIds);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询项目设备统计
|
|
||||||
*/
|
|
||||||
public Project selectProjectDeviceStats(Long projectId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java`
|
|
||||||
|
|
||||||
Implementation pattern should follow existing service implementations like GroupServiceImpl, using:
|
|
||||||
- @Autowired for mapper injection
|
|
||||||
- Transaction management for project-group relationship updates
|
|
||||||
- TenantId filtering from SecurityUtils.getLoginUser()
|
|
||||||
- delFlag = '0' for active records
|
|
||||||
|
|
||||||
#### 2.3 Mapper Interface and XML
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProjectMapper.java`
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.fastbee.iot.mapper;
|
|
||||||
|
|
||||||
import com.fastbee.iot.domain.Project;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目Mapper接口
|
|
||||||
*/
|
|
||||||
public interface ProjectMapper {
|
|
||||||
public Project selectProjectById(Long projectId);
|
|
||||||
public List<Project> selectProjectList(Project project);
|
|
||||||
public List<Project> selectProjectsByUserId(Long userId);
|
|
||||||
public List<Project> selectProjectsByTenantId(Long tenantId);
|
|
||||||
public Long[] selectGroupIdsByProjectId(Long projectId);
|
|
||||||
public int insertProject(Project project);
|
|
||||||
public int updateProject(Project project);
|
|
||||||
public int deleteProjectById(Long projectId);
|
|
||||||
public int deleteProjectByIds(Long[] projectIds);
|
|
||||||
public int insertProjectGroups(Long projectId, Long[] groupIds);
|
|
||||||
public int deleteProjectGroupsByProjectId(Long projectId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml`
|
|
||||||
|
|
||||||
XML should follow GroupMapper.xml pattern with:
|
|
||||||
- resultMap for Project entity
|
|
||||||
- selectGroupIdsByProjectId joining iot_project_group
|
|
||||||
- insertProjectGroups batch insert
|
|
||||||
- delFlag filtering
|
|
||||||
|
|
||||||
#### 2.4 Controller Implementation
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/fastbee-open-api/src/main/java/com/fastbee/controller/project/ProjectController.java`
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.fastbee.controller.project;
|
|
||||||
|
|
||||||
import com.fastbee.common.core.controller.BaseController;
|
|
||||||
import com.fastbee.common.core.domain.AjaxResult;
|
|
||||||
import com.fastbee.iot.domain.Project;
|
|
||||||
import com.fastbee.iot.service.IProjectService;
|
|
||||||
import io.swagger.annotations.Api;
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Api(tags = "项目管理")
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/iot/project")
|
|
||||||
public class ProjectController extends BaseController {
|
|
||||||
@Autowired
|
|
||||||
private IProjectService projectService;
|
|
||||||
|
|
||||||
@ApiOperation("项目列表")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:list')")
|
|
||||||
@GetMapping("/list")
|
|
||||||
public AjaxResult list(Project project) {
|
|
||||||
startPage();
|
|
||||||
List<Project> list = projectService.selectProjectList(project);
|
|
||||||
return getDataTable(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("项目详情")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:query')")
|
|
||||||
@GetMapping("/{projectId}")
|
|
||||||
public AjaxResult getInfo(@PathVariable Long projectId) {
|
|
||||||
Project project = projectService.selectProjectById(projectId);
|
|
||||||
project.setGroupIds(projectService.selectGroupIdsByProjectId(projectId));
|
|
||||||
return AjaxResult.success(project);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("新增项目")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:add')")
|
|
||||||
@PostMapping
|
|
||||||
public AjaxResult add(@RequestBody Project project) {
|
|
||||||
return toAjax(projectService.insertProject(project));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("修改项目")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:edit')")
|
|
||||||
@PutMapping
|
|
||||||
public AjaxResult edit(@RequestBody Project project) {
|
|
||||||
return toAjax(projectService.updateProject(project));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("更新项目分组")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:edit')")
|
|
||||||
@PutMapping("/updateProjectGroups")
|
|
||||||
public AjaxResult updateProjectGroups(@RequestParam Long projectId, @RequestBody Long[] groupIds) {
|
|
||||||
return toAjax(projectService.updateProjectGroups(projectId, groupIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("删除项目")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:remove')")
|
|
||||||
@DeleteMapping("/{projectIds}")
|
|
||||||
public AjaxResult remove(@PathVariable Long[] projectIds) {
|
|
||||||
return toAjax(projectService.deleteProjectByIds(projectIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("按人员查询项目")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:list')")
|
|
||||||
@GetMapping("/listByUser/{userId}")
|
|
||||||
public AjaxResult listByUser(@PathVariable Long userId) {
|
|
||||||
return AjaxResult.success(projectService.selectProjectsByUserId(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("项目设备统计")
|
|
||||||
@PreAuthorize("@ss.hasPermi('iot:project:query')")
|
|
||||||
@GetMapping("/deviceStats/{projectId}")
|
|
||||||
public AjaxResult deviceStats(@PathVariable Long projectId) {
|
|
||||||
return AjaxResult.success(projectService.selectProjectDeviceStats(projectId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Frontend Implementation
|
|
||||||
|
|
||||||
#### 3.1 Project API File
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/yack-web/src/api/iot/project.js`
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
// 查询项目列表
|
|
||||||
export function listProject(query) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/list',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询项目详情
|
|
||||||
export function getProject(projectId) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/' + projectId,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增项目
|
|
||||||
export function addProject(data) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project',
|
|
||||||
method: 'post',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改项目
|
|
||||||
export function updateProject(data) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project',
|
|
||||||
method: 'put',
|
|
||||||
data: data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新项目分组
|
|
||||||
export function updateProjectGroups(projectId, groupIds) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/updateProjectGroups',
|
|
||||||
method: 'put',
|
|
||||||
params: { projectId },
|
|
||||||
data: groupIds
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除项目
|
|
||||||
export function delProject(projectId) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/' + projectId,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按人员查询项目
|
|
||||||
export function listProjectByUser(userId) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/listByUser/' + userId,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 项目设备统计
|
|
||||||
export function getProjectDeviceStats(projectId) {
|
|
||||||
return request({
|
|
||||||
url: '/iot/project/deviceStats/' + projectId,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 Project Configuration Page
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/iot/project/index.vue`
|
|
||||||
|
|
||||||
Key features:
|
|
||||||
- Project list table with CRUD operations
|
|
||||||
- Project creation/edit form with:
|
|
||||||
- Project name, code, description
|
|
||||||
- Responsible person selection (user dropdown)
|
|
||||||
- Device group binding (multi-select)
|
|
||||||
- Map coordinate picker (integrate tdtMap.js)
|
|
||||||
- Project status selection (dropdown)
|
|
||||||
- Start/end time
|
|
||||||
- Integration with Tianditu map for coordinate selection
|
|
||||||
- Reuse patterns from `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/iot/group/index.vue`
|
|
||||||
|
|
||||||
#### 3.3 Map Mode Enhancements
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/bigScreen/home.vue` (Enhancement)
|
|
||||||
|
|
||||||
Enhancements needed:
|
|
||||||
1. **Project Points Visualization**:
|
|
||||||
- Load projects via API on map initialization
|
|
||||||
- Create map markers for each project (based on longitude/latitude)
|
|
||||||
- Color markers based on project status (warning=red, running=green, completed=gray)
|
|
||||||
- Click marker to show project details panel
|
|
||||||
|
|
||||||
2. **Project Status Display**:
|
|
||||||
- Add status legend in map toolbar (already partially implemented)
|
|
||||||
- Count projects by status
|
|
||||||
- Display warningProjectCount, runningProjectCount, completedProjectCount
|
|
||||||
|
|
||||||
3. **Group Expansion**:
|
|
||||||
- Click project marker to show associated groups
|
|
||||||
- Expand groups to show devices in each group
|
|
||||||
- Device list with status indicators
|
|
||||||
|
|
||||||
4. **Code Structure**:
|
|
||||||
```javascript
|
|
||||||
// Add to data():
|
|
||||||
projectList: [],
|
|
||||||
selectedProject: null,
|
|
||||||
projectDeviceMap: {}, // projectId -> { groups: [], devices: [] }
|
|
||||||
|
|
||||||
// Add computed:
|
|
||||||
warningProjectCount() {
|
|
||||||
return this.projectList.filter(p => p.projectStatus === 1).length;
|
|
||||||
},
|
|
||||||
runningProjectCount() {
|
|
||||||
return this.projectList.filter(p => p.projectStatus === 2).length;
|
|
||||||
},
|
|
||||||
completedProjectCount() {
|
|
||||||
return this.projectList.filter(p => p.projectStatus === 3).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add methods:
|
|
||||||
async loadProjects() {
|
|
||||||
const res = await listProject({});
|
|
||||||
this.projectList = res.rows || [];
|
|
||||||
this.createProjectMarkers();
|
|
||||||
},
|
|
||||||
createProjectMarkers() {
|
|
||||||
// Use Tianditu API to create markers with custom icons
|
|
||||||
// Bind click events to show project details
|
|
||||||
},
|
|
||||||
async loadProjectDevices(projectId) {
|
|
||||||
// Get groupIds from project
|
|
||||||
// For each groupId, load devices
|
|
||||||
// Build projectDeviceMap
|
|
||||||
},
|
|
||||||
focusProject(project) {
|
|
||||||
// Center map on project coordinates
|
|
||||||
// Show project details panel
|
|
||||||
// Load and display associated devices
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4 View Devices by Personnel
|
|
||||||
|
|
||||||
**New Feature**: Add personnel device view panel
|
|
||||||
|
|
||||||
Implementation approach:
|
|
||||||
1. Add a panel in left column showing "人员设备视图"
|
|
||||||
2. User selection dropdown (list all users in tenant)
|
|
||||||
3. When user selected, show:
|
|
||||||
- Projects responsible by this user
|
|
||||||
- Devices in those projects (via groups)
|
|
||||||
- Device status summary
|
|
||||||
|
|
||||||
**API Endpoint** (Backend):
|
|
||||||
Add to IProjectService and implement:
|
|
||||||
```java
|
|
||||||
/**
|
|
||||||
* 查询人员负责的项目及关联设备
|
|
||||||
*/
|
|
||||||
public List<Project> selectUserProjectsAndDevices(Long userId);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend Integration** in home.vue:
|
|
||||||
```javascript
|
|
||||||
// Add to data:
|
|
||||||
selectedUserId: null,
|
|
||||||
userProjects: [],
|
|
||||||
userDeviceSummary: { total: 0, online: 0, offline: 0, alarms: 0 }
|
|
||||||
|
|
||||||
// Add methods:
|
|
||||||
async loadUserDeviceView(userId) {
|
|
||||||
// Load projects by responsibleUserId
|
|
||||||
// Aggregate device stats from all project groups
|
|
||||||
// Display in panel
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Analysis Mode Completion
|
|
||||||
|
|
||||||
#### 4.1 Data Center API Integration
|
|
||||||
|
|
||||||
The DataCenterController already provides comprehensive analysis APIs:
|
|
||||||
- `/data/center/deviceHistory` - Device historical data
|
|
||||||
- `/data/center/deviceStats` - Device statistics
|
|
||||||
- `/data/center/deviceDataQuality` - Data quality analysis
|
|
||||||
- `/data/center/deviceHistogram` - Value distribution
|
|
||||||
- `/data/center/subDeviceComparativeHistory` - Multi-device comparison
|
|
||||||
|
|
||||||
**Enhancement**: Integrate these APIs into bigScreen analysis mode
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/bigScreen/home.vue` (Analysis section)
|
|
||||||
|
|
||||||
Enhancements:
|
|
||||||
1. Connect analysisQuery device selection to project devices
|
|
||||||
2. Load identifierList from selected device's things models
|
|
||||||
3. Display analysis results using existing chart components
|
|
||||||
4. Add project-context filtering (only show devices from selected project)
|
|
||||||
|
|
||||||
### Phase 5: Menu and Permission Configuration
|
|
||||||
|
|
||||||
#### 5.1 Menu SQL Script
|
|
||||||
|
|
||||||
**File**: `/Users/admin/IdeaProjects/fastbee/sql/project-menu.sql`
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 项目管理菜单
|
|
||||||
-- 菜单ID范围: 3600-3620
|
|
||||||
-- 父菜单: 2000 (设备管理)
|
|
||||||
|
|
||||||
INSERT INTO `sys_menu` VALUES(3600, '项目管理', 2000, 1, 'project', 'iot/project/index', NULL, 1, 0, 'C', '0', '0', 'iot:project:list', 'project', 'admin', NOW(), '', NULL, '项目管理菜单');
|
|
||||||
INSERT INTO `sys_menu` VALUES(3601, '项目查询', 3600, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:project:query', '#', 'admin', NOW(), '', NULL, '');
|
|
||||||
INSERT INTO `sys_menu` VALUES(3602, '项目新增', 3600, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:project:add', '#', 'admin', NOW(), '', NULL, '');
|
|
||||||
INSERT INTO `sys_menu` VALUES(3603, '项目修改', 3600, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:project:edit', '#', 'admin', NOW(), '', NULL, '');
|
|
||||||
INSERT INTO `sys_menu` VALUES(3604, '项目删除', 3600, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:project:remove', '#', 'admin', NOW(), '', NULL, '');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.2 Router Configuration
|
|
||||||
|
|
||||||
Add route in `/Users/admin/IdeaProjects/fastbee/yack-web/src/router/index.js`:
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
path: '/iot/project',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/iot/project/index',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'index',
|
|
||||||
component: () => import('@/views/iot/project/index'),
|
|
||||||
name: 'Project',
|
|
||||||
meta: { title: '项目管理', icon: 'project' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 6: Testing and Validation
|
|
||||||
|
|
||||||
#### 6.1 Backend Testing Checklist
|
|
||||||
- Project CRUD operations with tenantId filtering
|
|
||||||
- Project-group relationship updates (batch insert/delete)
|
|
||||||
- User-project queries with proper permission checks
|
|
||||||
- Project device stats calculation (count devices in all associated groups)
|
|
||||||
- Coordinate validation (longitude/latitude ranges)
|
|
||||||
|
|
||||||
#### 6.2 Frontend Testing Checklist
|
|
||||||
- Project list display with pagination
|
|
||||||
- Project creation with coordinate picker
|
|
||||||
- Responsible user selection dropdown
|
|
||||||
- Group binding multi-select
|
|
||||||
- Map marker creation with proper icons
|
|
||||||
- Project click to show details and devices
|
|
||||||
- Personnel device view panel functionality
|
|
||||||
- Analysis mode data loading and chart rendering
|
|
||||||
|
|
||||||
### Implementation Sequence
|
|
||||||
|
|
||||||
**Week 1**: Database and Backend Core
|
|
||||||
1. Create SQL scripts for iot_project and iot_project_group tables
|
|
||||||
2. Execute SQL in database
|
|
||||||
3. Create Project.java and ProjectGroup.java entities
|
|
||||||
4. Create IProjectService interface
|
|
||||||
5. Create ProjectMapper interface and XML
|
|
||||||
6. Implement ProjectServiceImpl
|
|
||||||
7. Create ProjectController with basic CRUD APIs
|
|
||||||
8. Test backend APIs using Swagger
|
|
||||||
|
|
||||||
**Week 2**: Frontend Project Management
|
|
||||||
1. Create project.js API file
|
|
||||||
2. Create project/index.vue configuration page
|
|
||||||
3. Implement project CRUD forms
|
|
||||||
4. Integrate Tianditu coordinate picker
|
|
||||||
5. Implement group binding multi-select
|
|
||||||
6. Add menu SQL and router configuration
|
|
||||||
7. Test frontend project management page
|
|
||||||
|
|
||||||
**Week 3**: Map Mode Enhancements
|
|
||||||
1. Enhance home.vue to load projects
|
|
||||||
2. Implement project marker creation on Tianditu map
|
|
||||||
3. Add status-based marker coloring
|
|
||||||
4. Implement project click handler
|
|
||||||
5. Load project devices via group expansion
|
|
||||||
6. Display project details panel
|
|
||||||
7. Test map mode visualization
|
|
||||||
|
|
||||||
**Week 4**: Personnel View and Analysis
|
|
||||||
1. Implement backend API for user projects and devices
|
|
||||||
2. Add personnel device view panel to home.vue
|
|
||||||
3. Connect analysis mode to DataCenter APIs
|
|
||||||
4. Add project-context filtering in analysis
|
|
||||||
5. Implement device stats aggregation
|
|
||||||
6. Complete analysis chart integration
|
|
||||||
7. End-to-end testing
|
|
||||||
|
|
||||||
### Key Technical Considerations
|
|
||||||
|
|
||||||
#### Multi-Tenancy Support
|
|
||||||
- All queries must filter by tenantId from SecurityUtils.getLoginUser()
|
|
||||||
- Project tenantId should match user's tenant
|
|
||||||
- Use delFlag = '0' for active records filtering
|
|
||||||
|
|
||||||
#### Map Integration
|
|
||||||
- Reuse tdtMap.js from `/Users/admin/IdeaProjects/fastbee/yack-web/src/utils/tdtMap.js`
|
|
||||||
- Tianditu API key: c9ef88370bacf8f51662c01ad80a580c (or custom)
|
|
||||||
- Handle fallback map when Tianditu tiles fail to load
|
|
||||||
- Marker icons: Create custom SVG or use default T.Marker
|
|
||||||
|
|
||||||
#### Performance Optimization
|
|
||||||
- Lazy load project devices on marker click (not on initial load)
|
|
||||||
- Use batch queries for group-device relationships
|
|
||||||
- Implement device stats caching if needed
|
|
||||||
- Consider pagination for large device lists
|
|
||||||
|
|
||||||
#### Data Consistency
|
|
||||||
- When updating project groups: delete old relationships first, then insert new
|
|
||||||
- Validate group existence before binding
|
|
||||||
- Validate user existence as responsible person
|
|
||||||
- Check coordinate ranges (longitude: -180 to 180, latitude: -90 to 90)
|
|
||||||
|
|
||||||
### Potential Challenges
|
|
||||||
|
|
||||||
1. **Map Marker Performance**: With many projects, markers may slow down map rendering
|
|
||||||
- Solution: Implement marker clustering or lazy loading
|
|
||||||
|
|
||||||
2. **Device Aggregation**: Counting devices across multiple groups may be complex
|
|
||||||
- Solution: Use SQL aggregation queries or cache results
|
|
||||||
|
|
||||||
3. **Coordinate Accuracy**: User-entered coordinates may not be precise
|
|
||||||
- Solution: Validate coordinates and provide map picker UI
|
|
||||||
|
|
||||||
4. **Permission Complexity**: Users may need access to projects they don't own
|
|
||||||
- Solution: Implement project sharing or role-based access control
|
|
||||||
|
|
||||||
5. **Analysis Mode Scalability**: Loading large datasets for analysis may timeout
|
|
||||||
- Solution: Implement time range limits and pagination
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
**Backend Dependencies**:
|
|
||||||
- Existing Group and Device entities
|
|
||||||
- SecurityUtils for tenant and user context
|
|
||||||
- BaseEntity for standard fields
|
|
||||||
- AjaxResult for responses
|
|
||||||
- MyBatis for data access
|
|
||||||
|
|
||||||
**Frontend Dependencies**:
|
|
||||||
- Vue.js and Element UI
|
|
||||||
- tdtMap.js for Tianditu integration
|
|
||||||
- echarts-extension-leaflet for charts
|
|
||||||
- Existing API request utility
|
|
||||||
- Vuex store for user context
|
|
||||||
|
|
||||||
**External APIs**:
|
|
||||||
- Tianditu Map API (https://api.tianditu.gov.cn/api?v=4.0)
|
|
||||||
- DataCenter APIs (existing)
|
|
||||||
|
|
||||||
### Critical Files for Implementation
|
|
||||||
|
|
||||||
The following files are most critical for implementing this plan:
|
|
||||||
|
|
||||||
1. `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Project.java`
|
|
||||||
- Core entity defining project structure
|
|
||||||
|
|
||||||
2. `/Users/admin/IdeaProjects/fastbee/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProjectService.java`
|
|
||||||
- Service interface defining all project operations
|
|
||||||
|
|
||||||
3. `/Users/admin/IdeaProjects/fastbee/fastbee-open-api/src/main/java/com/fastbee/controller/project/ProjectController.java`
|
|
||||||
- REST API endpoints for frontend integration
|
|
||||||
|
|
||||||
4. `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/bigScreen/home.vue`
|
|
||||||
- Main big screen page requiring map mode enhancements
|
|
||||||
|
|
||||||
5. `/Users/admin/IdeaProjects/fastbee/yack-web/src/views/iot/project/index.vue`
|
|
||||||
- Project configuration management page
|
|
||||||
|
|
||||||
These files form the core of the implementation, handling data modeling, business logic, API exposure, and user interface respectively.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This implementation plan provides a comprehensive roadmap for transforming the big screen into a full-featured project management and visualization platform, following existing architectural patterns and ensuring seamless integration with the current system.
|
|
||||||
@ -30,7 +30,7 @@ public class DeviceAlertUserController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 查询设备告警用户列表
|
* 查询设备告警用户列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:device:alert:user:list')")
|
@PreAuthorize("@ss.hasAnyPermi('iot:device:alert:user:list,iot:device:list')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo list(DeviceAlertUser deviceAlertUser) {
|
public TableDataInfo list(DeviceAlertUser deviceAlertUser) {
|
||||||
startPage();
|
startPage();
|
||||||
|
|||||||
@ -34,7 +34,7 @@ public class ProductSubGatewayController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 查询网关与子产品关联列表
|
* 查询网关与子产品关联列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('productModbus:gateway:list')")
|
@PreAuthorize("@ss.hasAnyPermi('productModbus:gateway:list,iot:device:list')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperation("查询网关与子产品关联列表")
|
@ApiOperation("查询网关与子产品关联列表")
|
||||||
public TableDataInfo list(ProductSubGateway productSubGateway) {
|
public TableDataInfo list(ProductSubGateway productSubGateway) {
|
||||||
|
|||||||
@ -12,6 +12,6 @@ public class UnknowReqHandler extends ReqAbstractHandler implements IReqHandler
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processMsg(RequestEvent evt) {
|
public void processMsg(RequestEvent evt) {
|
||||||
log.warn("Unknow the method! Method:" + evt.getRequest().getMethod());
|
log.warn("Unknow the method! Method:{}", evt.getRequest().getMethod());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -240,6 +240,12 @@ public class Device extends BaseEntity {
|
|||||||
@ApiModelProperty("所属项目ID")
|
@ApiModelProperty("所属项目ID")
|
||||||
private Long projectId;
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属项目名称
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("所属项目名称")
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMEI(蜂窝模块设备标识)
|
* IMEI(蜂窝模块设备标识)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
package com.fastbee.iot.domain;
|
|
||||||
|
|
||||||
import io.swagger.annotations.ApiModel;
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目-分组关系对象 iot_project_group
|
|
||||||
*
|
|
||||||
* @author fastbee
|
|
||||||
* @date 2026-06-28
|
|
||||||
*/
|
|
||||||
@ApiModel(value = "ProjectGroup", description = "项目-设备分组关系对象 iot_project_group")
|
|
||||||
public class ProjectGroup {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目ID
|
|
||||||
*/
|
|
||||||
@ApiModelProperty("项目ID")
|
|
||||||
private Long projectId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分组ID
|
|
||||||
*/
|
|
||||||
@ApiModelProperty("分组ID")
|
|
||||||
private Long groupId;
|
|
||||||
|
|
||||||
public Long getProjectId() {
|
|
||||||
return projectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProjectId(Long projectId) {
|
|
||||||
this.projectId = projectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getGroupId() {
|
|
||||||
return groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupId(Long groupId) {
|
|
||||||
this.groupId = groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ProjectGroup{" +
|
|
||||||
"projectId=" + projectId +
|
|
||||||
", groupId=" + groupId +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -370,7 +370,8 @@ public interface DeviceMapper extends BaseMapperX<Device> {
|
|||||||
* @param: userName 管理员名称
|
* @param: userName 管理员名称
|
||||||
* @param: deviceIdList 设备id集合
|
* @param: deviceIdList 设备id集合
|
||||||
*/
|
*/
|
||||||
int updateTenantIdByDeptIds(@Param("tenantId") Long tenantId, @Param("tenantName") String tenantName, @Param("deviceIdList") List<Long> deviceIdList);
|
int updateTenantIdByDeptIds(@Param("tenantId") Long tenantId, @Param("tenantName") String tenantName,
|
||||||
|
@Param("clearProject") Boolean clearProject, @Param("deviceIdList") List<Long> deviceIdList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新单个设备所属用户
|
* 更新单个设备所属用户
|
||||||
@ -458,4 +459,10 @@ public interface DeviceMapper extends BaseMapperX<Device> {
|
|||||||
*/
|
*/
|
||||||
int clearDeviceProjectByProjectIds(@Param("projectIds") Long[] projectIds);
|
int clearDeviceProjectByProjectIds(@Param("projectIds") Long[] projectIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备ID查询简易设备信息
|
||||||
|
* @param deviceIds 设备ID
|
||||||
|
* @return 设备数组
|
||||||
|
*/
|
||||||
|
List<Device> selectDeviceShortByIds(@Param("deviceIds") Long[] deviceIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,11 @@ public class DeviceAllShortOutput {
|
|||||||
*/
|
*/
|
||||||
private Long projectId;
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ -216,6 +221,14 @@ public class DeviceAllShortOutput {
|
|||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProjectName() {
|
||||||
|
return projectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjectName(String projectName) {
|
||||||
|
this.projectName = projectName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getUserName() {
|
public String getUserName() {
|
||||||
return userName;
|
return userName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,11 @@ public class DeviceShortOutput {
|
|||||||
*/
|
*/
|
||||||
private Long projectId;
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 经度
|
* 经度
|
||||||
*/
|
*/
|
||||||
@ -441,6 +446,14 @@ public class DeviceShortOutput {
|
|||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getProjectName() {
|
||||||
|
return projectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjectName(String projectName) {
|
||||||
|
this.projectName = projectName;
|
||||||
|
}
|
||||||
|
|
||||||
public BigDecimal getLongitude() {
|
public BigDecimal getLongitude() {
|
||||||
return longitude;
|
return longitude;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,4 +132,8 @@ public class ThingsModelDTO extends BaseEntity {
|
|||||||
|
|
||||||
private String shadow = "";
|
private String shadow = "";
|
||||||
|
|
||||||
|
private Integer slaveId;
|
||||||
|
|
||||||
|
private String subDeviceName;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -458,7 +458,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
DeviceRecord deviceRecord = deviceRecords.get(0);
|
DeviceRecord deviceRecord = deviceRecords.get(0);
|
||||||
if (deviceRecord.getTargetDeptId().equals(user.getDeptId())) {
|
if (deviceRecord.getTargetDeptId().equals(user.getDeptId())) {
|
||||||
deviceRecordMapper.deleteDeviceRecordById(deviceRecord.getId());
|
deviceRecordMapper.deleteDeviceRecordById(deviceRecord.getId());
|
||||||
int result = deviceMapper.updateTenantIdByDeptIds(deviceRecord.getTenantId(), deviceRecord.getTenantName(), Collections.singletonList(deviceId));
|
int result = deviceMapper.updateTenantIdByDeptIds(deviceRecord.getTenantId(), deviceRecord.getTenantName(), true, Collections.singletonList(deviceId));
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
return AjaxResult.success("已解除设备分享关系");
|
return AjaxResult.success("已解除设备分享关系");
|
||||||
}
|
}
|
||||||
@ -2095,7 +2095,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
List<Long> deviceIdList = Arrays.stream(deviceIds.split(","))
|
List<Long> deviceIdList = Arrays.stream(deviceIds.split(","))
|
||||||
.map(d -> Long.parseLong(d.trim())).distinct()
|
.map(d -> Long.parseLong(d.trim())).distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
int result = deviceMapper.updateTenantIdByDeptIds(destDeptUserId, destSysUser.getUserName(), deviceIdList);
|
int result = deviceMapper.updateTenantIdByDeptIds(destDeptUserId, destSysUser.getUserName(), false, deviceIdList);
|
||||||
DeviceRecord deviceRecord = new DeviceRecord();
|
DeviceRecord deviceRecord = new DeviceRecord();
|
||||||
Long currDeptUserId = currSysUser.getDept().getDeptUserId();
|
Long currDeptUserId = currSysUser.getDept().getDeptUserId();
|
||||||
Long currDeptId = currSysUser.getDeptId();
|
Long currDeptId = currSysUser.getDeptId();
|
||||||
@ -2190,7 +2190,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
List<Long> deviceIdList = Arrays.stream(deviceIds.split(","))
|
List<Long> deviceIdList = Arrays.stream(deviceIds.split(","))
|
||||||
.map(d -> Long.parseLong(d.trim())).distinct()
|
.map(d -> Long.parseLong(d.trim())).distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
int result = deviceMapper.updateTenantIdByDeptIds(currDeptUserId, sysUser.getUserName(), deviceIdList);
|
int result = deviceMapper.updateTenantIdByDeptIds(currDeptUserId, sysUser.getUserName(), false, deviceIdList);
|
||||||
List<DeviceRecord> deviceRecordList = new ArrayList<>();
|
List<DeviceRecord> deviceRecordList = new ArrayList<>();
|
||||||
List<Device> deviceList = deviceMapper.selectDeviceListByDeviceIds(deviceIdList);
|
List<Device> deviceList = deviceMapper.selectDeviceListByDeviceIds(deviceIdList);
|
||||||
for (Device device : deviceList) {
|
for (Device device : deviceList) {
|
||||||
@ -2264,6 +2264,8 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
thingsModelDTO.setIsShadow(1 == device.getIsShadow());
|
thingsModelDTO.setIsShadow(1 == device.getIsShadow());
|
||||||
thingsModelDTO.setCanSend(canSend);
|
thingsModelDTO.setCanSend(canSend);
|
||||||
thingsModelDTO.setParentSerialNumber(device.getSerialNumber());
|
thingsModelDTO.setParentSerialNumber(device.getSerialNumber());
|
||||||
|
thingsModelDTO.setSlaveId(subDeviceListVO.getSlaveId());
|
||||||
|
thingsModelDTO.setSubDeviceName(subDeviceListVO.getSubDeviceName());
|
||||||
}
|
}
|
||||||
list.addAll(subList);
|
list.addAll(subList);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,28 @@
|
|||||||
package com.fastbee.iot.service.impl;
|
package com.fastbee.iot.service.impl;
|
||||||
|
|
||||||
|
import com.fastbee.common.enums.DeviceDistributeTypeEnum;
|
||||||
|
import com.fastbee.common.enums.DeviceRecordTypeEnum;
|
||||||
|
import com.fastbee.common.enums.StatusEnum;
|
||||||
import com.fastbee.common.utils.DateUtils;
|
import com.fastbee.common.utils.DateUtils;
|
||||||
import com.fastbee.common.core.domain.entity.SysUser;
|
import com.fastbee.common.core.domain.entity.SysUser;
|
||||||
|
import com.fastbee.common.utils.SecurityUtils;
|
||||||
import com.fastbee.iot.domain.Device;
|
import com.fastbee.iot.domain.Device;
|
||||||
|
import com.fastbee.iot.domain.DeviceRecord;
|
||||||
import com.fastbee.iot.domain.Project;
|
import com.fastbee.iot.domain.Project;
|
||||||
import com.fastbee.iot.mapper.DeviceMapper;
|
import com.fastbee.iot.mapper.DeviceMapper;
|
||||||
|
import com.fastbee.iot.mapper.DeviceRecordMapper;
|
||||||
import com.fastbee.iot.mapper.ProjectMapper;
|
import com.fastbee.iot.mapper.ProjectMapper;
|
||||||
import com.fastbee.iot.service.IProjectService;
|
import com.fastbee.iot.service.IProjectService;
|
||||||
|
import com.fastbee.system.service.ISysUserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import javax.annotation.Resource;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.fastbee.common.utils.SecurityUtils.getLoginUser;
|
import static com.fastbee.common.utils.SecurityUtils.getLoginUser;
|
||||||
import static com.fastbee.common.utils.SecurityUtils.getDeptId;
|
import static com.fastbee.common.utils.SecurityUtils.getDeptId;
|
||||||
@ -24,6 +33,7 @@ import static com.fastbee.common.utils.SecurityUtils.getDeptId;
|
|||||||
* @author fastbee
|
* @author fastbee
|
||||||
* @date 2026-06-28
|
* @date 2026-06-28
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class ProjectServiceImpl implements IProjectService {
|
public class ProjectServiceImpl implements IProjectService {
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -32,6 +42,12 @@ public class ProjectServiceImpl implements IProjectService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private DeviceMapper deviceMapper;
|
private DeviceMapper deviceMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DeviceRecordMapper deviceRecordMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISysUserService userService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目
|
* 查询项目
|
||||||
*
|
*
|
||||||
@ -51,12 +67,23 @@ public class ProjectServiceImpl implements IProjectService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<Project> selectProjectList(Project project) {
|
public List<Project> selectProjectList(Project project) {
|
||||||
Long deptId = getDeptId();
|
// Long deptId = getDeptId();
|
||||||
if (Objects.isNull(deptId)) {
|
// if (Objects.isNull(deptId)) {
|
||||||
project.setResponsibleUserId(getLoginUser().getUserId());
|
// project.setResponsibleUserId(getLoginUser().getUserId());
|
||||||
|
// } else {
|
||||||
|
// Long deptUserId = getLoginUser().getUser().getDept().getDeptUserId();
|
||||||
|
// project.setTenantId(deptUserId);
|
||||||
|
// }
|
||||||
|
Long userId = project.getResponsibleUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
userId = getLoginUser().getUserId();
|
||||||
|
}
|
||||||
|
boolean admin = SecurityUtils.isAdmin(userId);
|
||||||
|
if (admin) {
|
||||||
|
project.setTenantId(userId);
|
||||||
|
project.setResponsibleUserId(null);
|
||||||
} else {
|
} else {
|
||||||
Long deptUserId = getLoginUser().getUser().getDept().getDeptUserId();
|
project.setResponsibleUserId(userId);
|
||||||
project.setTenantId(deptUserId);
|
|
||||||
}
|
}
|
||||||
return projectMapper.selectProjectList(project);
|
return projectMapper.selectProjectList(project);
|
||||||
}
|
}
|
||||||
@ -202,12 +229,64 @@ public class ProjectServiceImpl implements IProjectService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int updateProjectDevices(Long projectId, Long[] deviceIds) {
|
public int updateProjectDevices(Long projectId, Long[] deviceIds) {
|
||||||
// 先清除项目下不在新列表中的设备归属
|
Project project = projectMapper.selectProjectById(projectId);
|
||||||
deviceMapper.clearDeviceProjectExcept(projectId, deviceIds);
|
if (project == null || project.getResponsibleUserId() == null) {
|
||||||
// 再将新列表中的设备归属到本项目(覆盖式转移)
|
throw new IllegalArgumentException("项目不存在或未设置负责人");
|
||||||
if (deviceIds != null && deviceIds.length > 0) {
|
|
||||||
deviceMapper.updateDeviceProjectByIds(projectId, deviceIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deviceIds == null || deviceIds.length == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
deviceMapper.clearDeviceProjectExcept(projectId, deviceIds);
|
||||||
|
|
||||||
|
SysUser currSysUser = getLoginUser().getUser();
|
||||||
|
|
||||||
|
Long targetUserId = project.getResponsibleUserId();
|
||||||
|
SysUser targetUser = userService.selectUserById(targetUserId);
|
||||||
|
if (targetUser == null) {
|
||||||
|
throw new IllegalArgumentException("项目负责人不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long[] selectedDeviceIds = Arrays.stream(deviceIds)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.toArray(Long[]::new);
|
||||||
|
if (selectedDeviceIds.length == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, Device> deviceMap = deviceMapper.selectDeviceShortByIds(selectedDeviceIds)
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(Device::getDeviceId, Function.identity(), (v1, v2) -> v1));
|
||||||
|
List<Long> transferDeviceIds = new ArrayList<>();
|
||||||
|
List<DeviceRecord> deviceRecords = new ArrayList<>();
|
||||||
|
for (Long deviceId : selectedDeviceIds) {
|
||||||
|
Device device = deviceMap.get(deviceId);
|
||||||
|
if (device == null || Objects.equals(device.getTenantId(), targetUserId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
transferDeviceIds.add(deviceId);
|
||||||
|
DeviceRecord deviceRecord = new DeviceRecord();
|
||||||
|
deviceRecord.setOperateDeptId(currSysUser.getDeptId())
|
||||||
|
.setTargetDeptId(targetUser.getDeptId())
|
||||||
|
.setDeviceId(device.getDeviceId())
|
||||||
|
.setProductId(device.getProductId())
|
||||||
|
.setSerialNumber(device.getSerialNumber())
|
||||||
|
.setType(DeviceRecordTypeEnum.ASSIGNMENT_DETAIL.getType())
|
||||||
|
.setDistributeType(DeviceDistributeTypeEnum.SELECT.getType())
|
||||||
|
.setTenantId(device.getTenantId())
|
||||||
|
.setTenantName(device.getTenantName())
|
||||||
|
.setStatus(StatusEnum.SUCCESS.getStatus())
|
||||||
|
.setCreateBy(currSysUser.getUserName());
|
||||||
|
deviceRecords.add(deviceRecord);
|
||||||
|
}
|
||||||
|
if (!transferDeviceIds.isEmpty()) {
|
||||||
|
deviceMapper.updateTenantIdByDeptIds(targetUserId, targetUser.getUserName(), false, transferDeviceIds);
|
||||||
|
deviceRecordMapper.insertBatch(deviceRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMapper.updateDeviceProjectByIds(projectId, selectedDeviceIds);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="imei" column="imei" />
|
<result property="imei" column="imei" />
|
||||||
<result property="iccid" column="iccid" />
|
<result property="iccid" column="iccid" />
|
||||||
<result property="projectId" column="project_id" />
|
<result property="projectId" column="project_id" />
|
||||||
|
<result property="projectName" column="project_name" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -82,6 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="imei" column="imei" />
|
<result property="imei" column="imei" />
|
||||||
<result property="iccid" column="iccid" />
|
<result property="iccid" column="iccid" />
|
||||||
<result property="projectId" column="project_id" />
|
<result property="projectId" column="project_id" />
|
||||||
|
<result property="projectName" column="project_name" />
|
||||||
<result property="longitude" column="longitude" />
|
<result property="longitude" column="longitude" />
|
||||||
<result property="latitude" column="latitude" />
|
<result property="latitude" column="latitude" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
@ -107,6 +109,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="imei" column="imei" />
|
<result property="imei" column="imei" />
|
||||||
<result property="iccid" column="iccid" />
|
<result property="iccid" column="iccid" />
|
||||||
<result property="projectId" column="project_id" />
|
<result property="projectId" column="project_id" />
|
||||||
|
<result property="projectName" column="project_name" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<resultMap type="com.fastbee.iot.model.UserAndTenant" id="UserAndTenantResult">
|
<resultMap type="com.fastbee.iot.model.UserAndTenant" id="UserAndTenantResult">
|
||||||
@ -213,7 +216,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
imei,
|
imei,
|
||||||
iccid,
|
iccid,
|
||||||
remark,
|
remark,
|
||||||
slave_id
|
slave_id,
|
||||||
|
project_id,
|
||||||
|
project_name
|
||||||
from iot_device
|
from iot_device
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@ -236,7 +241,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
imei,
|
imei,
|
||||||
iccid,
|
iccid,
|
||||||
img_url,
|
img_url,
|
||||||
slave_id
|
slave_id,
|
||||||
|
project_id,
|
||||||
|
project_name
|
||||||
from iot_device
|
from iot_device
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@ -285,7 +292,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="selectUnAuthDeviceList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceResult">
|
<select id="selectUnAuthDeviceList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceResult">
|
||||||
select d.device_id, d.device_name, d.product_id, d.product_name, d.tenant_id, d.tenant_name,
|
select d.device_id, d.device_name, d.product_id, d.product_name, d.tenant_id, d.tenant_name,
|
||||||
d.serial_number,d.gw_dev_code, d.firmware_version, d.status,d.is_shadow,d.is_simulate ,d.location_way,d.active_time, d.img_url,a.device_id as auth_device_id
|
d.serial_number,d.gw_dev_code, d.firmware_version, d.status,d.is_shadow,d.is_simulate ,d.location_way,d.active_time, d.img_url,
|
||||||
|
d.project_id, d.project_name, a.device_id as auth_device_id
|
||||||
from iot_device d
|
from iot_device d
|
||||||
left join iot_product_authorize a on a.device_id=d.device_id
|
left join iot_product_authorize a on a.device_id=d.device_id
|
||||||
<where>
|
<where>
|
||||||
@ -376,7 +384,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="selectAllDeviceShortList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceAllShortResult">
|
<select id="selectAllDeviceShortList" parameterType="com.fastbee.iot.domain.Device" resultMap="DeviceAllShortResult">
|
||||||
select d.device_id, d.device_name, d.product_name,p.device_type, d.tenant_name, d.serial_number,d.gw_dev_code, d.firmware_version, d.status,d.rssi,d.is_shadow ,
|
select d.device_id, d.device_name, d.product_name,p.device_type, d.tenant_name, d.serial_number,d.gw_dev_code, d.firmware_version, d.status,d.rssi,d.is_shadow ,
|
||||||
d.location_way, d.active_time,d.network_address,d.longitude,d.latitude,d.project_id
|
d.location_way, d.active_time,d.network_address,d.longitude,d.latitude,d.project_id,d.project_name
|
||||||
from iot_device d
|
from iot_device d
|
||||||
left join iot_product p on p.product_id=d.product_id
|
left join iot_product p on p.product_id=d.product_id
|
||||||
<where>
|
<where>
|
||||||
@ -517,7 +525,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
select d.device_id, d.device_name, d.product_id, p.product_name,p.device_type, d.tenant_id, d.tenant_name,
|
select d.device_id, d.device_name, d.product_id, p.product_name,p.device_type, d.tenant_id, d.tenant_name,
|
||||||
d.serial_number, d.firmware_version,d.wireless_version, d.status, d.rssi,d.is_shadow,d.is_simulate ,d.location_way,d.things_model_value,
|
d.serial_number, d.firmware_version,d.wireless_version, d.status, d.rssi,d.is_shadow,d.is_simulate ,d.location_way,d.things_model_value,
|
||||||
d.network_address, d.network_ip, d.longitude, d.latitude, d.active_time, d.create_time, d.update_time,
|
d.network_address, d.network_ip, d.longitude, d.latitude, d.active_time, d.create_time, d.update_time,
|
||||||
d.img_url,d.summary,d.imei,d.iccid,d.remark,p.guid, p.transport, p.protocol_code from iot_device d
|
d.img_url,d.summary,d.imei,d.iccid,d.remark,d.project_id,d.project_name,p.guid, p.transport, p.protocol_code from iot_device d
|
||||||
left join iot_product p on p.product_id=d.product_id
|
left join iot_product p on p.product_id=d.product_id
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
</select>
|
</select>
|
||||||
@ -970,7 +978,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
select d.device_id, d.device_name, d.product_id, d.product_name,p.device_type,
|
select d.device_id, d.device_name, d.product_id, d.product_name,p.device_type,
|
||||||
d.tenant_id, d.tenant_name, d.serial_number,d.gw_dev_code,
|
d.tenant_id, d.tenant_name, d.serial_number,d.gw_dev_code,
|
||||||
d.firmware_version, d.status,d.rssi,d.is_shadow,d.is_simulate ,d.location_way,
|
d.firmware_version, d.status,d.rssi,d.is_shadow,d.is_simulate ,d.location_way,
|
||||||
d.things_model_value, d.active_time,d.create_time,d.img_url,
|
d.things_model_value, d.active_time,d.create_time,d.img_url,d.project_id,d.project_name,
|
||||||
(select count(1) from iot_device d1 where d1.gw_dev_code = d.serial_number) as sub_device_count,
|
(select count(1) from iot_device d1 where d1.gw_dev_code = d.serial_number) as sub_device_count,
|
||||||
p.protocol_code,p.transport, u.is_owner
|
p.protocol_code,p.transport, u.is_owner
|
||||||
from (
|
from (
|
||||||
@ -1040,7 +1048,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<select id="listTerminalUserByGroup" resultType="com.fastbee.iot.domain.Device">
|
<select id="listTerminalUserByGroup" resultType="com.fastbee.iot.domain.Device">
|
||||||
select d.device_id, d.device_name, d.product_name, d.serial_number,d.gw_dev_code, d.firmware_version,d.wireless_version, d.status,d.rssi,d.is_shadow ,
|
select d.device_id, d.device_name, d.product_name, d.serial_number,d.gw_dev_code, d.firmware_version,d.wireless_version, d.status,d.rssi,d.is_shadow ,
|
||||||
d.location_way, d.active_time,d.network_address,d.longitude,d.latitude
|
d.location_way, d.active_time,d.network_address,d.longitude,d.latitude,d.project_id,d.project_name
|
||||||
from (
|
from (
|
||||||
select device_id, 1 AS is_owner
|
select device_id, 1 AS is_owner
|
||||||
from iot_device_user
|
from iot_device_user
|
||||||
@ -1091,6 +1099,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
update iot_device
|
update iot_device
|
||||||
set tenant_id = #{tenantId},
|
set tenant_id = #{tenantId},
|
||||||
tenant_name = #{tenantName}
|
tenant_name = #{tenantName}
|
||||||
|
<if test="clearProject == true">,project_id = null </if>
|
||||||
where device_id in
|
where device_id in
|
||||||
<foreach collection="deviceIdList" item="deviceId" open="(" separator="," close=")">
|
<foreach collection="deviceIdList" item="deviceId" open="(" separator="," close=")">
|
||||||
#{deviceId}
|
#{deviceId}
|
||||||
@ -1167,4 +1176,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</foreach>
|
</foreach>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
|
||||||
|
<select id="selectDeviceShortByIds" resultType="com.fastbee.iot.domain.Device">
|
||||||
|
SELECT device_id, product_id, serial_number, tenant_id, tenant_name
|
||||||
|
FROM iot_device
|
||||||
|
WHERE del_flag = '0' AND device_id IN
|
||||||
|
<foreach item="id" collection="deviceIds" open="(" separator="," close=")">
|
||||||
|
#{id}
|
||||||
|
</foreach>
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
-- ========================================
|
|
||||||
-- FastBee项目设备关联改造迁移脚本
|
|
||||||
-- 创建日期: 2026-08-01
|
|
||||||
-- 描述: 将项目-设备关联从两层关联表改为设备表直接外键
|
|
||||||
-- ========================================
|
|
||||||
|
|
||||||
-- 1. 设备表新增所属项目字段
|
|
||||||
ALTER TABLE `iot_device`
|
|
||||||
ADD COLUMN `project_id` BIGINT(20) DEFAULT NULL COMMENT '所属项目ID' AFTER `tenant_name`,
|
|
||||||
ADD INDEX `idx_project_id` (`project_id`);
|
|
||||||
|
|
||||||
-- 2. 废弃项目-分组关系表(未上线,无需迁移数据)
|
|
||||||
DROP TABLE IF EXISTS `iot_project_group`;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 变更说明:
|
|
||||||
-- 1. 项目与设备的关系从"项目→分组→设备"两层间接关联,简化为"项目→设备"直接关联
|
|
||||||
-- 2. 一个设备最多属于一个项目(1:1),一个项目可包含多个设备(1:N)
|
|
||||||
-- 3. iot_device.project_id 为 NULL 表示设备未关联项目
|
|
||||||
-- 4. iot_group / iot_device_group 表保留,继续用于独立的设备分组管理功能
|
|
||||||
-- ========================================
|
|
||||||
@ -34,6 +34,14 @@ CREATE TABLE `iot_project` (
|
|||||||
KEY `idx_create_time` (`create_time`)
|
KEY `idx_create_time` (`create_time`)
|
||||||
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='业务项目表';
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='业务项目表';
|
||||||
|
|
||||||
|
|
||||||
|
-- 1. 设备表新增所属项目字段
|
||||||
|
ALTER TABLE `iot_device`
|
||||||
|
ADD COLUMN `project_id` BIGINT(20) DEFAULT NULL COMMENT '所属项目ID' AFTER `tenant_name`,
|
||||||
|
ADD COLUMN project_name VARCHAR(64) COMMENT '所属项目名称',
|
||||||
|
ADD INDEX `idx_project_id` (`project_id`);
|
||||||
|
|
||||||
|
|
||||||
-- ========================================
|
-- ========================================
|
||||||
-- 层级关系说明:
|
-- 层级关系说明:
|
||||||
-- User (responsible_user_id) → Project (project_id) → Device (project_id)
|
-- User (responsible_user_id) → Project (project_id) → Device (project_id)
|
||||||
|
|||||||
@ -602,6 +602,84 @@
|
|||||||
background: rgba(14, 59, 126, .62);
|
background: rgba(14, 59, 126, .62);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-row {
|
||||||
|
cursor: default;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 10px 12px;
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-row--sub {
|
||||||
|
border-left: 3px solid rgba(255, 184, 77, .65);
|
||||||
|
background: rgba(18, 28, 53, .52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-group-title {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #5edaff;
|
||||||
|
background: linear-gradient(90deg, rgba(45, 120, 255, .15), transparent);
|
||||||
|
border-left: 3px solid rgba(94, 218, 255, .65);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-group-title--sub {
|
||||||
|
color: #ffb84d;
|
||||||
|
background: linear-gradient(90deg, rgba(255, 184, 77, .15), transparent);
|
||||||
|
border-left-color: rgba(255, 184, 77, .65);
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-name {
|
||||||
|
color: #edf9ff;
|
||||||
|
font-size: 13px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-id {
|
||||||
|
color: #80a7ca;
|
||||||
|
font-size: 11px;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-source {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(255, 184, 77, .18);
|
||||||
|
color: #ffb84d;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.device-dot {
|
.device-dot {
|
||||||
width: 9px;
|
width: 9px;
|
||||||
height: 9px;
|
height: 9px;
|
||||||
@ -632,8 +710,7 @@
|
|||||||
|
|
||||||
.device-meta,
|
.device-meta,
|
||||||
.alarm-meta,
|
.alarm-meta,
|
||||||
.project-meta,
|
.project-meta {
|
||||||
.model-row small {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #80a7ca;
|
color: #80a7ca;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@ -255,14 +255,39 @@
|
|||||||
<div class="panel-card analysis-card">
|
<div class="panel-card analysis-card">
|
||||||
<div class="panel-title">测点 / 物模型</div>
|
<div class="panel-title">测点 / 物模型</div>
|
||||||
<div class="model-list">
|
<div class="model-list">
|
||||||
|
<!-- 网关自身物模型 -->
|
||||||
|
<template v-if="groupedIdentifierList.gateway.length > 0">
|
||||||
|
<div class="model-group-title">网关设备</div>
|
||||||
<div
|
<div
|
||||||
v-for="(model, index) in identifierList"
|
v-for="(model, index) in groupedIdentifierList.gateway"
|
||||||
:key="'model-' + model.modelId + '-' + (model.serialNumber || '') + '-' + index"
|
:key="'gw-' + model.modelId + '-' + index"
|
||||||
class="model-row"
|
class="model-row"
|
||||||
>
|
>
|
||||||
<span>{{ model.modelName || model.identifier }}</span>
|
<div class="model-info">
|
||||||
<small>{{ model.identifier }}</small>
|
<span class="model-name">{{ model.modelName || model.identifier }}</span>
|
||||||
|
<small class="model-id">{{ model.identifier }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 子设备物模型,按从机分组 -->
|
||||||
|
<template v-for="group in groupedIdentifierList.subDevices">
|
||||||
|
<div :key="'group-title-' + group.slaveId" class="model-group-title model-group-title--sub">
|
||||||
|
<i class="el-icon-connection"></i>
|
||||||
|
{{ group.subDeviceName }} (从机{{ group.slaveId }})
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(model, index) in group.models"
|
||||||
|
:key="'sub-' + group.slaveId + '-' + model.modelId + '-' + index"
|
||||||
|
class="model-row model-row--sub"
|
||||||
|
>
|
||||||
|
<div class="model-info">
|
||||||
|
<span class="model-name">{{ model.modelName || model.identifier }}</span>
|
||||||
|
<small class="model-id">{{ model.identifier }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div v-if="identifierList.length === 0" class="empty-tip">选择设备后展示测点</div>
|
<div v-if="identifierList.length === 0" class="empty-tip">选择设备后展示测点</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -524,6 +549,34 @@ export default {
|
|||||||
});
|
});
|
||||||
return rows.slice(0, 100);
|
return rows.slice(0, 100);
|
||||||
},
|
},
|
||||||
|
groupedIdentifierList() {
|
||||||
|
// 按从机分组显示物模型
|
||||||
|
const gatewayModels = [];
|
||||||
|
const subDeviceGroups = {};
|
||||||
|
|
||||||
|
this.identifierList.forEach((model) => {
|
||||||
|
if (model.parentSerialNumber) {
|
||||||
|
// 子设备物模型,按 slaveId 分组
|
||||||
|
const slaveId = model.slaveId || 0;
|
||||||
|
if (!subDeviceGroups[slaveId]) {
|
||||||
|
subDeviceGroups[slaveId] = {
|
||||||
|
slaveId: slaveId,
|
||||||
|
subDeviceName: model.subDeviceName || `从机${slaveId}`,
|
||||||
|
models: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
subDeviceGroups[slaveId].models.push(model);
|
||||||
|
} else {
|
||||||
|
// 网关自身物模型
|
||||||
|
gatewayModels.push(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
gateway: gatewayModels,
|
||||||
|
subDevices: Object.values(subDeviceGroups).sort((a, b) => a.slaveId - b.slaveId)
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
activeMode(value) {
|
activeMode(value) {
|
||||||
@ -772,9 +825,9 @@ export default {
|
|||||||
const device = devices[0];
|
const device = devices[0];
|
||||||
this.selectedAnalysisDevice = device;
|
this.selectedAnalysisDevice = device;
|
||||||
this.analysisQuery.deviceId = device.deviceId;
|
this.analysisQuery.deviceId = device.deviceId;
|
||||||
this.searchAnalysisDevices('').then(() => {
|
// 用现有列表作为初始选项,保证下拉回显;全量选项等真正进入分析页再懒加载
|
||||||
|
this.analysisDeviceOptions = devices.slice(0, 10);
|
||||||
this.handleAnalysisDeviceChange(device.deviceId);
|
this.handleAnalysisDeviceChange(device.deviceId);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
searchAnalysisDevices(keyword) {
|
searchAnalysisDevices(keyword) {
|
||||||
const kw = String(keyword || '').trim();
|
const kw = String(keyword || '').trim();
|
||||||
@ -795,9 +848,7 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onAnalysisDeviceVisible(visible) {
|
onAnalysisDeviceVisible(visible) {
|
||||||
if (visible && this.analysisDeviceOptions.length === 0) {
|
if (visible) this.searchAnalysisDevices('');
|
||||||
this.searchAnalysisDevices('');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
renderCharts() {
|
renderCharts() {
|
||||||
if (this.activeMode !== 'screen') return;
|
if (this.activeMode !== 'screen') return;
|
||||||
|
|||||||
@ -149,8 +149,8 @@
|
|||||||
<i class="el-icon-close"></i>
|
<i class="el-icon-close"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="cockpit-modal__body">
|
<div class="cockpit-modal__body cockpit-modal__body--pwd">
|
||||||
<el-form ref="pwdForm" :model="pwdForm" :rules="pwdRules" label-width="100px" size="mini" class="profile-form">
|
<el-form ref="pwdForm" :model="pwdForm" :rules="pwdRules" label-width="120px" size="large" class="profile-form">
|
||||||
<el-form-item label="旧密码" prop="oldPassword">
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
<el-input v-model="pwdForm.oldPassword" type="password" show-password placeholder="请输入旧密码" />
|
<el-input v-model="pwdForm.oldPassword" type="password" show-password placeholder="请输入旧密码" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -161,8 +161,8 @@
|
|||||||
<el-input v-model="pwdForm.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
<el-input v-model="pwdForm.confirmPassword" type="password" show-password placeholder="请再次输入新密码" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="profile-form__actions">
|
<el-form-item class="profile-form__actions">
|
||||||
<el-button type="primary" size="mini" :loading="pwdLoading" @click="savePwd">保存</el-button>
|
<el-button type="primary" size="large" :loading="pwdLoading" @click="savePwd">保存</el-button>
|
||||||
<el-button size="mini" @click="closePwd">取消</el-button>
|
<el-button size="large" @click="closePwd">取消</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
@ -582,7 +582,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--pwd {
|
&--pwd {
|
||||||
width: 460px;
|
width: 860px;
|
||||||
|
height: 540px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--avatar {
|
&--avatar {
|
||||||
@ -626,22 +627,62 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cockpit-modal__close {
|
.cockpit-modal__close {
|
||||||
width: 28px;
|
position: relative;
|
||||||
height: 28px;
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid rgba(99, 184, 255, 0.26);
|
padding: 0;
|
||||||
color: rgba(161, 212, 255, 0.6);
|
border: none;
|
||||||
background: rgba(4, 16, 56, 0.3);
|
outline: none;
|
||||||
|
color: #6fd6ff;
|
||||||
|
font-size: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 50%;
|
background: linear-gradient(135deg, rgba(18, 86, 180, 0.55), rgba(4, 14, 48, 0.65));
|
||||||
transition: all 0.18s ease;
|
clip-path: polygon(50% 0, 86% 14%, 100% 50%, 86% 86%, 50% 100%, 14% 86%, 0 50%, 14% 14%);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(72, 201, 255, 0.35);
|
||||||
|
transition: color 0.2s ease, filter 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 10%;
|
||||||
|
right: 10%;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(122, 232, 255, 0.95), transparent);
|
||||||
|
box-shadow: 0 0 6px rgba(122, 232, 255, 0.8);
|
||||||
|
transform: scaleX(0);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 0.22s ease, opacity 0.22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #8fe8ff;
|
color: #eafcff;
|
||||||
border-color: rgba(104, 224, 255, 0.64);
|
filter: drop-shadow(0 0 7px rgba(62, 201, 255, 0.8));
|
||||||
background: rgba(7, 35, 95, 0.72);
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(126, 234, 255, 0.9),
|
||||||
|
inset 0 0 10px rgba(62, 201, 255, 0.35);
|
||||||
|
|
||||||
|
i {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,6 +692,15 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
|
|
||||||
|
&--pwd {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.profile-form {
|
||||||
|
width: 520px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-aside {
|
.profile-aside {
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export default {
|
|||||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||||
resetQuery() { this.queryParams = this.createQueryParams(); this.getList() },
|
resetQuery() { this.queryParams = this.createQueryParams(); this.getList() },
|
||||||
async handleConfirm() {
|
async handleConfirm() {
|
||||||
if (this.transferCount) await this.$modal.confirm(`有 ${this.transferCount} 台设备将从其他项目转移到“${this.projectName}”,是否继续?`)
|
if (this.transferCount) await this.$modal.confirm(`有 ${this.transferCount} 台设备将从其他项目转移到【${this.projectName}】,是否继续?`)
|
||||||
this.saving = true
|
this.saving = true
|
||||||
try { await updateProjectDevices(this.projectId, this.selectedDeviceIds); this.$modal.msgSuccess('项目设备保存成功'); this.open = false; this.$emit('refresh') } finally { this.saving = false }
|
try { await updateProjectDevices(this.projectId, this.selectedDeviceIds); this.$modal.msgSuccess('项目设备保存成功'); this.open = false; this.$emit('refresh') } finally { this.saving = false }
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user