diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ee3a2356..ec3d6ecc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -27,7 +27,9 @@ "Bash(nvm use:*)", "Bash(npm run:*)", "mcp__filesystem__read_text_file", - "Bash(sips:*)" + "Bash(sips:*)", + "Bash(grep:*)", + "Bash(source ~/.nvm/nvm.sh)" ] } } diff --git a/docs/big-screen-implementation-plan.md b/docs/big-screen-implementation-plan.md new file mode 100644 index 00000000..1a6ebd03 --- /dev/null +++ b/docs/big-screen-implementation-plan.md @@ -0,0 +1,790 @@ +# 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 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 selectProjectList(Project project); + + /** + * 根据负责人查询项目 + */ + public List selectProjectsByUserId(Long userId); + + /** + * 根据租户查询项目 + */ + public List 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 selectProjectList(Project project); + public List selectProjectsByUserId(Long userId); + public List 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 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 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. \ No newline at end of file diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/project/ProjectController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/project/ProjectController.java new file mode 100644 index 00000000..9bd1ce1f --- /dev/null +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/project/ProjectController.java @@ -0,0 +1,199 @@ +package com.fastbee.controller.project; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.Device; +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 javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; + +/** + * 项目管理Controller + * + * @author fastbee + * @date 2026-06-28 + */ +@Api(tags = "项目管理") +@RestController +@RequestMapping("/iot/project") +public class ProjectController extends BaseController { + @Autowired + private IProjectService projectService; + + /** + * 查询项目列表 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/list") + @ApiOperation("项目分页列表") + public TableDataInfo list(Project project) { + startPage(); + List list = projectService.selectProjectList(project); + return getDataTable(list); + } + + /** + * 导出项目列表 + */ + @PreAuthorize("@ss.hasPermi('iot:project:export')") + @Log(title = "项目", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出项目") + public void export(HttpServletResponse response, Project project) { + List list = projectService.selectProjectList(project); + ExcelUtil util = new ExcelUtil(Project.class); + util.exportExcel(response, list, "项目数据"); + } + + /** + * 获取项目详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:project:query')") + @GetMapping(value = "/{projectId}") + @ApiOperation("获取项目详情") + public AjaxResult getInfo(@PathVariable("projectId") Long projectId) { + Project project = projectService.selectProjectById(projectId); + if (project != null) { + project.setGroupIds(projectService.selectGroupIdsByProjectId(projectId)); + } + return AjaxResult.success(project); + } + + /** + * 获取项目下的所有关联分组ID数组 + */ + @PreAuthorize("@ss.hasPermi('iot:project:query')") + @GetMapping(value = "/getGroupIds/{projectId}") + @ApiOperation("获取项目下的所有关联分组ID数组") + public AjaxResult getGroupIds(@PathVariable("projectId") Long projectId) { + return AjaxResult.success(projectService.selectGroupIdsByProjectId(projectId)); + } + + /** + * 新增项目 + */ + @PreAuthorize("@ss.hasPermi('iot:project:add')") + @Log(title = "项目", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加项目") + public AjaxResult add(@RequestBody Project project) { + return toAjax(projectService.insertProject(project)); + } + + /** + * 修改项目 + */ + @PreAuthorize("@ss.hasPermi('iot:project:edit')") + @Log(title = "项目", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改项目") + public AjaxResult edit(@RequestBody Project project) { + return toAjax(projectService.updateProject(project)); + } + + /** + * 更新项目下的关联分组 + * + * @param projectId 项目ID + * @param groupIds 分组ID数组 + * @return 结果 + */ + @PreAuthorize("@ss.hasPermi('iot:project:edit')") + @Log(title = "项目分组", businessType = BusinessType.UPDATE) + @PutMapping("/updateProjectGroups") + @ApiOperation("更新项目下的关联分组") + public AjaxResult updateProjectGroups(@RequestParam Long projectId, @RequestBody Long[] groupIds) { + return toAjax(projectService.updateProjectGroups(projectId, groupIds)); + } + + /** + * 删除项目 + */ + @PreAuthorize("@ss.hasPermi('iot:project:remove')") + @Log(title = "项目", businessType = BusinessType.DELETE) + @DeleteMapping("/{projectIds}") + @ApiOperation("批量删除项目") + public AjaxResult remove(@PathVariable Long[] projectIds) { + return toAjax(projectService.deleteProjectByIds(projectIds)); + } + + /** + * 根据负责人ID查询项目列表 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/listByUser/{userId}") + @ApiOperation("根据负责人ID查询项目列表") + public AjaxResult listByUser(@PathVariable Long userId) { + List list = projectService.selectProjectsByUserId(userId); + return AjaxResult.success(list); + } + + /** + * 查询项目设备统计 + */ + @PreAuthorize("@ss.hasPermi('iot:project:query')") + @GetMapping("/deviceStats/{projectId}") + @ApiOperation("查询项目设备统计") + public AjaxResult deviceStats(@PathVariable Long projectId) { + Project project = projectService.selectProjectDeviceStats(projectId); + return AjaxResult.success(project); + } + + /** + * 查询用户负责的项目设备统计 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/userProjectDeviceStats/{userId}") + @ApiOperation("查询用户负责的项目设备统计") + public AjaxResult userProjectDeviceStats(@PathVariable Long userId) { + Map stats = projectService.selectProjectDeviceStatsByUserId(userId); + return AjaxResult.success(stats); + } + + /** + * 查询用户负责的项目设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/userProjectDevices/{userId}") + @ApiOperation("查询用户负责的项目设备列表") + public TableDataInfo userProjectDevices(@PathVariable Long userId) { + startPage(); + List list = projectService.selectProjectDevicesByUserId(userId); + return getDataTable(list); + } + + /** + * 查询设备归属用户的设备统计 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/userDeviceStats/{userId}") + @ApiOperation("查询设备归属用户的设备统计") + public AjaxResult userDeviceStats(@PathVariable Long userId) { + Map stats = projectService.selectUserDeviceStats(userId); + return AjaxResult.success(stats); + } + + /** + * 查询设备归属用户的设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:project:list')") + @GetMapping("/userDevices/{userId}") + @ApiOperation("查询设备归属用户的设备列表") + public TableDataInfo userDevices(@PathVariable Long userId) { + startPage(); + List list = projectService.selectUserDevices(userId); + return getDataTable(list); + } +} \ No newline at end of file diff --git a/fastbee-plugs/fastbee-mqtt-client/src/main/java/com/fastbee/mqttclient/PubMqttCallBack.java b/fastbee-plugs/fastbee-mqtt-client/src/main/java/com/fastbee/mqttclient/PubMqttCallBack.java index a2a15f0c..f5656466 100644 --- a/fastbee-plugs/fastbee-mqtt-client/src/main/java/com/fastbee/mqttclient/PubMqttCallBack.java +++ b/fastbee-plugs/fastbee-mqtt-client/src/main/java/com/fastbee/mqttclient/PubMqttCallBack.java @@ -97,12 +97,12 @@ public class PubMqttCallBack implements MqttCallbackExtended { public void connectComplete(boolean reconnect, String serverURI) { log.info("MQTT内部客户端已经连接!"); System.out.print( - "▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌ ▍▌▌▌▌▍ ▌▌▌ ▌▌▌▌ ▌▌▌▌▌▍ ▌▌▌▌\n" + - " ▌▌▌▌▎▌▌▌▌ ▌▌▌ ▌▌▌ ▍▌▌▌▌▌▌▎ ▌▌▌ ▌▌▌▎▌▌ ▌▌▌▌▌▌▌ ▌▌▌▌\n" + - " ▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌ ▍▌▌▌▏▌▌▌▌ ▌▌▌ ▌▌▌ ▌▌▌ ▌▌▌▌▌▌▌▌ ▌▌▌▌\n" + - " ▌▌▌▍ ▌▌▌▍▍▍▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌ ▌▌▌▌▍▍▍▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌▌\n" + - " ▌▌▌▏ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌\n" + - " ▌▌▌▏ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌" + "▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▍▌▌▌▌▍ ▌▌▌ ▌▌▌▌ ▍▌▌▌▌▍ ▌▌▌\n" + + " ▌▌▌▌▎▌▌▌▌ ▌▌▌▎▌▌ ▍▌▌▌▌▌▌▎ ▌▌▌ ▌▌▌▎▌▌ ▍▌▌▌▌▌▌▎ ▌▌▌\n" + + " ▌▌▌▌▌▌ ▌▌▌ ▌▌▌ ▍▌▌▌▏▌▌▌▌ ▌▌▌ ▌▌▌ ▌▌▌ ▍▌▌▌▏▌▌▌▌ ▌▌▌\n" + + " ▌▌▌▍ ▌▌▌▌▍▍▍▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌ ▌▌▌▌▍▍▍▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌▌\n" + + " ▌▌▌▏ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌ ▌▌▌▌▌▌▌▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌▌\n" + + " ▌▌▌▏ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌ ▌▌▌▌▌" ); diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Project.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Project.java new file mode 100644 index 00000000..c95f34d2 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Project.java @@ -0,0 +1,329 @@ +package com.fastbee.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 业务项目对象 iot_project + * + * @author fastbee + * @date 2026-06-28 + */ +@ApiModel(value = "Project", description = "业务项目对象 iot_project") +public class Project extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** + * 项目ID + */ + @ApiModelProperty("项目ID") + private Long projectId; + + /** + * 项目名称 + */ + @ApiModelProperty("项目名称") + @Excel(name = "项目名称") + private String projectName; + + /** + * 项目编码 + */ + @ApiModelProperty("项目编码") + @Excel(name = "项目编码") + private String projectCode; + + /** + * 租户ID + */ + @ApiModelProperty("租户ID") + private Long tenantId; + + /** + * 租户名称 + */ + @ApiModelProperty("租户名称") + private String tenantName; + + /** + * 负责人ID + */ + @ApiModelProperty("负责人ID") + @Excel(name = "负责人ID") + private Long responsibleUserId; + + /** + * 负责人姓名 + */ + @ApiModelProperty("负责人姓名") + @Excel(name = "负责人姓名") + private String responsibleUserName; + + /** + * 项目经度 + */ + @ApiModelProperty("项目经度") + private BigDecimal longitude; + + /** + * 项目纬度 + */ + @ApiModelProperty("项目纬度") + private BigDecimal latitude; + + /** + * 项目地址 + */ + @ApiModelProperty("项目地址") + private String address; + + /** + * 项目状态(1-预警,2-运行中,3-完结) + */ + @ApiModelProperty("项目状态(1-预警,2-运行中,3-完结)") + @Excel(name = "项目状态") + private Integer projectStatus; + + /** + * 开始时间 + */ + @ApiModelProperty("开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "开始时间", dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + /** + * 结束时间 + */ + @ApiModelProperty("结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "结束时间", dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + /** + * 项目描述 + */ + @ApiModelProperty("项目描述") + private String projectDesc; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @ApiModelProperty("删除标志") + private String delFlag; + + /** + * 关联分组ID数组(用于项目-分组关系管理) + */ + @ApiModelProperty("关联分组ID数组") + private Long[] groupIds; + + /** + * 项目关联设备总数 + */ + @ApiModelProperty("项目关联设备总数") + private Integer deviceCount; + + /** + * 项目在线设备数 + */ + @ApiModelProperty("项目在线设备数") + private Integer onlineCount; + + /** + * 项目离线设备数 + */ + @ApiModelProperty("项目离线设备数") + private Integer offlineCount; + + public Long getProjectId() { + return projectId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getProjectCode() { + return projectCode; + } + + public void setProjectCode(String projectCode) { + this.projectCode = projectCode; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + + public Long getResponsibleUserId() { + return responsibleUserId; + } + + public void setResponsibleUserId(Long responsibleUserId) { + this.responsibleUserId = responsibleUserId; + } + + public String getResponsibleUserName() { + return responsibleUserName; + } + + public void setResponsibleUserName(String responsibleUserName) { + this.responsibleUserName = responsibleUserName; + } + + public BigDecimal getLongitude() { + return longitude; + } + + public void setLongitude(BigDecimal longitude) { + this.longitude = longitude; + } + + public BigDecimal getLatitude() { + return latitude; + } + + public void setLatitude(BigDecimal latitude) { + this.latitude = latitude; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Integer getProjectStatus() { + return projectStatus; + } + + public void setProjectStatus(Integer projectStatus) { + this.projectStatus = projectStatus; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public String getProjectDesc() { + return projectDesc; + } + + public void setProjectDesc(String projectDesc) { + this.projectDesc = projectDesc; + } + + public String getDelFlag() { + return delFlag; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public Long[] getGroupIds() { + return groupIds; + } + + public void setGroupIds(Long[] groupIds) { + this.groupIds = groupIds; + } + + public Integer getDeviceCount() { + return deviceCount; + } + + public void setDeviceCount(Integer deviceCount) { + this.deviceCount = deviceCount; + } + + public Integer getOnlineCount() { + return onlineCount; + } + + public void setOnlineCount(Integer onlineCount) { + this.onlineCount = onlineCount; + } + + public Integer getOfflineCount() { + return offlineCount; + } + + public void setOfflineCount(Integer offlineCount) { + this.offlineCount = offlineCount; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("projectId", getProjectId()) + .append("projectName", getProjectName()) + .append("projectCode", getProjectCode()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("responsibleUserId", getResponsibleUserId()) + .append("responsibleUserName", getResponsibleUserName()) + .append("longitude", getLongitude()) + .append("latitude", getLatitude()) + .append("address", getAddress()) + .append("projectStatus", getProjectStatus()) + .append("startTime", getStartTime()) + .append("endTime", getEndTime()) + .append("projectDesc", getProjectDesc()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("groupIds", getGroupIds()) + .append("deviceCount", getDeviceCount()) + .append("onlineCount", getOnlineCount()) + .append("offlineCount", getOfflineCount()) + .toString(); + } +} diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProjectGroup.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProjectGroup.java new file mode 100644 index 00000000..cb55b5c9 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProjectGroup.java @@ -0,0 +1,51 @@ +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 + + '}'; + } +} \ No newline at end of file diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProjectMapper.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProjectMapper.java new file mode 100644 index 00000000..324795e3 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProjectMapper.java @@ -0,0 +1,147 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import java.util.Map; + +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.Project; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +/** + * 项目Mapper接口 + * + * @author fastbee + * @date 2026-06-28 + */ +@Repository +public interface ProjectMapper { + /** + * 查询项目 + * + * @param projectId 项目主键 + * @return 项目 + */ + public Project selectProjectById(Long projectId); + + /** + * 查询项目列表 + * + * @param project 项目 + * @return 项目集合 + */ + public List selectProjectList(Project project); + + /** + * 通过项目ID查询关联的分组ID数组 + * + * @param projectId 项目ID + * @return 分组ID数组 + */ + public Long[] selectGroupIdsByProjectId(Long projectId); + + /** + * 根据负责人ID查询项目列表 + * + * @param userId 用户ID + * @return 项目集合 + */ + public List selectProjectsByUserId(Long userId); + + /** + * 根据租户ID查询项目列表 + * + * @param tenantId 租户ID + * @return 项目集合 + */ + public List selectProjectsByTenantId(Long tenantId); + + /** + * 查询用户负责的项目设备统计 + * + * @param userId 用户ID + * @return 设备统计Map + */ + public Map selectProjectDeviceStatsByUserId(Long userId); + + /** + * 查询用户负责的项目设备列表 + * + * @param userId 用户ID + * @return 设备集合 + */ + public List selectProjectDevicesByUserId(Long userId); + + /** + * 查询设备归属用户的设备统计 + * + * @param userId 用户ID + * @return 设备统计Map + */ + public Map selectUserDeviceStats(Long userId); + + /** + * 查询设备归属用户的设备列表 + * + * @param userId 用户ID + * @return 设备集合 + */ + public List selectUserDevices(Long userId); + + /** + * 新增项目 + * + * @param project 项目 + * @return 结果 + */ + public int insertProject(Project project); + + /** + * 修改项目 + * + * @param project 项目 + * @return 结果 + */ + public int updateProject(Project project); + + /** + * 批量插入项目-分组关系 + * + * @param projectId 项目ID + * @param groupIds 分组ID数组 + * @return 结果 + */ + public int insertProjectGroups(@Param("projectId") Long projectId, @Param("groupIds") Long[] groupIds); + + /** + * 删除项目关联的所有分组关系 + * + * @param projectId 项目ID + * @return 结果 + */ + public int deleteProjectGroupsByProjectId(Long projectId); + + /** + * 批量删除项目 + * + * @param projectIds 需要删除的项目主键集合 + * @return 结果 + */ + public int deleteProjectByIds(Long[] projectIds); + + /** + * 删除项目 + * + * @param projectId 项目主键 + * @return 结果 + */ + public int deleteProjectById(Long projectId); + + /** + * 查询项目设备统计 + * + * @param projectId 项目ID + * @return 项目对象(包含统计数据) + */ + public Project selectProjectDeviceStats(Long projectId); +} \ No newline at end of file diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProjectService.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProjectService.java new file mode 100644 index 00000000..45c9e901 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProjectService.java @@ -0,0 +1,136 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.Project; + +import java.util.List; +import java.util.Map; + +/** + * 项目管理Service接口 + * + * @author fastbee + * @date 2026-06-28 + */ +public interface IProjectService { + /** + * 查询项目 + * + * @param projectId 项目主键 + * @return 项目 + */ + public Project selectProjectById(Long projectId); + + /** + * 查询项目列表 + * + * @param project 项目 + * @return 项目集合 + */ + public List selectProjectList(Project project); + + /** + * 通过项目ID查询关联的分组ID数组 + * + * @param projectId 项目ID + * @return 分组ID数组 + */ + public Long[] selectGroupIdsByProjectId(Long projectId); + + /** + * 根据负责人ID查询项目列表 + * + * @param userId 用户ID + * @return 项目集合 + */ + public List selectProjectsByUserId(Long userId); + + /** + * 根据租户ID查询项目列表 + * + * @param tenantId 租户ID + * @return 项目集合 + */ + public List selectProjectsByTenantId(Long tenantId); + + /** + * 查询用户负责的项目设备统计 + * + * @param userId 用户ID + * @return 设备统计Map(totalDevices, onlineDevices, offlineDevices) + */ + public Map selectProjectDeviceStatsByUserId(Long userId); + + /** + * 查询用户负责的项目设备列表 + * + * @param userId 用户ID + * @return 设备集合 + */ + public List selectProjectDevicesByUserId(Long userId); + + /** + * 查询设备归属用户的设备统计(现有逻辑完善) + * + * @param userId 用户ID + * @return 设备统计Map(totalDevices, onlineDevices, offlineDevices) + */ + public Map selectUserDeviceStats(Long userId); + + /** + * 查询设备归属用户的设备列表(现有逻辑完善) + * + * @param userId 用户ID + * @return 设备集合 + */ + public List selectUserDevices(Long userId); + + /** + * 新增项目 + * + * @param project 项目 + * @return 结果 + */ + public int insertProject(Project project); + + /** + * 修改项目 + * + * @param project 项目 + * @return 结果 + */ + public int updateProject(Project project); + + /** + * 更新项目关联的分组 + * + * @param projectId 项目ID + * @param groupIds 分组ID数组 + * @return 结果 + */ + public int updateProjectGroups(Long projectId, Long[] groupIds); + + /** + * 批量删除项目 + * + * @param projectIds 需要删除的项目主键集合 + * @return 结果 + */ + public int deleteProjectByIds(Long[] projectIds); + + /** + * 删除项目信息 + * + * @param projectId 项目主键 + * @return 结果 + */ + public int deleteProjectById(Long projectId); + + /** + * 查询项目设备统计(包含设备总数、在线数、离线数、预警数) + * + * @param projectId 项目ID + * @return 项目对象(包含统计数据) + */ + public Project selectProjectDeviceStats(Long projectId); +} \ No newline at end of file diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java index 49de5187..f3f5a295 100644 --- a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -1017,7 +1017,7 @@ public class DeviceServiceImpl implements IDeviceService { .forEach(it -> it.setAlertCount(item)); } } - fillShortListLatestReportTime(list); + fillShortListLatestReportTime(list, device.getEntrance()); return list; } @@ -1025,8 +1025,8 @@ public class DeviceServiceImpl implements IDeviceService { * 短时列表:从 Redis 物模型缓存聚合各点位 ts最大值写入 updateTime(方案 B)。 * 网关设备自身往往无属性上报,数据在子设备 SN 的缓存中,需合并子设备的 ts。 */ - private void fillShortListLatestReportTime(List list) { - if (CollectionUtils.isEmpty(list)) { + private void fillShortListLatestReportTime(List list, Integer entrance) { + if (CollectionUtils.isEmpty(list) && !Integer.valueOf(1).equals(entrance)) { return; } for (DeviceShortOutput row : list) { @@ -2187,7 +2187,7 @@ public class DeviceServiceImpl implements IDeviceService { @Override public List listTerminalUser(Device device) { List list = deviceMapper.listTerminalUser(device); - fillShortListLatestReportTime(list); + fillShortListLatestReportTime(list, device.getEntrance()); return list; } diff --git a/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java new file mode 100644 index 00000000..e34f0f37 --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProjectServiceImpl.java @@ -0,0 +1,264 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.Project; +import com.fastbee.iot.mapper.ProjectMapper; +import com.fastbee.iot.service.IProjectService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; +import static com.fastbee.common.utils.SecurityUtils.getDeptId; + +/** + * 项目管理Service业务层处理 + * + * @author fastbee + * @date 2026-06-28 + */ +@Service +public class ProjectServiceImpl implements IProjectService { + @Autowired + private ProjectMapper projectMapper; + + /** + * 查询项目 + * + * @param projectId 项目主键 + * @return 项目 + */ + @Override + public Project selectProjectById(Long projectId) { + return projectMapper.selectProjectById(projectId); + } + + /** + * 查询项目列表 + * + * @param project 项目 + * @return 项目集合 + */ + @Override + public List selectProjectList(Project project) { + Long deptId = getDeptId(); + if (Objects.isNull(deptId)) { + project.setResponsibleUserId(getLoginUser().getUserId()); + } else { + Long deptUserId = getLoginUser().getUser().getDept().getDeptUserId(); + project.setTenantId(deptUserId); + } + return projectMapper.selectProjectList(project); + } + + /** + * 通过项目ID查询关联的分组ID数组 + * + * @param projectId 项目ID + * @return 分组ID数组 + */ + @Override + public Long[] selectGroupIdsByProjectId(Long projectId) { + return projectMapper.selectGroupIdsByProjectId(projectId); + } + + /** + * 根据负责人ID查询项目列表 + * + * @param userId 用户ID + * @return 项目集合 + */ + @Override + public List selectProjectsByUserId(Long userId) { + return projectMapper.selectProjectsByUserId(userId); + } + + /** + * 根据租户ID查询项目列表 + * + * @param tenantId 租户ID + * @return 项目集合 + */ + @Override + public List selectProjectsByTenantId(Long tenantId) { + return projectMapper.selectProjectsByTenantId(tenantId); + } + + /** + * 查询用户负责的项目设备统计 + * + * @param userId 用户ID + * @return 设备统计Map(totalDevices, onlineDevices, offlineDevices) + */ + @Override + public Map selectProjectDeviceStatsByUserId(Long userId) { + return projectMapper.selectProjectDeviceStatsByUserId(userId); + } + + /** + * 查询用户负责的项目设备列表 + * + * @param userId 用户ID + * @return 设备集合 + */ + @Override + public List selectProjectDevicesByUserId(Long userId) { + return projectMapper.selectProjectDevicesByUserId(userId); + } + + /** + * 查询设备归属用户的设备统计(现有逻辑完善) + * + * @param userId 用户ID + * @return 设备统计Map(totalDevices, onlineDevices, offlineDevices) + */ + @Override + public Map selectUserDeviceStats(Long userId) { + return projectMapper.selectUserDeviceStats(userId); + } + + /** + * 查询设备归属用户的设备列表(现有逻辑完善) + * + * @param userId 用户ID + * @return 设备集合 + */ + @Override + public List selectUserDevices(Long userId) { + return projectMapper.selectUserDevices(userId); + } + + /** + * 新增项目 + *

+ * 前端传入了 responsibleUserId 则尊重前端选择,未传则自动填充当前用户。 + * + * @param project 项目 + * @return 结果 + */ + @Override + public int insertProject(Project project) { + Long deptId = getDeptId(); + if (Objects.isNull(deptId)) { + // 终端用户:未选负责人时默认当前用户 + if (project.getResponsibleUserId() == null) { + SysUser user = getLoginUser().getUser(); + project.setResponsibleUserId(user.getUserId()); + project.setResponsibleUserName(user.getUserName()); + } + } else { + // 租户用户:未选负责人时默认当前租户管理员 + if (project.getResponsibleUserId() == null) { + Long deptUserId = getLoginUser().getUser().getDept().getDeptUserId(); + String deptUserName = getLoginUser().getUser().getDept().getDeptUserName(); + project.setResponsibleUserId(deptUserId); + project.setResponsibleUserName(deptUserName); + } + if (project.getTenantId() == null) { + Long deptUserId = getLoginUser().getUser().getDept().getDeptUserId(); + String deptUserName = getLoginUser().getUser().getDept().getDeptUserName(); + project.setTenantId(deptUserId); + project.setTenantName(deptUserName); + } + } + project.setCreateTime(DateUtils.getNowDate()); + project.setCreateBy(getLoginUser().getUsername()); + project.setDelFlag("0"); + + int result = projectMapper.insertProject(project); + + // 插入项目-分组关系 + if (project.getGroupIds() != null && project.getGroupIds().length > 0) { + projectMapper.insertProjectGroups(project.getProjectId(), project.getGroupIds()); + } + + return result; + } + + /** + * 修改项目(含分组关联更新) + * + * @param project 项目 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateProject(Project project) { + project.setUpdateTime(DateUtils.getNowDate()); + project.setUpdateBy(getLoginUser().getUsername()); + int rows = projectMapper.updateProject(project); + + // 更新项目-分组关联关系(先删后增) + if (project.getProjectId() != null) { + projectMapper.deleteProjectGroupsByProjectId(project.getProjectId()); + if (project.getGroupIds() != null && project.getGroupIds().length > 0) { + projectMapper.insertProjectGroups(project.getProjectId(), project.getGroupIds()); + } + } + + return rows; + } + + /** + * 更新项目关联的分组(事务管理) + * + * @param projectId 项目ID + * @param groupIds 分组ID数组 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateProjectGroups(Long projectId, Long[] groupIds) { + projectMapper.deleteProjectGroupsByProjectId(projectId); + if (groupIds != null && groupIds.length > 0) { + return projectMapper.insertProjectGroups(projectId, groupIds); + } + return 1; + } + + /** + * 批量删除项目(同时清理关联分组) + * + * @param projectIds 需要删除的项目主键集合 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteProjectByIds(Long[] projectIds) { + // 先清理关联分组关系 + for (Long projectId : projectIds) { + projectMapper.deleteProjectGroupsByProjectId(projectId); + } + return projectMapper.deleteProjectByIds(projectIds); + } + + /** + * 删除项目信息(同时清理关联分组) + * + * @param projectId 项目主键 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteProjectById(Long projectId) { + projectMapper.deleteProjectGroupsByProjectId(projectId); + return projectMapper.deleteProjectById(projectId); + } + + /** + * 查询项目设备统计(包含设备总数、在线数、离线数、预警数) + * + * @param projectId 项目ID + * @return 项目对象(包含统计数据) + */ + @Override + public Project selectProjectDeviceStats(Long projectId) { + return projectMapper.selectProjectDeviceStats(projectId); + } +} diff --git a/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml new file mode 100644 index 00000000..cb79259b --- /dev/null +++ b/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProjectMapper.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select project_id, project_name, project_code, tenant_id, tenant_name, + responsible_user_id, responsible_user_name, longitude, latitude, address, + project_status, start_time, end_time, project_desc, del_flag, + create_by, create_time, update_by, update_time, remark + from iot_project + + + + + + + + + + + + + + + + + + + + + + + + + + insert into iot_project + + project_name, + project_code, + tenant_id, + tenant_name, + responsible_user_id, + responsible_user_name, + longitude, + latitude, + address, + project_status, + start_time, + end_time, + project_desc, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{projectName}, + #{projectCode}, + #{tenantId}, + #{tenantName}, + #{responsibleUserId}, + #{responsibleUserName}, + #{longitude}, + #{latitude}, + #{address}, + #{projectStatus}, + #{startTime}, + #{endTime}, + #{projectDesc}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + + insert into iot_project_group (project_id,group_id) + values + + (#{projectId},#{groupId}) + + + + + update iot_project + + project_name = #{projectName}, + project_code = #{projectCode}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + responsible_user_id = #{responsibleUserId}, + responsible_user_name = #{responsibleUserName}, + longitude = #{longitude}, + latitude = #{latitude}, + address = #{address}, + project_status = #{projectStatus}, + start_time = #{startTime}, + end_time = #{endTime}, + project_desc = #{projectDesc}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where project_id = #{projectId} + + + + delete from iot_project_group where project_id=#{projectId} + + + + update iot_project set del_flag = '2' where project_id in + + #{projectId} + + + + + update iot_project set del_flag = '2' where project_id = #{projectId} + + + + + + diff --git a/sql/iot_project_tables.sql b/sql/iot_project_tables.sql new file mode 100644 index 00000000..1922950f --- /dev/null +++ b/sql/iot_project_tables.sql @@ -0,0 +1,67 @@ +-- ======================================== +-- FastBee项目管理数据库表创建脚本 +-- 创建日期: 2026-06-28 +-- 描述: 创建项目表和项目-分组关系表,实现User→Project→Group→Device层级 +-- ======================================== + +-- 1. 创建项目表(iot_project) +-- 实现 User → Project 层级(通过responsible_user_id字段) +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 2 COMMENT '项目状态(1-预警,2-运行中,3-完结)', + `start_time` DATETIME DEFAULT NULL COMMENT '开始时间', + `end_time` DATETIME DEFAULT NULL COMMENT '结束时间', + `project_desc` VARCHAR(500) 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_user` (`tenant_id`, `responsible_user_id`), + KEY `idx_project_status` (`project_status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='业务项目表'; + +-- 2. 创建项目-分组关系表(iot_project_group) +-- 实现 Project → Device Group 层级 +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='项目-设备分组关系表'; + +-- ======================================== +-- 层级关系说明: +-- User (responsible_user_id) → Project (project_id) → Project_Group (group_id) → Device_Group (device_id) → Device +-- +-- 与现有逻辑兼容的关键点: +-- 1. iot_group表保持不变(现有分组管理逻辑不受影响) +-- 2. iot_device_user表保持不变(设备归属关系独立) +-- 3. 项目-分组关系是可选的(未关联项目的分组保持现有功能) +-- +-- 用户查询设备的两种路径: +-- 路径1:查询用户负责的项目设备(新增功能) +-- SELECT DISTINCT d.* FROM iot_device d +-- INNER JOIN iot_device_group dg ON d.device_id = dg.device_id +-- INNER JOIN iot_project_group pg ON dg.group_id = pg.group_id +-- INNER JOIN iot_project p ON pg.project_id = p.project_id +-- WHERE p.responsible_user_id = #{userId} AND p.del_flag='0' AND d.del_flag='0' +-- +-- 路径2:查询设备归属用户的设备(现有逻辑) +-- SELECT d.* FROM iot_device d +-- INNER JOIN iot_device_user du ON d.device_id = du.device_id +-- WHERE du.user_id = #{userId} AND d.del_flag='0' +-- ======================================== \ No newline at end of file diff --git a/sql/project-menu.sql b/sql/project-menu.sql new file mode 100644 index 00000000..acba6df5 --- /dev/null +++ b/sql/project-menu.sql @@ -0,0 +1,44 @@ +-- ======================================== +-- FastBee项目管理菜单SQL脚本 +-- 创建日期: 2026-06-28 +-- 描述: 插入项目管理相关菜单和权限配置到sys_menu表 +-- ======================================== + +-- 项目管理菜单 +-- 菜单ID范围: 3600-3605 +-- 父菜单: 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, ''); + +-- 子菜单:项目导出 +INSERT INTO `sys_menu` VALUES(3605, '项目导出', 3600, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:project:export', '#', 'admin', NOW(), '', NULL, ''); + +-- ======================================== +-- 菜单说明: +-- menu_id: 菜单ID(3600为主菜单,3601-3605为按钮权限) +-- menu_name: 菜单名称 +-- parent_id: 父菜单ID(2000为设备管理) +-- order_num: 显示顺序 +-- path: 菜单路径(前端路由路径) +-- component: 组件路径(前端Vue组件路径) +-- menu_type: 菜单类型(C=目录菜单,F=按钮权限) +-- visible: 是否显示(0=显示) +-- status: 菜单状态(0=正常) +-- perms: 权限标识(iot:project:*) +-- icon: 菜单图标 +-- create_by: 创建者 +-- create_time: 创建时间 +-- ======================================== \ No newline at end of file diff --git a/yack-web/src/api/iot/project.js b/yack-web/src/api/iot/project.js new file mode 100644 index 00000000..48421f44 --- /dev/null +++ b/yack-web/src/api/iot/project.js @@ -0,0 +1,112 @@ +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' + }) +} + +// 查询项目下的关联分组ID数组 +export function getGroupIds(projectId) { + return request({ + url: '/iot/project/getGroupIds/' + 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' + }) +} + +// 根据负责人ID查询项目列表 +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' + }) +} + +// 查询用户负责的项目设备统计 +export function getUserProjectDeviceStats(userId) { + return request({ + url: '/iot/project/userProjectDeviceStats/' + userId, + method: 'get' + }) +} + +// 查询用户负责的项目设备列表 +export function getUserProjectDevices(userId, query) { + return request({ + url: '/iot/project/userProjectDevices/' + userId, + method: 'get', + params: query + }) +} + +// 查询设备归属用户的设备统计 +export function getUserDeviceStats(userId) { + return request({ + url: '/iot/project/userDeviceStats/' + userId, + method: 'get' + }) +} + +// 查询设备归属用户的设备列表 +export function getUserDevices(userId, query) { + return request({ + url: '/iot/project/userDevices/' + userId, + method: 'get', + params: query + }) +} diff --git a/yack-web/src/assets/icons/svg/data analysis.svg b/yack-web/src/assets/icons/svg/data analysis.svg new file mode 100644 index 00000000..b4e75757 --- /dev/null +++ b/yack-web/src/assets/icons/svg/data analysis.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/device-binding.svg b/yack-web/src/assets/icons/svg/device-binding.svg new file mode 100644 index 00000000..9d311ad7 --- /dev/null +++ b/yack-web/src/assets/icons/svg/device-binding.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/flow-orchestration.svg b/yack-web/src/assets/icons/svg/flow-orchestration.svg new file mode 100644 index 00000000..3217fc50 --- /dev/null +++ b/yack-web/src/assets/icons/svg/flow-orchestration.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/online.svg b/yack-web/src/assets/icons/svg/online.svg new file mode 100644 index 00000000..27cca3ed --- /dev/null +++ b/yack-web/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/platform-integration.svg b/yack-web/src/assets/icons/svg/platform-integration.svg new file mode 100644 index 00000000..6d552bd0 --- /dev/null +++ b/yack-web/src/assets/icons/svg/platform-integration.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/project.svg b/yack-web/src/assets/icons/svg/project.svg new file mode 100644 index 00000000..6e4fd4db --- /dev/null +++ b/yack-web/src/assets/icons/svg/project.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/project1.svg b/yack-web/src/assets/icons/svg/project1.svg new file mode 100644 index 00000000..003352c2 --- /dev/null +++ b/yack-web/src/assets/icons/svg/project1.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/assets/icons/svg/project2.svg b/yack-web/src/assets/icons/svg/project2.svg new file mode 100644 index 00000000..fb518bc8 --- /dev/null +++ b/yack-web/src/assets/icons/svg/project2.svg @@ -0,0 +1 @@ + diff --git a/yack-web/src/permission.js b/yack-web/src/permission.js index 0344ff6b..cb5131ac 100644 --- a/yack-web/src/permission.js +++ b/yack-web/src/permission.js @@ -17,7 +17,7 @@ router.beforeEach((to, from, next) => { to.meta.title && store.dispatch('settings/setTitle', to.meta.title); /* has token*/ if (to.path === '/login') { - next({ path: '/' }); + next({ path: '/bigscreen' }); NProgress.done(); } else { if (store.getters.roles.length === 0) { @@ -30,7 +30,7 @@ router.beforeEach((to, from, next) => { store.dispatch('GenerateRoutes').then((accessRoutes) => { // 根据roles权限生成可访问的路由表 router.addRoutes(accessRoutes); // 动态添加可访问路由表 - next({ ...to, replace: true }); // hack方法 确保addRoutes已完成 + next({ path: '/bigscreen', replace: true }); // 登录后跳转到大数据看板 }); }) .catch((err) => { diff --git a/yack-web/src/router/index.js b/yack-web/src/router/index.js index d2df932b..5a4ab484 100644 --- a/yack-web/src/router/index.js +++ b/yack-web/src/router/index.js @@ -42,7 +42,8 @@ export const constantRoutes = [ ], }, { - path: '/bigScreen', + path: '/bigscreen', + alias: ['/bigScreen', '/largeScreen', '/mapView', '/analysis'], component: () => import('@/views/bigScreen/home'), hidden: true, meta: { bigScreen: true }, @@ -70,7 +71,7 @@ export const constantRoutes = [ { path: '', component: Layout, - redirect: 'index', + redirect: '/bigscreen', children: [ { path: 'index', diff --git a/yack-web/src/utils/tdtMap.js b/yack-web/src/utils/tdtMap.js new file mode 100644 index 00000000..b8abaa51 --- /dev/null +++ b/yack-web/src/utils/tdtMap.js @@ -0,0 +1,41 @@ +const DEFAULT_TIANDITU_AK = process.env.VUE_APP_TIANDITU_AK || 'c9ef88370bacf8f51662c01ad80a580c'; +const DEFAULT_TIANDITU_TILE_KEY = process.env.VUE_APP_TIANDITU_TILE_KEY || 'b920e6076a06aa279c974f5f76cf746e'; + +let tiandituLoading = null; + +export function loadTiandituMap(tk = DEFAULT_TIANDITU_AK) { + if (window.T) { + return Promise.resolve(window.T); + } + if (tiandituLoading) { + return tiandituLoading; + } + + tiandituLoading = new Promise((resolve, reject) => { + const existingScript = document.getElementById('tianditu-js-api'); + if (existingScript) { + existingScript.addEventListener('load', () => resolve(window.T)); + existingScript.addEventListener('error', reject); + return; + } + + const script = document.createElement('script'); + script.id = 'tianditu-js-api'; + script.type = 'text/javascript'; + const protocol = window.location.protocol === 'http:' ? 'http:' : 'https:'; + script.src = `${protocol}//api.tianditu.gov.cn/api?v=4.0&tk=${tk}`; + script.onload = () => { + if (window.T) { + resolve(window.T); + } else { + reject(new Error('天地图脚本已加载,但未找到 T 对象')); + } + }; + script.onerror = reject; + document.head.appendChild(script); + }); + + return tiandituLoading; +} + +export { DEFAULT_TIANDITU_AK, DEFAULT_TIANDITU_TILE_KEY }; diff --git a/yack-web/src/views/bigScreen/home.scss b/yack-web/src/views/bigScreen/home.scss index 9d19332a..2a41e3d1 100644 --- a/yack-web/src/views/bigScreen/home.scss +++ b/yack-web/src/views/bigScreen/home.scss @@ -1,5 +1,5 @@ #index { - color: #d3d6dd; + color: #d9f2ff; width: 1920px; height: 1080px; overflow: hidden; @@ -8,117 +8,917 @@ position: absolute; top: 50%; left: 50%; - transform: translate(-50%, -50%); - transform-origin: left top; + transform: translate(-50%, -50%) scale(1); + transform-origin: center center; } - - - .bg { - width: 100%; - height: 100%; - padding: 16px 16px 10px 16px; - box-sizing: border-box; - // 深蓝色背景 - // background-image: radial-gradient(closest-side,#264194, #12214c); - background-color: #0e2e87; - background-size: cover; - background-position: center center; - } - - .host-body { - height: 100%; - - .title_wrap { - height: 60px; - background-image: url("../../assets/bigScreen/img/top.png"); - background-size: cover; - background-position: center center; - position: relative; - margin-bottom: 4px; - - .guang { - position: absolute; - bottom: -26px; - background-image: url("../../assets/bigScreen/img/guang.png"); - background-position: 80px center; - width: 100%; - height: 56px; - } - - .zuojuxing, - .youjuxing { - position: absolute; - top: -2px; - width: 140px; - height: 6px; - background-image: url("../../assets/bigScreen/img/headers/juxing1.png"); - } - - .zuojuxing { - - left: 11%; - } - - .youjuxing { - right: 11%; - transform: rotate(180deg); - } - - .timers { - position: absolute; - right: 0; - top: 30px; - font-size: 18px; - display: flex; - align-items: center; - - .blq-icon-shezhi02 { - cursor: pointer; - } - } - } - - .title { - position: relative; - // width: 500px; - text-align: center; - background-size: cover; - color: transparent; - height: 60px; - line-height: 46px; - - .title-text { - font-size: 38px; - font-weight: 900; - letter-spacing: 6px; - width: 100%; - background: linear-gradient(92deg, #0072FF 0%, #00EAFF 48.8525390625%, #01AAFF 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - } - } - } - - } -.index_home { +.cockpit-page { width: 100vw; height: 100vh; - background-color: #03050C; - .pagetab { - position: absolute; - top: -35px; - display: flex; + background: #020918; +} - .item { - width: 130px; - height: 36px; - color: #00FBF8; - text-indent: 5px; - line-height: 36px; - font-size: 16px; - } +.cockpit-page--map-view .cockpit-bg { + padding: 0; + background: #05070b; +} + +.cockpit-page--map-view .cockpit-header--new { + height: 54px; + margin-bottom: 0; + background: rgba(31, 36, 42, .96); + border-bottom: 1px solid rgba(0, 0, 0, .6); +} + +.cockpit-page--map-view .header-title-console { + top: 0; + width: 360px; + height: 54px; + background: transparent; + clip-path: none; + border: 0; + box-shadow: none; +} + +.cockpit-page--map-view .header-title-console::before, +.cockpit-page--map-view .header-title-console::after { + display: none; +} + +.cockpit-page--map-view .header-title-console__text { + color: #d7dde5; + font-size: 18px; + letter-spacing: 1px; + text-shadow: none; +} + +.cockpit-page--map-view .header-wing { + top: 0; + width: 180px; + height: 54px; + padding: 0; + color: #bfc7d0; + font-size: 18px; + background: transparent; + clip-path: none; + border: 0; + box-shadow: none; +} + +.cockpit-page--map-view .header-wing i { + color: #1ba6e8; + font-size: 22px; +} + +.cockpit-page--map-view .header-wing--left { + left: 0; +} + +.cockpit-page--map-view .header-wing--right { + left: 184px; + right: auto; +} + +.cockpit-page--map-view .admin-stealth { + top: 10px; + right: 18px; +} + +.cockpit-page--map-view .cockpit-main { + height: calc(100% - 54px); +} + +.cockpit-bg { + position: relative; + width: 100%; + height: 100%; + padding: 18px 22px 22px; + box-sizing: border-box; + background: + radial-gradient(circle at 50% 12%, rgba(45, 120, 255, .42), transparent 30%), + radial-gradient(circle at 12% 88%, rgba(0, 242, 255, .14), transparent 28%), + linear-gradient(135deg, #030814 0%, #071a46 42%, #030918 100%); + overflow: hidden; + + &::before { + position: absolute; + inset: 0; + content: ''; + pointer-events: none; + background-image: + linear-gradient(rgba(31, 175, 255, .05) 1px, transparent 1px), + linear-gradient(90deg, rgba(31, 175, 255, .05) 1px, transparent 1px); + background-size: 42px 42px; + mask-image: radial-gradient(circle at center, rgba(0, 0, 0, .9), transparent 76%); + } +} + +.cockpit-shell { + position: relative; + z-index: 1; + height: 100%; +} + +.cockpit-header--new { + position: relative; + height: 76px; + margin-bottom: 8px; +} + +.header-title-console { + position: absolute; + top: -10px; + left: 50%; + z-index: 4; + width: 580px; + height: 72px; + display: flex; + align-items: center; + justify-content: center; + color: #f7fbff; + cursor: pointer; + background: + linear-gradient(180deg, rgba(28, 75, 202, .98), rgba(7, 21, 83, .98) 70%, rgba(5, 14, 56, .98)), + radial-gradient(circle at 50% -10%, rgba(67, 225, 255, .28), transparent 58%); + clip-path: polygon(8% 0, 92% 0, 100% 82%, 86% 100%, 14% 100%, 0 82%); + border-bottom: 2px solid rgba(36, 198, 255, .82); + box-shadow: 0 16px 34px rgba(0, 20, 90, .56), inset 0 -12px 22px rgba(0, 7, 52, .34); + transform: translateX(-50%); + + &::before, + &::after { + position: absolute; + content: ''; + } + + &::before { + left: 34px; + right: 34px; + bottom: 8px; + height: 2px; + background: linear-gradient(90deg, transparent, rgba(94, 232, 255, .9), transparent); + } + + &::after { + inset: 8px 40px auto; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(125, 222, 255, .7), transparent); + } +} + +.header-title-console__text { + font-size: 34px; + font-weight: 900; + letter-spacing: 4px; + line-height: 1; + text-shadow: 0 0 18px rgba(87, 220, 255, .68); +} + +.header-wing { + position: absolute; + top: 0; + z-index: 3; + width: 500px; + height: 60px; + border: 0; + color: #eef8ff; + cursor: pointer; + font-size: 27px; + font-weight: 800; + letter-spacing: 2px; + background: + linear-gradient(180deg, rgba(28, 68, 178, .92), rgba(9, 28, 93, .88)), + linear-gradient(90deg, transparent, rgba(54, 187, 255, .16), transparent); + border-bottom: 1px solid rgba(69, 204, 255, .5); + box-shadow: inset 0 -12px 20px rgba(5, 14, 66, .34); + transition: color .18s ease, filter .18s ease, transform .18s ease; +} + +.header-wing i { + margin-right: 12px; + color: #2ecbff; + font-size: 32px; + vertical-align: -3px; +} + +.header-wing:hover, +.header-wing.active { + color: #fff; + filter: brightness(1.12); + transform: translateY(-1px); +} + +.header-wing--left { + left: 182px; + padding-right: 132px; + clip-path: polygon(0 0, 88% 0, 100% 100%, 8% 100%); +} + +.header-wing--right { + right: 182px; + padding-left: 132px; + clip-path: polygon(12% 0, 100% 0, 92% 100%, 0 100%); +} + +.admin-stealth { + position: absolute; + top: 12px; + right: 22px; + z-index: 8; + width: 34px; + height: 34px; + border: 1px solid rgba(99, 184, 255, .26); + color: rgba(161, 212, 255, .46); + cursor: pointer; + background: rgba(4, 16, 56, .3); + transition: color .18s ease, border-color .18s ease, background .18s ease; +} + +.admin-stealth:hover { + color: #8fe8ff; + border-color: rgba(104, 224, 255, .64); + background: rgba(7, 35, 95, .72); +} + +.map-time-overlay { + position: absolute; + top: 112px; + left: 50%; + z-index: 12; + display: flex; + align-items: baseline; + justify-content: center; + gap: 22px; + min-width: 330px; + height: 30px; + color: #cddfff; + pointer-events: none; + transform: translateX(-50%); +} + +.map-time-overlay span { + font-size: 19px; + font-weight: 700; + letter-spacing: 1px; +} + +.map-time-overlay span:nth-child(2) { + color: #a6bde8; +} + +.map-time-overlay strong { + color: #eff7ff; + font-size: 20px; + font-weight: 900; + letter-spacing: 2px; + text-shadow: 0 0 12px rgba(86, 214, 255, .5); +} + +.map-time-overlay--view { + top: 88px; +} + +.cockpit-main { + position: relative; + z-index: 1; + height: calc(100% - 84px); +} + +.cockpit-main--map { + display: grid; + grid-template-columns: 420px 1fr 420px; + gap: 14px; +} + +.panel-column { + display: grid; + grid-template-rows: 282px 292px 1fr; + gap: 14px; + min-height: 0; +} + +.panel-card { + position: relative; + min-height: 0; + padding: 16px; + box-sizing: border-box; + overflow: hidden; + border: 1px solid rgba(46, 152, 255, .46); + background: + linear-gradient(180deg, rgba(11, 42, 117, .82), rgba(5, 18, 58, .72)), + radial-gradient(circle at 100% 0, rgba(0, 209, 255, .2), transparent 38%); + box-shadow: + inset 0 0 28px rgba(28, 142, 255, .18), + 0 10px 34px rgba(0, 0, 0, .28); + + &::before, + &::after { + position: absolute; + width: 18px; + height: 18px; + content: ''; + border-color: #26d8ff; + border-style: solid; + pointer-events: none; + } + + &::before { + top: -1px; + left: -1px; + border-width: 2px 0 0 2px; + } + + &::after { + right: -1px; + bottom: -1px; + border-width: 0 2px 2px 0; + } +} + +.panel-title { + position: relative; + z-index: 1; + display: flex; + align-items: center; + margin-bottom: 12px; + color: #e5f8ff; + font-size: 17px; + font-weight: 800; + letter-spacing: 1px; + + &::before { + width: 4px; + height: 17px; + margin-right: 8px; + content: ''; + border-radius: 6px; + background: linear-gradient(180deg, #65f5ff, #1477ff); + box-shadow: 0 0 10px rgba(63, 231, 255, .8); + } +} + +.panel-title--between { + justify-content: space-between; +} + +.panel-subtitle { + margin-left: auto; + color: #80b5de; + font-size: 12px; + font-weight: 500; +} + +.metrics-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; +} + +.metric-card { + height: 88px; + padding: 12px 8px; + box-sizing: border-box; + border: 1px solid rgba(75, 188, 255, .2); + background: linear-gradient(180deg, rgba(18, 74, 157, .62), rgba(6, 27, 74, .78)); +} + +.metric-label { + color: #9bc7ef; + font-size: 13px; +} + +.metric-value { + margin-top: 10px; + color: #5df2ff; + font-size: 28px; + font-weight: 900; + line-height: 1; + text-shadow: 0 0 12px rgba(69, 226, 255, .55); +} + +.metric-card--warning .metric-value, +.metric-card--alarm .metric-value { + color: #ff607c; + text-shadow: 0 0 12px rgba(255, 69, 113, .45); +} + +.metric-card--running .metric-value, +.metric-card--online .metric-value { + color: #20f2a4; + text-shadow: 0 0 12px rgba(32, 242, 164, .45); +} + +.user-device-summary { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-bottom: 12px; + + > div { + height: 62px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border: 1px solid rgba(42, 181, 255, .26); + background: rgba(8, 35, 92, .54); + } +} + +.summary-number { + color: #32e6ff; + font-size: 26px; + font-weight: 900; +} + +.summary-number--warn { + color: #ff607c; +} + +.summary-label { + margin-top: 3px; + color: #9bc7ef; + font-size: 12px; +} + +.device-list, +.project-list, +.model-list { + height: calc(100% - 38px); + overflow: auto; +} + +.device-row, +.project-row, +.model-row { + display: flex; + align-items: center; + min-height: 42px; + margin-bottom: 8px; + padding: 8px 10px; + box-sizing: border-box; + border: 1px solid rgba(61, 163, 255, .18); + background: rgba(4, 18, 53, .42); + cursor: pointer; +} + +.device-row:hover, +.project-row:hover { + border-color: rgba(72, 218, 255, .62); + background: rgba(14, 59, 126, .62); +} + +.device-dot { + width: 9px; + height: 9px; + margin-right: 10px; + border-radius: 50%; + background: #7385a4; + box-shadow: 0 0 8px rgba(115, 133, 164, .8); + + &.online { + background: #24f0a2; + box-shadow: 0 0 10px rgba(36, 240, 162, .85); + } +} + +.device-info { + min-width: 0; + flex: 1; +} + +.device-name, +.alarm-name { + overflow: hidden; + color: #edf9ff; + font-size: 13px; + white-space: nowrap; + text-overflow: ellipsis; +} + +.device-meta, +.alarm-meta, +.project-meta, +.model-row small { + overflow: hidden; + color: #80a7ca; + font-size: 12px; + white-space: nowrap; + text-overflow: ellipsis; +} + +.device-status { + color: #91c8ee; + font-size: 12px; +} + +.alarm-list { + height: calc(100% - 38px); + overflow: hidden; +} + +.alarm-row { + display: flex; + align-items: center; + min-height: 54px; + margin-bottom: 10px; + border-bottom: 1px dashed rgba(67, 221, 255, .34); +} + +.alarm-index { + width: 32px; + color: #23d7ff; + font-size: 18px; + font-weight: 900; +} + +.alarm-content { + min-width: 0; + flex: 1; +} + +.map-stage { + position: relative; + min-width: 0; + min-height: 0; + border: 1px solid rgba(44, 158, 255, .4); + background: rgba(3, 13, 37, .62); + box-shadow: inset 0 0 48px rgba(28, 128, 255, .16); + overflow: hidden; +} + +.cockpit-main--map-view { + display: block; +} + +.map-view-stage { + position: relative; + height: 100%; + border: 1px solid rgba(44, 158, 255, .4); + background: #05070b; + box-shadow: inset 0 0 58px rgba(0, 0, 0, .5); + overflow: hidden; +} + +.satellite-map { + position: absolute; + inset: 0; + z-index: 1; + background: #05070b; +} + +.satellite-map .leaflet-tile { + filter: saturate(1.08) contrast(1.04); +} + +.satellite-map .leaflet-control-container { + display: none; +} + +.map-view-title { + position: absolute; + top: 16px; + left: 18px; + z-index: 12; + padding: 8px 12px; + border: 1px solid rgba(10, 166, 255, .25); + background: rgba(20, 26, 33, .72); +} + +.map-toolbar { + position: absolute; + top: 16px; + left: 18px; + right: 18px; + z-index: 10; + display: flex; + align-items: flex-start; + justify-content: space-between; + pointer-events: none; +} + +.map-title { + color: #edf9ff; + font-size: 22px; + font-weight: 900; + letter-spacing: 1px; +} + +.map-subtitle { + margin-top: 5px; + color: #93bfe2; + font-size: 12px; +} + +.map-legends { + display: flex; + gap: 14px; + padding: 8px 12px; + color: #b9dcf5; + font-size: 12px; + border: 1px solid rgba(62, 179, 255, .3); + background: rgba(3, 17, 50, .72); +} + +.map-legends--view { + position: absolute; + right: 18px; + bottom: 58px; + z-index: 12; + flex-direction: column; + gap: 8px; + border-color: rgba(255, 255, 255, .16); + background: rgba(24, 28, 33, .82); +} + +.map-tools { + position: absolute; + z-index: 14; + display: flex; + gap: 8px; +} + +.map-tools--left { + top: 14px; + left: 14px; + transform: translateY(72px); +} + +.map-tools button { + width: 40px; + height: 40px; + border: 1px solid rgba(255, 255, 255, .18); + color: #37b9ff; + cursor: pointer; + background: rgba(28, 35, 43, .88); + box-shadow: 0 4px 14px rgba(0, 0, 0, .28); +} + +.map-tools button:hover { + color: #fff; + border-color: rgba(46, 190, 255, .7); +} + +.legend-dot { + display: inline-block; + width: 9px; + height: 9px; + margin-right: 6px; + border-radius: 50%; +} + +.legend-dot--warning { + background: #ff607c; +} + +.legend-dot--running { + background: #1df2a2; +} + +.legend-dot--completed { + background: #9aa8c7; +} + +.china-map-wrap { + position: absolute; + inset: 0; + background: + radial-gradient(circle at 50% 52%, rgba(24, 155, 255, .28), transparent 40%), + radial-gradient(circle at 50% 50%, rgba(5, 31, 80, .7), transparent 62%), + linear-gradient(180deg, rgba(3, 15, 44, .78), rgba(2, 9, 28, .96)); +} + +.china-map { + position: absolute; + inset: 18px 0 0; + z-index: 1; +} + +.map-empty { + position: absolute; + right: 24px; + bottom: 24px; + z-index: 1000; + padding: 9px 14px; + color: #9ec9e8; + font-size: 12px; + border: 1px solid rgba(70, 195, 255, .28); + background: rgba(3, 18, 56, .72); +} + +.selected-project { + position: absolute; + left: 22px; + bottom: 22px; + z-index: 10; + min-width: 280px; + padding: 14px 16px; + border: 1px solid rgba(70, 195, 255, .34); + background: rgba(3, 18, 56, .78); + box-shadow: 0 12px 30px rgba(0, 0, 0, .34); +} + +.selected-project--view { + min-width: 330px; + background: rgba(24, 28, 33, .84); + border-color: rgba(255, 255, 255, .16); +} + +.satellite-marker { + position: relative; + display: flex; + align-items: center; + height: 34px; + color: #eef6ff; + font-size: 13px; + font-weight: 700; + text-shadow: 0 1px 4px rgba(0, 0, 0, .9); + white-space: nowrap; +} + +.satellite-marker span { + position: relative; + width: 18px; + height: 18px; + margin-right: 7px; + border: 2px solid rgba(196, 255, 196, .88); + border-radius: 50%; + background: #25e231; + box-shadow: 0 0 0 5px rgba(37, 226, 49, .26), 0 0 18px rgba(37, 226, 49, .85); +} + +.satellite-marker span::after { + position: absolute; + inset: -9px; + content: ''; + border: 2px solid rgba(37, 226, 49, .36); + border-radius: 50%; +} + +.satellite-marker--warning span { + background: #ff5e75; + box-shadow: 0 0 0 5px rgba(255, 94, 117, .25), 0 0 18px rgba(255, 94, 117, .78); +} + +.satellite-marker--completed span { + background: #a5aeb8; + box-shadow: 0 0 0 5px rgba(165, 174, 184, .22), 0 0 18px rgba(165, 174, 184, .7); +} + +.satellite-marker b { + max-width: 230px; + overflow: hidden; + text-overflow: ellipsis; +} + +.selected-project__title { + color: #e9f9ff; + font-size: 16px; + font-weight: 900; +} + +.selected-project__body { + display: flex; + gap: 16px; + margin-top: 8px; + color: #9ec9e8; + font-size: 12px; +} + +.chart-box { + height: calc(100% - 38px); +} + +.project-row { + align-items: stretch; + flex-direction: column; + cursor: pointer; +} + +.project-main, +.project-meta { + display: flex; + justify-content: space-between; +} + +.project-main { + color: #ecf9ff; + font-size: 14px; +} + +.project-meta { + margin-top: 7px; +} + +.project-status { + width: 9px; + height: 9px; + margin: 5px 8px 0 0; + border-radius: 50%; + background: #20f2a4; +} + +.project-status--warning { + background: #ff607c; +} + +.project-status--completed { + background: #9aa8c7; +} + +.empty-tip { + display: flex; + align-items: center; + justify-content: center; + height: 80px; + color: #7fa8c8; + font-size: 13px; +} + +.empty-tip--large { + height: 100%; +} + +.cockpit-main--analysis { + display: grid; + grid-template-columns: 360px 1fr; + gap: 16px; +} + +.analysis-sidebar, +.analysis-content { + display: grid; + gap: 16px; + min-height: 0; +} + +.analysis-sidebar { + grid-template-rows: 275px 1fr; +} + +.analysis-content { + grid-template-rows: 1fr 332px; +} + +.analysis-form { + .el-form-item__label { + color: #a8cff0; + } + + .el-select, + .el-date-editor { + width: 100%; + } + + .el-input__inner { + color: #dff8ff; + border-color: rgba(71, 190, 255, .34); + background: rgba(6, 22, 62, .68); + } +} + +.model-row { + align-items: flex-start; + flex-direction: column; + cursor: default; + + span { + color: #e9f9ff; + font-size: 13px; + } +} + +.analysis-chart { + height: calc(100% - 38px); +} + +.empty-tip--chart { + height: calc(100% - 38px); +} + +.dark-table { + color: #d6efff; + background: transparent; + + &::before { + background: rgba(82, 168, 255, .18); + } + + th.el-table__cell { + color: #bde2ff; + background: rgba(9, 39, 93, .86); + border-color: rgba(82, 168, 255, .2); + } + + tr, + td.el-table__cell { + background: rgba(4, 18, 53, .46); + border-color: rgba(82, 168, 255, .14); + } + + .el-table__body tr:hover > td.el-table__cell { + background: rgba(20, 83, 154, .62); } } @@ -129,138 +929,4 @@ z-index: 999; top: 0; left: 0; - - .left_shu { - color: #000; - font-weight: 900; - position: relative; - text-indent: 10px; - padding:16px 0 10px 0 ; - &::before { - display: block; - content: " "; - height: 16px; - width: 4px; - border-radius: 2px; - background: #0072FF; - position: absolute; - left: 0px; - } - } - - .setting_dislog { - background-color: rgba($color: #000000, $alpha: .5); - position: absolute; - width: 100%; - height: 100%; - z-index: 0; - right: 0; - top: 0; - } - - .setting_inner { - box-sizing: border-box; - background: #FFF; - width: 340px; - height: 100%; - position: absolute; - right: 0px; - top: 0; - z-index: 1; - color: #000000; - box-shadow: 0 8px 10px -5px rgba(0, 0, 0, .2), 0 16px 24px 2px rgba(0, 0, 0, .14), 0 6px 30px 5px rgba(0, 0, 0, .12); - - .setting_header { - font-size: 20px; - color: rgb(0, 0, 0); - font-weight: 900; - text-align: center; - line-height: 40px; - } - - .setting_body { - padding: 0px 16px; - box-sizing: border-box; - position: relative; - } - - .setting_item { - font-size: 14px; - line-height: 1.5; - - // display: flex; - .setting_label { - color: #555454; - } - .setting_label_tip{ - font-size: 12px; - color: #838282; - } - } - } - - .setting_inner { - animation: rtl-drawer-out .3s; - } -} - - -.settingShow { - .setting_inner { - animation: rtl-drawer-in .3s 1ms; - } - -} - -.yh-setting-fade-enter-active { - animation: yh-setting-fade-in .3s; -} - -.yh-setting-fade-leave-active { - - animation: yh-setting-fade-out .3s; - -} - -@keyframes yh-setting-fade-in { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@keyframes yh-setting-fade-out { - 0% { - opacity: 1; - - } - - 100% { - opacity: 0; - } -} - - -@keyframes rtl-drawer-in { - 0% { - transform: translate(100%, 0) - } - - 100% { - -webkit-transform: translate(0, 0); - transform: translate(0, 0) - } -} - -@keyframes rtl-drawer-out { - 0% { - transform: translate(0, 0) - } - - 100% { - transform: translate(100%, 0) - } } diff --git a/yack-web/src/views/bigScreen/home.vue b/yack-web/src/views/bigScreen/home.vue index fcbe9993..f7b5bc8d 100644 --- a/yack-web/src/views/bigScreen/home.vue +++ b/yack-web/src/views/bigScreen/home.vue @@ -1,89 +1,1034 @@ + + diff --git a/yack-web/src/views/login.vue b/yack-web/src/views/login.vue index b1e3b50f..72c51806 100644 --- a/yack-web/src/views/login.vue +++ b/yack-web/src/views/login.vue @@ -362,7 +362,7 @@ export default { setToken(res.token); this.$router .push({ - path: this.redirect || '/', + path: '/bigscreen', }) .catch(() => {}); if (this.captchaOnOff) { @@ -484,7 +484,7 @@ export default { setToken(res.data); this.$router .push({ - path: '/', + path: '/bigscreen', }) .catch(() => {}); }); @@ -517,7 +517,7 @@ export default { setToken(res.token); this.$router .push({ - path: '/', + path: '/bigscreen', }) .catch(() => {}); }) @@ -572,7 +572,7 @@ export default { .then(() => { this.$router .push({ - path: this.redirect || '/', + path: '/bigscreen', }) .catch(() => {}); })