邮件API

This commit is contained in:
2025-11-16 19:58:51 +08:00
parent 9d442b017a
commit 3b3d6dea66
91 changed files with 6017 additions and 413 deletions

View File

@@ -1,19 +1,31 @@
package com.mini.capi.api.job;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.mini.capi.biz.domain.BizDeviceInfo;
import com.mini.capi.biz.domain.BizMonitorAccount;
import com.mini.capi.biz.domain.BizMonitorHost;
import com.mini.capi.biz.domain.BizServerInfo;
import com.mini.capi.biz.service.BizDeviceInfoService;
import com.mini.capi.biz.service.BizMonitorAccountService;
import com.mini.capi.biz.service.BizMonitorHostService;
import com.mini.capi.biz.service.BizServerInfoService;
import com.mini.capi.model.ApiResult;
import com.mini.capi.model.info.CpuInfo;
import com.mini.capi.model.info.DiskInfo;
import com.mini.capi.model.info.ServerInfo;
import com.mini.capi.utils.NetworkUtils;
import com.mini.capi.utils.SystemInfoUtil;
import com.mini.capi.utils.vId;
import jakarta.annotation.Resource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -25,6 +37,16 @@ public class jobController {
@Resource
private BizMonitorHostService bizMonitorHostService;
@Resource
private BizMonitorAccountService bizMonitorAccountService;
@Resource
private BizServerInfoService bizServerInfoService;
@Resource
private BizDeviceInfoService bizDeviceInfoService;
// 注入配置好的线程池
@Resource(name = "hostMonitorExecutor")
private ThreadPoolTaskExecutor hostMonitorExecutor;
@@ -44,6 +66,7 @@ public class jobController {
boolean isReachable = NetworkUtils.isNetworkReachable(monitorHost.getIpAddress());
monitorHost.setUstatus(isReachable ? "1" : "0");
if (isReachable) {
syncServerInfo(monitorHost);
monitorHost.setLastOnlineTime(LocalDateTime.now());
}
bizMonitorHostService.update(monitorHost, updateWrapper);
@@ -53,7 +76,6 @@ public class jobController {
}, hostMonitorExecutor); // 指定使用配置的线程池
futures.add(future);
}
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.get(60, TimeUnit.SECONDS); // 超时时间可根据业务调整
@@ -62,4 +84,68 @@ public class jobController {
return ApiResult.error(101, e.getMessage());
}
}
public void syncServerInfo(BizMonitorHost host) {
try {
QueryWrapper<BizMonitorAccount> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("host_id", host.getHostId()).eq("ssh_username", "ogsapp");
BizMonitorAccount account = bizMonitorAccountService.getOne(queryWrapper);
if (account != null) {
CpuInfo cpuInfo = SystemInfoUtil.getCpuMemUsage(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), account.getSshPassword());
ServerInfo info = SystemInfoUtil.getServerBasicInfo(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), account.getSshPassword(), host.getIpAddress());
List<DiskInfo> diskInfos = SystemInfoUtil.getDiskInfos(host.getIpAddress(), account.getSshPort(), account.getSshUsername(), account.getSshPassword());
syncDeviceInfo(host, diskInfos);
Optional<BizServerInfo> serverInfoOpt = Optional.ofNullable(
bizServerInfoService.getOne(new QueryWrapper<BizServerInfo>().eq("host_id", host.getHostId()))
);
BizServerInfo serverInfo = serverInfoOpt.orElseGet(() -> {
BizServerInfo newInfo = new BizServerInfo();
newInfo.setHostId(host.getHostId()); // 初始化唯一标识
return newInfo;
});
serverInfo.setUptime(info.getUptime());
serverInfo.setOs(info.getOs());
serverInfo.setKernelVersion(info.getKernelVersion());
serverInfo.setHostname(info.getHostname());
serverInfo.setIpAddress(info.getIpAddress());
serverInfo.setCpuModel(info.getCpuModel());
serverInfo.setMemoryTotal(info.getMemoryTotal());
serverInfo.setCpuUsage(cpuInfo.getCpuUsage());
serverInfo.setMemoryUsage(cpuInfo.getMemoryUsage());
serverInfo.setLastOnlineTime(LocalDateTime.now());
UpdateWrapper<BizServerInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("host_id", host.getHostId());
bizServerInfoService.saveOrUpdate(serverInfo, updateWrapper);
}
} catch (Exception e) {
System.out.print(e.getMessage());
}
}
public void syncDeviceInfo(BizMonitorHost host, List<DiskInfo> diskInfos) {
for (DiskInfo diskInfo : diskInfos) {
Optional<BizDeviceInfo> deviceInfoOpt = Optional.ofNullable(
bizDeviceInfoService.getOne(new QueryWrapper<BizDeviceInfo>().eq("host_id", host.getHostId()).eq("device", diskInfo.getDevice()).eq("mount_point", diskInfo.getMountPoint()))
);
BizDeviceInfo deviceInfo = deviceInfoOpt.orElseGet(() -> {
BizDeviceInfo newInfo = new BizDeviceInfo();
newInfo.setHostId(host.getHostId()); // 初始化唯一标识
return newInfo;
});
deviceInfo.setDevice(diskInfo.getDevice());
deviceInfo.setMountPoint(diskInfo.getMountPoint());
deviceInfo.setTotalSize(diskInfo.getTotalSize());
deviceInfo.setUsedSize(diskInfo.getUsedSize());
deviceInfo.setUsageRate(diskInfo.getUsageRate());
deviceInfo.setLastOnlineTime(LocalDateTime.now());
UpdateWrapper<BizDeviceInfo> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("host_id", host.getHostId()).eq("device", diskInfo.getDevice()).eq("mount_point", diskInfo.getMountPoint());
bizDeviceInfoService.saveOrUpdate(deviceInfo, updateWrapper);
}
}
}

View File

@@ -14,13 +14,7 @@ public class sysController {
@Resource
private AppRuntimeInfo appRuntimeInfo;
@Resource
private AppPathInfo appPathInfo;
@Resource
private AppProcessInfo appProcessInfo;
@Resource
private AppNetworkInfo appNetworkInfo;
private sysService service;
/**
* 运行信息
@@ -28,35 +22,7 @@ public class sysController {
@GetMapping("getRunInfo")
public ApiResult<?> getRunInfo() {
try {
// 用构建者模式链式创建对象,避免重复的参数传递和构造函数调用
RunInfo runInfo = RunInfo.builder()
.runtimeInfo(RuntimeInfo.builder()
.appName(appRuntimeInfo.getAppName())
.serverPort(appRuntimeInfo.getServerPort())
.activeProfiles(appRuntimeInfo.getActiveProfiles())
.springBootVersion(appRuntimeInfo.getSpringBootVersion())
.jdkVersion(appRuntimeInfo.getJdkVersion())
.build())
.pathInfo(PathInfo.builder()
.workDir(appPathInfo.getWorkDir())
.jarDir(appPathInfo.getJarDir())
.resourceDir(appPathInfo.getResourceDir())
.build())
.processInfo(ProcessInfo.builder()
.pid(appProcessInfo.getPid())
.uptime(appProcessInfo.getUptime())
.usedHeapMemory(appProcessInfo.getUsedHeapMemory())
.maxHeapMemory(appProcessInfo.getMaxHeapMemory())
.jvmArgs(appProcessInfo.getJvmArgs())
.build())
.networkInfo(NetworkInfo.builder()
.hostName(appNetworkInfo.getHostName())
.localIp(appNetworkInfo.getLocalIp())
.allIps(appNetworkInfo.getAllIps())
.build())
.build();
return ApiResult.success(runInfo);
return ApiResult.success(service.getRunInfo());
} catch (Exception e) {
return ApiResult.error(101, e.getMessage());
}

View File

@@ -0,0 +1,54 @@
package com.mini.capi.api.sys;
import com.mini.capi.config.component.AppNetworkInfo;
import com.mini.capi.config.component.AppPathInfo;
import com.mini.capi.config.component.AppProcessInfo;
import com.mini.capi.config.component.AppRuntimeInfo;
import com.mini.capi.model.info.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class sysService {
@Resource
private AppRuntimeInfo appRuntimeInfo;
@Resource
private AppPathInfo appPathInfo;
@Resource
private AppProcessInfo appProcessInfo;
@Resource
private AppNetworkInfo appNetworkInfo;
public RunInfo getRunInfo() throws Exception {
RunInfo runInfo = RunInfo.builder()
.runtimeInfo(RuntimeInfo.builder()
.appName(appRuntimeInfo.getAppName())
.serverPort(appRuntimeInfo.getServerPort())
.activeProfiles(appRuntimeInfo.getActiveProfiles())
.springBootVersion(appRuntimeInfo.getSpringBootVersion())
.jdkVersion(appRuntimeInfo.getJdkVersion())
.build())
.pathInfo(PathInfo.builder()
.workDir(appPathInfo.getWorkDir())
.jarDir(appPathInfo.getJarDir())
.resourceDir(appPathInfo.getResourceDir())
.build())
.processInfo(ProcessInfo.builder()
.pid(appProcessInfo.getPid())
.uptime(appProcessInfo.getUptime())
.usedHeapMemory(appProcessInfo.getUsedHeapMemory())
.maxHeapMemory(appProcessInfo.getMaxHeapMemory())
.jvmArgs(appProcessInfo.getJvmArgs())
.build())
.networkInfo(NetworkInfo.builder()
.hostName(appNetworkInfo.getHostName())
.localIp(appNetworkInfo.getLocalIp())
.allIps(appNetworkInfo.getAllIps())
.build())
.build();
return runInfo;
}
}

View File

@@ -1,20 +1,80 @@
package com.mini.capi.biz;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mini.capi.biz.domain.BizHomeUser;
import com.mini.capi.biz.service.BizHomeUserService;
import com.mini.capi.model.auth.LoginRequest;
import com.mini.capi.model.info.TodoHandleDTO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@Controller
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/biz")
public class LoginController {
@GetMapping("/login")
public String showLoginPage() {
return "login";
@Resource
private BizHomeUserService userService;
/**
* 登录
*/
@PostMapping("/userLogin")
public Map<String, Object> userLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> result = new HashMap<>();
try {
String username = loginRequest.getUsername();
String password = loginRequest.getPassword();
QueryWrapper<BizHomeUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", username).eq("password", password);
BizHomeUser user = userService.getOne(queryWrapper);
if (user != null) {
request.getSession().setAttribute("currentUser", user);
// 3.3 返回成功响应
result.put("success", true);
result.put("message", "登录成功");
} else {
result.put("success", false);
result.put("message", "账号或密码错误");
}
} catch (Exception e) {
result.put("success", false);
result.put("message", "服务器内部错误," + e.getMessage());
}
return result;
}
@GetMapping("/swagger")
public String redirectToSwagger() {
return "redirect:/swagger-ui/index.html";
/**
* 处理退出登录请求
* 对应前端 fetch('/logout', {method: 'POST'})
*/
@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
try {
// 3. 清除Session中的用户信息
if (session != null) {
session.removeAttribute("currentUser"); // 清除之前存入的currentUser
session.invalidate(); // 使Session失效
}
// 5. 返回成功响应200 OK
return ResponseEntity.ok().build();
} catch (Exception e) {
// 处理异常情况
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 设备存储信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizDeviceInfo")
public class BizDeviceInfoController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 文件夹信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizFileFolders")
public class BizFileFoldersController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 文件信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizFiles")
public class BizFilesController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-14
*/
@RestController
@RequestMapping("/biz/bizHomeUser")
public class BizHomeUserController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 快捷登录系统信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-15
*/
@RestController
@RequestMapping("/biz/bizQuickLogin")
public class BizQuickLoginController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 服务器信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizServerInfo")
public class BizServerInfoController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 待办任务子表(关联主任务) 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizSubTask")
public class BizSubTaskController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* VIEW 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/bizTodoTaskView")
public class BizTodoTaskViewController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 数据表字段信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/dataTableField")
public class DataTableFieldController {
}

View File

@@ -0,0 +1,18 @@
package com.mini.capi.biz.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 数据表基础信息表 前端控制器
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@RestController
@RequestMapping("/biz/dataTableInfo")
public class DataTableInfoController {
}

View File

@@ -0,0 +1,83 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 设备存储信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_device_info")
public class BizDeviceInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private String id;
/**
* 设备
*/
@TableField("device")
private String device;
/**
* 挂载点
*/
@TableField("mount_point")
private String mountPoint;
/**
* 总容量
*/
@TableField("total_size")
private String totalSize;
/**
* 已使用容量
*/
@TableField("used_size")
private String usedSize;
/**
* 使用率(%)
*/
@TableField("usage_rate")
private double usageRate;
/**
* 最后一次检测到在线的时间
*/
@TableField("last_online_time")
private LocalDateTime lastOnlineTime;
/**
* 主机唯一标识
*/
@TableField("host_id")
private String hostId;
}

View File

@@ -0,0 +1,74 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 文件夹信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_file_folders")
public class BizFileFolders implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 文件夹ID
*/
@TableId(value = "folder_id", type = IdType.AUTO)
private String folderId;
/**
* 文件夹名称
*/
@TableField("folder_name")
private String folderName;
/**
* 父文件夹ID0表示根目录
*/
@TableField("parent_id")
private Integer parentId;
/**
* 创建人ID关联用户表
*/
@TableField("creator_id")
private Integer creatorId;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 是否删除0-未删除1-已删除)
*/
@TableField("is_deleted")
private Byte isDeleted;
/**
* 文件夹描述
*/
@TableField("description")
private String description;
}

View File

@@ -0,0 +1,98 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 文件信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_files")
public class BizFiles implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 文件ID
*/
@TableId(value = "file_id", type = IdType.AUTO)
private String fileId;
/**
* 文件名称(含扩展名)
*/
@TableField("file_name")
private String fileName;
/**
* 所属文件夹ID关联folders表
*/
@TableField("folder_id")
private String folderId;
/**
* 文件大小(字节)
*/
@TableField("file_size")
private Long fileSize;
/**
* 文件类型image/jpeg、application/pdf
*/
@TableField("file_type")
private String fileType;
/**
* 文件存储路径物理路径或云存储URL
*/
@TableField("file_path")
private String filePath;
/**
* 上传人ID关联用户表
*/
@TableField("creator_id")
private Integer creatorId;
/**
* 上传时间
*/
@TableField("upload_time")
private LocalDateTime uploadTime;
/**
* 更新时间(如修改名称)
*/
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 是否删除0-未删除1-已删除)
*/
@TableField("is_deleted")
private Byte isDeleted;
/**
* 文件版本号(用于版本控制)
*/
@TableField("version")
private Integer version;
/**
* 文件MD5值用于去重校验
*/
@TableField("md5")
private String md5;
}

View File

@@ -0,0 +1,41 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
*
* </p>
*
* @author gaoxq
* @since 2025-11-14
*/
@Getter
@Setter
@TableName("biz_home_user")
public class BizHomeUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableField("create_time")
private LocalDateTime createTime;
@TableId(value = "user_id", type = IdType.AUTO)
private String userId;
@TableField("user_name")
private String userName;
@TableField("password")
private String password;
@TableField("uname")
private String uname;
}

View File

@@ -0,0 +1,104 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 快捷登录系统信息表
* </p>
*
* @author gaoxq
* @since 2025-11-15
*/
@Getter
@Setter
@TableName("biz_quick_login")
public class BizQuickLogin implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 自增主键
*/
@TableId(value = "id", type = IdType.AUTO)
private String id;
/**
* 系统名称
*/
@TableField("system_name")
private String systemName;
/**
* 首页地址
*/
@TableField("homepage_url")
private String homepageUrl;
/**
* 图标类名(Font Awesome)
*/
@TableField("icon_class")
private String iconClass;
/**
* 图标颜色(关联Tailwind配置)
*/
@TableField("icon_color")
private String iconColor;
/**
* 排序序号(升序排列)
*/
@TableField("sort_order")
private Integer sortOrder;
/**
* 是否启用(1:启用 0:禁用)
*/
@TableField("is_enabled")
private Integer isEnabled;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
}

View File

@@ -0,0 +1,106 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 服务器信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_server_info")
public class BizServerInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private String id;
/**
* 主机运行时间
*/
@TableField("uptime")
private String uptime;
/**
* 操作系统
*/
@TableField("os")
private String os;
/**
* 内核版本
*/
@TableField("kernel_version")
private String kernelVersion;
/**
* 主机名
*/
@TableField("hostname")
private String hostname;
/**
* IP地址
*/
@TableField("ip_address")
private String ipAddress;
/**
* CPU型号
*/
@TableField("cpu_model")
private String cpuModel;
/**
* 内存总量
*/
@TableField("memory_total")
private String memoryTotal;
/**
* CPU使用率(%)
*/
@TableField("cpu_usage")
private Double cpuUsage;
/**
* 内存使用率(%)
*/
@TableField("memory_usage")
private Double memoryUsage;
/**
* 最后一次检测到在线的时间
*/
@TableField("last_online_time")
private LocalDateTime lastOnlineTime;
/**
* 主机唯一标识
*/
@TableField("host_id")
private String hostId;
}

View File

@@ -0,0 +1,95 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 待办任务子表(关联主任务)
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_sub_task")
public class BizSubTask implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 唯一标识
*/
@TableId(value = "sub_task_id", type = IdType.AUTO)
private String subTaskId;
/**
* 关联的主任务ID外键
*/
@TableField("parent_task_id")
private String parentTaskId;
/**
* 子任务详明细
*/
@TableField("sub_task_name")
private String subTaskName;
/**
* 子任务优先级
*/
@TableField("priority")
private String priority;
/**
* 任务状态
*/
@TableField("ustatus")
private String ustatus;
/**
* 完成时间
*/
@TableField("finish_time")
private LocalDateTime finishTime;
@TableField("remark")
private String remark;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
}

View File

@@ -0,0 +1,120 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* VIEW
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("biz_todo_task_view")
public class BizTodoTaskView implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 唯一标识
*/
@TableField("task_id")
private String taskId;
/**
* 任务名称
*/
@TableField("task_name")
private String taskName;
/**
* 任务详细描述
*/
@TableField("task_desc")
private String taskDesc;
/**
* 任务截止时间
*/
@TableField("deadline")
private LocalDateTime deadline;
/**
* 待办人员
*/
@TableField("todo_user")
private String todoUser;
/**
* 创建人员
*/
@TableField("login_user")
private String loginUser;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
/**
* 唯一标识
*/
@TableField("sub_task_id")
private String subTaskId;
/**
* 子任务详明细
*/
@TableField("sub_task_name")
private String subTaskName;
/**
* 子任务优先级
*/
@TableField("priority")
private String priority;
/**
* 任务状态
*/
@TableField("ustatus")
private String ustatus;
/**
* 完成时间
*/
@TableField("finish_time")
private LocalDateTime finishTime;
}

View File

@@ -0,0 +1,98 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 数据表字段信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("data_table_field")
public class DataTableField implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 字段唯一标识(主键)
*/
@TableId(value = "field_id", type = IdType.AUTO)
private String fieldId;
/**
* 关联的数据表ID外键关联data_table_info.table_id
*/
@TableField("table_id")
private String tableId;
/**
* 字段序号(表示字段在表中的顺序)
*/
@TableField("field_order")
private Integer fieldOrder;
/**
* 字段类型int、varchar、datetime等
*/
@TableField("field_type")
private String fieldType;
/**
* 字段名称
*/
@TableField("field_name")
private String fieldName;
/**
* 字段长度如varchar(50)中的50数值型可表示精度
*/
@TableField("field_length")
private Integer fieldLength;
/**
* 字段说明
*/
@TableField("field_remark")
private String fieldRemark;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
}

View File

@@ -0,0 +1,111 @@
package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 数据表基础信息表
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Getter
@Setter
@TableName("data_table_info")
public class DataTableInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 数据表唯一标识(主键)
*/
@TableId(value = "table_id", type = IdType.AUTO)
private String tableId;
/**
* 数据表名称
*/
@TableField("table_name")
private String tableName;
/**
* 数据表描述
*/
@TableField("table_comment")
private String tableComment;
/**
* 数据表大小单位MB
*/
@TableField("table_size")
private BigDecimal tableSize;
/**
* 数据来源(如:业务系统、文件导入等)
*/
@TableField("data_source")
private String dataSource;
/**
* 创建人
*/
@TableField("creator")
private String creator;
/**
* 记录条数
*/
@TableField("data_rows")
private Long dataRows;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 租户id
*/
@TableField("f_tenant_id")
private String fTenantId;
/**
* 流程id
*/
@TableField("f_flow_id")
private String fFlowId;
/**
* 流程任务主键
*/
@TableField("f_flow_task_id")
private String fFlowTaskId;
/**
* 流程任务状态
*/
@TableField("f_flow_state")
private Integer fFlowState;
/**
* 备注信息
*/
@TableField("remarks")
private String remarks;
}

View File

@@ -2,6 +2,7 @@ package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
@@ -41,4 +42,18 @@ public class ErpSummaryAllView implements Serializable {
@TableField("f_cycle")
private String fCycle;
public BigDecimal getThisValue() {
return thisValue != null ? thisValue : BigDecimal.ZERO;
}
public BigDecimal getPrevValue() {
return prevValue != null ? prevValue : BigDecimal.ZERO;
}
public BigDecimal getMomRate() {
return momRate != null ? momRate : BigDecimal.ZERO;
}
}

View File

@@ -2,6 +2,7 @@ package com.mini.capi.biz.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizDeviceInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 设备存储信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizDeviceInfoMapper extends BaseMapper<BizDeviceInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizFileFolders;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 文件夹信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizFileFoldersMapper extends BaseMapper<BizFileFolders> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizFiles;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 文件信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizFilesMapper extends BaseMapper<BizFiles> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizHomeUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-14
*/
public interface BizHomeUserMapper extends BaseMapper<BizHomeUser> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizQuickLogin;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 快捷登录系统信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-15
*/
public interface BizQuickLoginMapper extends BaseMapper<BizQuickLogin> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizServerInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 服务器信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizServerInfoMapper extends BaseMapper<BizServerInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizSubTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 待办任务子表(关联主任务) Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizSubTaskMapper extends BaseMapper<BizSubTask> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.BizTodoTaskView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* VIEW Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizTodoTaskViewMapper extends BaseMapper<BizTodoTaskView> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.DataTableField;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 数据表字段信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface DataTableFieldMapper extends BaseMapper<DataTableField> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.mapper;
import com.mini.capi.biz.domain.DataTableInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 数据表基础信息表 Mapper 接口
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface DataTableInfoMapper extends BaseMapper<DataTableInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizDeviceInfo;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 设备存储信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizDeviceInfoService extends IService<BizDeviceInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizFileFolders;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 文件夹信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizFileFoldersService extends IService<BizFileFolders> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizFiles;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 文件信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizFilesService extends IService<BizFiles> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizHomeUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-14
*/
public interface BizHomeUserService extends IService<BizHomeUser> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizQuickLogin;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 快捷登录系统信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-15
*/
public interface BizQuickLoginService extends IService<BizQuickLogin> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizServerInfo;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 服务器信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizServerInfoService extends IService<BizServerInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizSubTask;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 待办任务子表(关联主任务) 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizSubTaskService extends IService<BizSubTask> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.BizTodoTaskView;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* VIEW 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface BizTodoTaskViewService extends IService<BizTodoTaskView> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.DataTableField;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 数据表字段信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface DataTableFieldService extends IService<DataTableField> {
}

View File

@@ -0,0 +1,16 @@
package com.mini.capi.biz.service;
import com.mini.capi.biz.domain.DataTableInfo;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 数据表基础信息表 服务类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
public interface DataTableInfoService extends IService<DataTableInfo> {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizDeviceInfo;
import com.mini.capi.biz.mapper.BizDeviceInfoMapper;
import com.mini.capi.biz.service.BizDeviceInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 设备存储信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizDeviceInfoServiceImpl extends ServiceImpl<BizDeviceInfoMapper, BizDeviceInfo> implements BizDeviceInfoService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizFileFolders;
import com.mini.capi.biz.mapper.BizFileFoldersMapper;
import com.mini.capi.biz.service.BizFileFoldersService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 文件夹信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizFileFoldersServiceImpl extends ServiceImpl<BizFileFoldersMapper, BizFileFolders> implements BizFileFoldersService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizFiles;
import com.mini.capi.biz.mapper.BizFilesMapper;
import com.mini.capi.biz.service.BizFilesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 文件信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizFilesServiceImpl extends ServiceImpl<BizFilesMapper, BizFiles> implements BizFilesService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizHomeUser;
import com.mini.capi.biz.mapper.BizHomeUserMapper;
import com.mini.capi.biz.service.BizHomeUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-14
*/
@Service
public class BizHomeUserServiceImpl extends ServiceImpl<BizHomeUserMapper, BizHomeUser> implements BizHomeUserService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizQuickLogin;
import com.mini.capi.biz.mapper.BizQuickLoginMapper;
import com.mini.capi.biz.service.BizQuickLoginService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 快捷登录系统信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-15
*/
@Service
public class BizQuickLoginServiceImpl extends ServiceImpl<BizQuickLoginMapper, BizQuickLogin> implements BizQuickLoginService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizServerInfo;
import com.mini.capi.biz.mapper.BizServerInfoMapper;
import com.mini.capi.biz.service.BizServerInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 服务器信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizServerInfoServiceImpl extends ServiceImpl<BizServerInfoMapper, BizServerInfo> implements BizServerInfoService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizSubTask;
import com.mini.capi.biz.mapper.BizSubTaskMapper;
import com.mini.capi.biz.service.BizSubTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 待办任务子表(关联主任务) 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizSubTaskServiceImpl extends ServiceImpl<BizSubTaskMapper, BizSubTask> implements BizSubTaskService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.BizTodoTaskView;
import com.mini.capi.biz.mapper.BizTodoTaskViewMapper;
import com.mini.capi.biz.service.BizTodoTaskViewService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* VIEW 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class BizTodoTaskViewServiceImpl extends ServiceImpl<BizTodoTaskViewMapper, BizTodoTaskView> implements BizTodoTaskViewService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.DataTableField;
import com.mini.capi.biz.mapper.DataTableFieldMapper;
import com.mini.capi.biz.service.DataTableFieldService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 数据表字段信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class DataTableFieldServiceImpl extends ServiceImpl<DataTableFieldMapper, DataTableField> implements DataTableFieldService {
}

View File

@@ -0,0 +1,20 @@
package com.mini.capi.biz.service.impl;
import com.mini.capi.biz.domain.DataTableInfo;
import com.mini.capi.biz.mapper.DataTableInfoMapper;
import com.mini.capi.biz.service.DataTableInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 数据表基础信息表 服务实现类
* </p>
*
* @author gaoxq
* @since 2025-11-16
*/
@Service
public class DataTableInfoServiceImpl extends ServiceImpl<DataTableInfoMapper, DataTableInfo> implements DataTableInfoService {
}

View File

@@ -0,0 +1,155 @@
package com.mini.capi.biz;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mini.capi.api.sys.sysService;
import com.mini.capi.biz.domain.*;
import com.mini.capi.biz.service.*;
import com.mini.capi.model.info.CpuInfo;
import com.mini.capi.model.info.DiskInfo;
import com.mini.capi.model.info.RunInfo;
import com.mini.capi.model.info.ServerInfo;
import com.mini.capi.utils.SqlUtils;
import com.mini.capi.utils.SystemInfoUtil;
import com.mini.capi.utils.vDate;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Controller
public class viewController {
@Resource
private BizMonitorHostService hostService;
@Resource
private BizQuickLoginService loginService;
@Resource
private BizFilesService filesService;
@Resource
private BizFileFoldersService foldersService;
@Resource
private sysService service;
@Resource
private BizServerInfoService bizServerInfoService;
@Resource
private BizDeviceInfoService bizDeviceInfoService;
@Resource
private DataTableInfoService dataTableInfoService;
@Resource
private DataTableFieldService dataTableFieldService;
@Resource
private BizTodoTaskViewService bizTodoTaskViewService;
@GetMapping("/login")
public String showLoginPage() {
return "login";
}
@GetMapping("/swagger")
public String redirectToSwagger() {
return "redirect:/swagger-ui/index.html";
}
@GetMapping("/biz/index")
public String getUserHome(Model model, HttpSession session) throws Exception {
RunInfo runInfo = service.getRunInfo();
List<BizMonitorHost> hosts = hostService.list();
List<BizQuickLogin> storages = loginService.list();
BizHomeUser user = (BizHomeUser) session.getAttribute("currentUser");
QueryWrapper<BizTodoTaskView> taskViewQueryWrapper = new QueryWrapper<>();
taskViewQueryWrapper.notIn("ustatus", "CPT");
List<BizTodoTaskView> todoViews = bizTodoTaskViewService.list(taskViewQueryWrapper);
model.addAttribute("hosts", hosts);
model.addAttribute("storages", storages);
model.addAttribute("uname", user.getUname());
model.addAttribute("todoViews", todoViews);
model.addAttribute("times", vDate.getRunTimes(runInfo.getProcessInfo().getUptime()));
return "index";
}
@GetMapping("/biz/getServerInfo")
public String getServerInfo(Model model, String hostId) {
// 1. 查询服务器信息确保不为null
QueryWrapper<BizServerInfo> infoQueryWrapper = new QueryWrapper<>();
infoQueryWrapper.eq("host_id", hostId);
BizServerInfo info = bizServerInfoService.getOne(infoQueryWrapper);
// 若查询不到数据创建空对象避免前端null指针
model.addAttribute("info", info != null ? info : new BizServerInfo());
// 2. 查询设备列表,确保集合始终存在
QueryWrapper<BizDeviceInfo> deviceQueryWrapper = new QueryWrapper<>();
deviceQueryWrapper.eq("host_id", hostId);
List<BizDeviceInfo> devices = bizDeviceInfoService.list(deviceQueryWrapper);
// 若查询不到数据返回空集合而非null
model.addAttribute("devices", devices != null ? devices : Collections.emptyList());
return "server";
}
@GetMapping("/biz/getFieldDetail")
public String getFieldDetail(Model model, String tableId) {
DataTableInfo tableInfo = dataTableInfoService.getById(tableId);
QueryWrapper<DataTableField> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("table_id", tableId);
queryWrapper.orderByAsc("field_order");
List<DataTableField> fields = dataTableFieldService.list(queryWrapper);
String createSql = SqlUtils.CreateTableSql(tableInfo, fields);
String selectSql = SqlUtils.SelectSqlComments(tableInfo, fields);
model.addAttribute("createSql", createSql != null ? createSql : null);
model.addAttribute("selectSql", selectSql != null ? selectSql : null);
model.addAttribute("tableInfo", tableInfo != null ? tableInfo : new DataTableInfo());
model.addAttribute("fields", fields != null ? fields : Collections.emptyList());
return "field";
}
/**
* 数据地图
*/
@GetMapping("/biz/dataMap")
public String getDataMap(Model model) {
List<DataTableInfo> tables = dataTableInfoService.list();
Collections.sort(tables,
Comparator.nullsLast(Comparator.comparing(DataTableInfo::getCreateTime)).reversed()
);
model.addAttribute("tables", tables);
return "data";
}
/**
* 文档中心
*/
@GetMapping("/biz/dataDoc")
public String getDataDox(Model model) {
List<BizFiles> files = filesService.list();
List<BizFileFolders> folders = foldersService.list();
model.addAttribute("files", files);
model.addAttribute("folders", folders);
return "file";
}
}

View File

@@ -0,0 +1,48 @@
package com.mini.capi.biz;
import com.mini.capi.biz.domain.BizSubTask;
import com.mini.capi.biz.service.BizSubTaskService;
import com.mini.capi.model.info.TodoHandleDTO;
import jakarta.annotation.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/biz")
public class workController {
@Resource
private BizSubTaskService bizSubTaskService;
/**
* 完成待办
*
* @return
*/
@PostMapping("/finishTodoSub")
public ResponseEntity<Void> finishTodoSub(@RequestBody TodoHandleDTO handleDTO) {
// 验证参数
if (handleDTO.getTaskId() == null || handleDTO.getOpinion() == null || handleDTO.getOpinion().trim().isEmpty()) {
return ResponseEntity.badRequest().build();
}
try {
// 调用服务层处理任务
BizSubTask bizSubTask = bizSubTaskService.getById(handleDTO.getTaskId());
bizSubTask.setUstatus("CPT");
bizSubTask.setFinishTime(LocalDateTime.now());
bizSubTask.setRemark(handleDTO.getOpinion());
bizSubTaskService.updateById(bizSubTask);
return ResponseEntity.ok().build();
} catch (Exception e) {
// 处理异常(如任务不存在、数据库错误等)
return ResponseEntity.status(500).build();
}
}
}

View File

@@ -0,0 +1,23 @@
package com.mini.capi.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cPath = request.getContextPath();
HttpSession session = request.getSession();
Object currentUser = session.getAttribute("currentUser");
if (currentUser != null) {
return true;
} else {
response.sendRedirect(cPath + "/login");
return false; // 拦截
}
}
}

View File

@@ -1,14 +1,33 @@
package com.mini.capi.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 访问根路径 "/" 时,自动重定向到 Swagger UI
registry.addRedirectViewController("/", "/login");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns( // 排除不需要拦截的路径
"/", // 登录页
"/login", // 登录页
"/biz/userLogin", // 登录接口
"/static/**", // 静态资源CSS、JS、图片等
"/erpApi/**",
"/appApi/**",
"/jobApi/**"
);
}
}

View File

@@ -0,0 +1,13 @@
package com.mini.capi.model.auth;
import lombok.Data;
import java.io.Serializable;
@Data
public class LoginRequest implements Serializable {
private String username;
private String password;
private boolean remember;
}

View File

@@ -0,0 +1,22 @@
package com.mini.capi.model.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class CpuInfo implements Serializable {
private double cpuUsage; // CPU使用率
private double memoryUsage; // 内存使用率
public CpuInfo() {
}
public CpuInfo(double cpuUsage, double memoryUsage) {
this.cpuUsage = cpuUsage;
this.memoryUsage = memoryUsage;
}
}

View File

@@ -0,0 +1,26 @@
package com.mini.capi.model.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class DiskInfo implements Serializable {
private String device; // 设备
private String mountPoint; // 挂载点
private String totalSize; // 总容量
private String usedSize; // 已使用
private double usageRate; // 使用率
public DiskInfo() {
}
public DiskInfo(String device, String mountPoint, String totalSize, String usedSize, double usageRate) {
this.device = device;
this.mountPoint = mountPoint;
this.totalSize = totalSize;
this.usedSize = usedSize;
this.usageRate = usageRate;
}
}

View File

@@ -0,0 +1,19 @@
package com.mini.capi.model.info;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class FileInfo implements Serializable {
private String id; // 文件唯一标识
private String name; // 文件名
private String type; // 文件类型pdf、excel、image等
private String size; // 文件大小
private String source; // 文件来源(本地上传、共享文件等)
private LocalDateTime createTime; // 创建时间
private String creator; // 创建人
private String folderId; // 所属文件夹ID
}

View File

@@ -0,0 +1,30 @@
package com.mini.capi.model.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class ServerInfo implements Serializable {
private String uptime; // 主机运行时间
private String os; // 操作系统
private String kernelVersion; // 内核版本
private String hostname; // 主机名
private String ipAddress; // IP地址
private String cpuModel; // CPU型号
private String memoryTotal; // 内存总量
public ServerInfo() {
}
public ServerInfo(String uptime, String os, String kernelVersion, String hostname, String ipAddress, String cpuModel, String memoryTotal) {
this.uptime = uptime;
this.os = os;
this.kernelVersion = kernelVersion;
this.hostname = hostname;
this.ipAddress = ipAddress;
this.cpuModel = cpuModel;
this.memoryTotal = memoryTotal;
}
}

View File

@@ -0,0 +1,15 @@
package com.mini.capi.model.info;
import lombok.Data;
import java.io.Serializable;
@Data
public class TodoHandleDTO implements Serializable {
private Long taskId;
private String opinion;
// 必须有默认无参构造函数
public TodoHandleDTO() {}
}

View File

@@ -29,7 +29,7 @@ public class demo {
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper"));
})
.strategyConfig(builder -> {
builder.addInclude("biz_cities,biz_company,biz_mail_account,biz_project_info,biz_project_requirements,biz_province,biz_resume_employee,biz_website_storage,biz_municipalities,biz_monitor_host,biz_monitor_account,erp_summary_all_view,erp_account,erp_transaction_flow,erp_expense,erp_category,erp_income,erp_summary_source_view,biz_project_report,biz_mail_sent")
builder.addInclude("biz_sub_task")
.addTablePrefix("biz_,erp_")
.entityBuilder()
.enableLombok()

View File

@@ -150,4 +150,57 @@ public class FileUtils {
}
return result;
}
public static String getFileType(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "unknown";
}
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
switch (extension) {
case "pdf":
return "pdf";
case "doc":
case "docx":
return "word";
case "xls":
case "xlsx":
return "excel";
case "ppt":
case "pptx":
return "ppt";
case "jpg":
case "jpeg":
case "png":
case "gif":
return "image";
case "mp4":
case "avi":
case "mov":
return "video";
case "mp3":
case "wav":
return "audio";
default:
// 如果没有扩展名,视为文件夹
if (!fileName.contains(".")) {
return "folder";
}
return "unknown";
}
}
// 格式化文件大小(字节转人类可读格式)
public static String formatFileSize(long bytes) {
if (bytes < 1024) {
return bytes + " B";
} else if (bytes < 1024 * 1024) {
return String.format("%.1f KB", bytes / 1024.0);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.1f MB", bytes / (1024.0 * 1024));
} else {
return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
}
}
}

View File

@@ -1,132 +0,0 @@
package com.mini.capi.utils;
import org.springframework.util.StringUtils;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class MailParseUtils {
/**
* 解析邮件发件人(名称+地址)
*/
public static String parseFrom(Message message) throws MessagingException {
Address[] froms = message.getFrom();
if (froms == null || froms.length == 0) {
return "";
}
InternetAddress from = (InternetAddress) froms[0];
String fromName = from.getPersonal(); // 发件人名称可能为null
String fromAddr = from.getAddress(); // 发件人地址
return StringUtils.hasText(fromName) ? fromName + "<" + fromAddr + ">" : fromAddr;
}
/**
* 解析邮件收件人(多个用逗号分隔)
*/
public static String parseTo(Message message) throws MessagingException {
return parseRecipients(message, Message.RecipientType.TO);
}
/**
* 解析邮件抄送人(多个用逗号分隔)
*/
public static String parseCc(Message message) throws MessagingException {
return parseRecipients(message, Message.RecipientType.CC);
}
/**
* 通用解析收件人/抄送人
*/
private static String parseRecipients(Message message, Message.RecipientType type) throws MessagingException {
Address[] recipients = message.getRecipients(type);
if (recipients == null || recipients.length == 0) {
return "";
}
List<String> addrList = new ArrayList<>();
for (Address addr : recipients) {
InternetAddress internetAddr = (InternetAddress) addr;
String name = internetAddr.getPersonal();
String address = internetAddr.getAddress();
addrList.add(StringUtils.hasText(name) ? name + "<" + address + ">" : address);
}
return String.join(",", addrList);
}
/**
* 解析邮件内容(支持文本/HTML
*/
public static String parseContent(Part part) throws MessagingException, IOException {
if (part.isMimeType("text/plain") || part.isMimeType("text/html")) {
// 文本/HTML直接读取
return (String) part.getContent();
}
// 多部分内容(含附件),递归解析文本部分
if (part.isMimeType("multipart/*")) {
MimeMultipart multipart = (MimeMultipart) part.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String content = parseContent(bodyPart);
if (StringUtils.hasText(content)) {
return content;
}
}
}
return "";
}
/**
* 提取邮件附件(返回附件流+原始文件名)
*/
public static List<AttachmentInfo> extractAttachments(Part part) throws MessagingException, IOException {
List<AttachmentInfo> attachments = new ArrayList<>();
// 多部分内容才可能有附件
if (part.isMimeType("multipart/*")) {
MimeMultipart multipart = (MimeMultipart) part.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
// 判断是否为附件Disposition为ATTACHMENT或INLINE
String disposition = bodyPart.getDisposition();
if (disposition != null && (disposition.equals(Part.ATTACHMENT) || disposition.equals(Part.INLINE))) {
// 获取原始文件名(处理中文乱码)
String originalFileName = MimeUtility.decodeText(bodyPart.getFileName());
// 获取附件输入流
InputStream inputStream = bodyPart.getInputStream();
attachments.add(new AttachmentInfo(originalFileName, inputStream));
} else {
// 递归处理嵌套的多部分内容
attachments.addAll(extractAttachments(bodyPart));
}
}
}
return attachments;
}
/**
* 附件信息封装(原始文件名+输入流)
*/
public static class AttachmentInfo {
private String originalFileName;
private InputStream inputStream;
public AttachmentInfo(String originalFileName, InputStream inputStream) {
this.originalFileName = originalFileName;
this.inputStream = inputStream;
}
// Getter
public String getOriginalFileName() {
return originalFileName;
}
public InputStream getInputStream() {
return inputStream;
}
}
}

View File

@@ -1,88 +0,0 @@
package com.mini.capi.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
public class MailUtil {
private static final Logger logger = LoggerFactory.getLogger(MailUtil.class);
/**
* 获取邮件内容
*/
public static String getEmailContent(Message message) {
try {
Object content = message.getContent();
// 简单文本内容
if (content instanceof String) {
return (String) content;
}
// 复杂内容(多部分)
else if (content instanceof Multipart) {
Multipart multipart = (Multipart) content;
StringBuilder contentBuilder = new StringBuilder();
// 遍历所有部分
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
// 忽略附件
if (BodyPart.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) ||
bodyPart.getFileName() != null) {
continue;
}
// 获取内容
Object partContent = bodyPart.getContent();
if (partContent instanceof String) {
contentBuilder.append(partContent);
} else if (partContent instanceof Multipart) {
// 处理嵌套的多部分内容
contentBuilder.append(getMultipartContent((Multipart) partContent));
}
}
return contentBuilder.toString();
}
} catch (Exception e) {
logger.error("获取邮件内容失败", e);
}
return "";
}
/**
* 处理嵌套的多部分内容
*/
private static String getMultipartContent(Multipart multipart) throws MessagingException, IOException {
StringBuilder contentBuilder = new StringBuilder();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
// 忽略附件
if (BodyPart.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) ||
bodyPart.getFileName() != null) {
continue;
}
Object partContent = bodyPart.getContent();
if (partContent instanceof String) {
contentBuilder.append(partContent);
} else if (partContent instanceof Multipart) {
contentBuilder.append(getMultipartContent((Multipart) partContent));
}
}
return contentBuilder.toString();
}
}

View File

@@ -0,0 +1,98 @@
package com.mini.capi.utils;
import com.mini.capi.biz.domain.DataTableField;
import com.mini.capi.biz.domain.DataTableInfo;
import java.util.List;
public class SqlUtils {
/**
* 生成CREATE TABLE语句
*
* @param tableInfo 数据表基础信息
* @param fieldList 字段列表
* @return CREATE TABLE语句
*/
public static String CreateTableSql(DataTableInfo tableInfo, List<DataTableField> fieldList) {
StringBuilder sb = new StringBuilder();
// 表定义开始
sb.append("CREATE TABLE ").append(tableInfo.getTableName()).append(" (\n");
// 拼接字段定义
for (int i = 0; i < fieldList.size(); i++) {
DataTableField field = fieldList.get(i);
sb.append(" ").append(field.getFieldName()).append(" ");
// 处理字段类型和长度
if (field.getFieldLength() != null && field.getFieldLength() > 0) {
sb.append(field.getFieldType()).append("(").append(field.getFieldLength()).append(") ");
} else {
sb.append(field.getFieldType()).append(" ");
}
// 处理主键简单判断fieldId为主键字段
if ("field_id".equals(field.getFieldName()) || "table_id".equals(field.getFieldName())) {
sb.append("NOT NULL AUTO_INCREMENT ");
} else {
sb.append("DEFAULT NULL ");
}
// 字段注释
if (field.getFieldRemark() != null && !field.getFieldRemark().isEmpty()) {
sb.append("COMMENT '").append(field.getFieldRemark()).append("'");
}
// 最后一个字段不加逗号
if (i != fieldList.size() - 1) {
sb.append(",");
}
sb.append("\n");
}
// 表引擎和字符集
sb.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ");
// 表注释
if (tableInfo.getTableComment() != null && !tableInfo.getTableComment().isEmpty()) {
sb.append("COMMENT\t'").append(tableInfo.getTableComment()).append("'");
}
sb.append(";");
return sb.toString();
}
/**
* 生成带注释的SELECT语句
*
* @param tableInfo 数据表基础信息
* @param fieldList 字段列表
* @return 带注释的SELECT语句
*/
public static String SelectSqlComments(DataTableInfo tableInfo, List<DataTableField> fieldList) {
StringBuilder sb = new StringBuilder();
// 表注释
sb.append("-- 表名:").append(tableInfo.getTableName()).append("\n");
sb.append("-- 描述:").append(tableInfo.getTableComment() != null ? tableInfo.getTableComment() : "").append("\n");
sb.append("-- 数据来源:").append(tableInfo.getDataSource() != null ? tableInfo.getDataSource() : "未知").append("\n");
// SELECT语句开始
sb.append("SELECT\n");
// 拼接字段(带注释)
for (int i = 0; i < fieldList.size(); i++) {
DataTableField field = fieldList.get(i);
sb.append(" ").append(field.getFieldName()).append("");
// 字段注释
if (field.getFieldRemark() != null && !field.getFieldRemark().isEmpty()) {
sb.append(" -- ").append(field.getFieldRemark());
}
// 最后一个字段不加逗号
if (i != fieldList.size() - 1) {
sb.append(",");
}
sb.append("\n");
}
// 表名
sb.append("FROM ").append(tableInfo.getTableName()).append(";");
return sb.toString();
}
}

View File

@@ -0,0 +1,183 @@
package com.mini.capi.utils;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.mini.capi.model.info.CpuInfo;
import com.mini.capi.model.info.DiskInfo;
import com.mini.capi.model.info.ServerInfo;
import lombok.Data;
import org.springframework.util.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class SystemInfoUtil {
// SSH 连接超时时间(毫秒)
private static final int SSH_TIMEOUT = 5000;
/**
* 建立SSH连接
*/
private static Session getSshSession(String host, int port, String username, String password) throws Exception {
JSch jsch = new JSch();
Session session = jsch.getSession(username, host, port);
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setTimeout(SSH_TIMEOUT);
session.connect();
return session;
}
/**
* 执行SSH命令并返回结果
*/
private static String executeCommand(Session session, String command) throws Exception {
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
InputStream in = channel.getInputStream();
channel.connect();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in, StandardCharsets.UTF_8)
);
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
reader.close();
channel.disconnect();
return result.toString().trim();
}
/**
* 获取服务器基础信息
*/
public static ServerInfo getServerBasicInfo(String host, int port, String username, String password, String ipAddress) throws Exception {
ServerInfo info = new ServerInfo();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// 主机运行时间
String uptimeResult = executeCommand(session, "uptime");
if (StringUtils.hasText(uptimeResult)) {
String[] parts = uptimeResult.split("up ");
if (parts.length > 1) {
info.setUptime(parts[1].split(", ")[0]);
}
}
// 操作系统
String osResult = executeCommand(session, "cat /etc/os-release | grep PRETTY_NAME | cut -d'=' -f2 | tr -d '\"'");
info.setOs(osResult.isEmpty() ? "Unknown" : osResult);
// 内核版本
info.setKernelVersion(executeCommand(session, "uname -r"));
// 主机名
info.setHostname(executeCommand(session, "hostname"));
// IP地址传入参数
info.setIpAddress(ipAddress);
// CPU型号
String cpuResult = executeCommand(session, "cat /proc/cpuinfo | grep 'model name' | head -n 1 | cut -d: -f2 | sed -e 's/^ *//'");
info.setCpuModel(cpuResult.isEmpty() ? "Unknown" : cpuResult);
// 内存总量
String memResult = executeCommand(session, "free -h | grep Mem | awk '{print $2}'");
info.setMemoryTotal(memResult.isEmpty() ? "Unknown" : memResult);
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return info;
}
/**
* 获取CPU和内存使用率
*/
public static CpuInfo getCpuMemUsage(String host, int port, String username, String password) throws Exception {
CpuInfo usage = new CpuInfo();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// CPU使用率
String cpuCommand = "top -bn2 | grep '%Cpu' | tail -n1 | awk '{print 100 - $8}'";
String cpuResult = executeCommand(session, cpuCommand);
if (StringUtils.hasText(cpuResult)) {
usage.setCpuUsage(Double.parseDouble(cpuResult));
}
// 内存使用率
String memCommand = "free | grep Mem | awk '{print $3/$2 * 100}'";
String memResult = executeCommand(session, memCommand);
if (StringUtils.hasText(memResult)) {
usage.setMemoryUsage(Double.parseDouble(memResult));
}
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return usage;
}
/**
* 获取所有磁盘信息
*/
public static List<DiskInfo> getDiskInfos(String host, int port, String username, String password) throws Exception {
List<DiskInfo> diskInfos = new ArrayList<>();
Session session = null;
try {
session = getSshSession(host, port, username, password);
// 关键修改:强制英文输出 + 调整字段索引
String diskCommand = "LANG=en_US.UTF-8 df -h | awk '{print $1, $6, $2, $3, $5}'";
String diskResult = executeCommand(session, diskCommand);
if (StringUtils.hasText(diskResult)) {
String[] lines = diskResult.split("\n");
for (String line : lines) {
// 过滤空行和标题行
if (line.trim().isEmpty() || line.contains("Filesystem")) {
continue;
}
// 按任意空格分割(兼容多个空格)
String[] parts = line.trim().split("\\s+");
if (parts.length >= 5) {
String usageStr = parts[4].replace("%", "").trim();
DiskInfo diskInfo = new DiskInfo(parts[0], parts[1], parts[2], parts[3], Double.parseDouble(usageStr));
diskInfos.add(diskInfo);
}
}
}
} finally {
if (session != null && session.isConnected()) {
session.disconnect();
}
}
return diskInfos;
}
}

View File

@@ -46,4 +46,39 @@ public class vDate {
return getNow();
}
}
public static String getRunTimes(long totalSeconds) {
if (totalSeconds < 0) {
throw new IllegalArgumentException("输入的秒数不能为负数");
}
// 定义时间单位换算关系
long secondsPerMinute = 60;
long secondsPerHour = secondsPerMinute * 60; // 3600秒/小时
long secondsPerDay = secondsPerHour * 24; // 86400秒/天
// 计算各单位数值
long days = totalSeconds / secondsPerDay;
long remainingSecondsAfterDays = totalSeconds % secondsPerDay;
long hours = remainingSecondsAfterDays / secondsPerHour;
long remainingSecondsAfterHours = remainingSecondsAfterDays % secondsPerHour;
long minutes = remainingSecondsAfterHours / secondsPerMinute;
long seconds = remainingSecondsAfterHours % secondsPerMinute;
// 拼接结果字符串
StringBuilder result = new StringBuilder();
if (days > 0) {
result.append(days).append("");
}
if (hours > 0) {
result.append(hours).append("小时");
}
if (minutes > 0) {
result.append(minutes).append("");
}
// 确保至少显示秒数即使其他单位都为0
result.append(seconds).append("");
return result.toString();
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizDeviceInfoMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizDeviceInfo">
<id column="id" property="id" />
<result column="create_time" property="createTime" />
<result column="device" property="device" />
<result column="mount_point" property="mountPoint" />
<result column="total_size" property="totalSize" />
<result column="used_size" property="usedSize" />
<result column="usage_rate" property="usageRate" />
<result column="last_online_time" property="lastOnlineTime" />
<result column="host_id" property="hostId" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, id, device, mount_point, total_size, used_size, usage_rate, last_online_time, host_id
</sql>
</mapper>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizFileFoldersMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizFileFolders">
<id column="folder_id" property="folderId" />
<result column="create_time" property="createTime" />
<result column="folder_name" property="folderName" />
<result column="parent_id" property="parentId" />
<result column="creator_id" property="creatorId" />
<result column="update_time" property="updateTime" />
<result column="is_deleted" property="isDeleted" />
<result column="description" property="description" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, folder_id, folder_name, parent_id, creator_id, update_time, is_deleted, description
</sql>
</mapper>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizFilesMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizFiles">
<id column="file_id" property="fileId" />
<result column="file_name" property="fileName" />
<result column="folder_id" property="folderId" />
<result column="file_size" property="fileSize" />
<result column="file_type" property="fileType" />
<result column="file_path" property="filePath" />
<result column="creator_id" property="creatorId" />
<result column="upload_time" property="uploadTime" />
<result column="update_time" property="updateTime" />
<result column="is_deleted" property="isDeleted" />
<result column="version" property="version" />
<result column="md5" property="md5" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
file_id, file_name, folder_id, file_size, file_type, file_path, creator_id, upload_time, update_time, is_deleted, version, md5
</sql>
</mapper>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizHomeUserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizHomeUser">
<id column="user_id" property="userId" />
<result column="create_time" property="createTime" />
<result column="user_name" property="userName" />
<result column="password" property="password" />
<result column="uname" property="uname" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, user_id, user_name, password, uname
</sql>
</mapper>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizQuickLoginMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizQuickLogin">
<id column="id" property="id" />
<result column="create_time" property="createTime" />
<result column="system_name" property="systemName" />
<result column="homepage_url" property="homepageUrl" />
<result column="icon_class" property="iconClass" />
<result column="icon_color" property="iconColor" />
<result column="sort_order" property="sortOrder" />
<result column="is_enabled" property="isEnabled" />
<result column="update_time" property="updateTime" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, id, system_name, homepage_url, icon_class, icon_color, sort_order, is_enabled, update_time, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state
</sql>
</mapper>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizServerInfoMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizServerInfo">
<id column="id" property="id" />
<result column="create_time" property="createTime" />
<result column="uptime" property="uptime" />
<result column="os" property="os" />
<result column="kernel_version" property="kernelVersion" />
<result column="hostname" property="hostname" />
<result column="ip_address" property="ipAddress" />
<result column="cpu_model" property="cpuModel" />
<result column="memory_total" property="memoryTotal" />
<result column="cpu_usage" property="cpuUsage" />
<result column="memory_usage" property="memoryUsage" />
<result column="last_online_time" property="lastOnlineTime" />
<result column="host_id" property="hostId" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, id, uptime, os, kernel_version, hostname, ip_address, cpu_model, memory_total, cpu_usage, memory_usage, last_online_time, host_id
</sql>
</mapper>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizSubTaskMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizSubTask">
<id column="sub_task_id" property="subTaskId" />
<result column="create_time" property="createTime" />
<result column="parent_task_id" property="parentTaskId" />
<result column="sub_task_name" property="subTaskName" />
<result column="priority" property="priority" />
<result column="ustatus" property="ustatus" />
<result column="finish_time" property="finishTime" />
<result column="remark" property="remark" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, sub_task_id, parent_task_id, sub_task_name, priority, ustatus, finish_time, remark, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state
</sql>
</mapper>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.BizTodoTaskViewMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.BizTodoTaskView">
<result column="create_time" property="createTime" />
<result column="task_id" property="taskId" />
<result column="task_name" property="taskName" />
<result column="task_desc" property="taskDesc" />
<result column="deadline" property="deadline" />
<result column="todo_user" property="todoUser" />
<result column="login_user" property="loginUser" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
<result column="sub_task_id" property="subTaskId" />
<result column="sub_task_name" property="subTaskName" />
<result column="priority" property="priority" />
<result column="ustatus" property="ustatus" />
<result column="finish_time" property="finishTime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, task_id, task_name, task_desc, deadline, todo_user, login_user, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state, sub_task_id, sub_task_name, priority, ustatus, finish_time
</sql>
</mapper>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.DataTableFieldMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.DataTableField">
<id column="field_id" property="fieldId" />
<result column="create_time" property="createTime" />
<result column="table_id" property="tableId" />
<result column="field_order" property="fieldOrder" />
<result column="field_type" property="fieldType" />
<result column="field_name" property="fieldName" />
<result column="field_length" property="fieldLength" />
<result column="field_remark" property="fieldRemark" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, field_id, table_id, field_order, field_type, field_name, field_length, field_remark, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state
</sql>
</mapper>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mini.capi.biz.mapper.DataTableInfoMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.mini.capi.biz.domain.DataTableInfo">
<id column="table_id" property="tableId" />
<result column="create_time" property="createTime" />
<result column="table_name" property="tableName" />
<result column="table_comment" property="tableComment" />
<result column="table_size" property="tableSize" />
<result column="data_source" property="dataSource" />
<result column="creator" property="creator" />
<result column="data_rows" property="dataRows" />
<result column="update_time" property="updateTime" />
<result column="f_tenant_id" property="fTenantId" />
<result column="f_flow_id" property="fFlowId" />
<result column="f_flow_task_id" property="fFlowTaskId" />
<result column="f_flow_state" property="fFlowState" />
<result column="remarks" property="remarks" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
create_time, table_id, table_name, table_comment, table_size, data_source, creator, data_rows, update_time, f_tenant_id, f_flow_id, f_flow_task_id, f_flow_state, remarks
</sql>
</mapper>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,357 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据展示平台</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 配置Tailwind自定义颜色 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
lightBlue: '#e6f0ff',
cardBlue: '#f0f7ff',
borderBlue: '#bfd8ff',
btnBlue: '#165dff',
statBlue: '#dceaff'
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5);
border-radius: 3px;
}
.stat-card {
@apply bg-statBlue rounded-lg p-4 border border-borderBlue hover:shadow-sm transition duration-200;
}
.detail-panel {
@apply fixed top-0 right-0 h-full w-0 bg-white shadow-lg z-50 transition-all duration-300 ease-in-out overflow-hidden;
}
/* 修改为70%宽度 */
.detail-panel.open {
@apply w-[70%];
}
.overlay {
@apply fixed inset-0 bg-black bg-opacity-20 z-40 hidden transition-opacity duration-300;
}
.overlay.show {
@apply block;
}
}
</style>
</head>
<body class="bg-lightBlue min-h-screen flex flex-col">
<!-- 主容器 -->
<div class="flex-1 flex w-full mb-4 px-4 md:px-8 lg:px-16">
<!-- 左侧基本信息 (25%) -->
<div class="w-1/4 p-4 bg-white rounded-lg shadow-sm mr-4 h-[calc(100vh-3rem)] overflow-y-auto scrollbar-thin">
<h2 class="text-xl font-bold text-gray-800 mb-6 pb-2 border-b border-gray-200">基本信息</h2>
<div class="space-y-5">
<!-- 数据统计卡片 -->
<div class="stat-card">
<div class="flex items-center mb-3">
<div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-3">
<i class="fa fa-database text-btnBlue"></i>
</div>
<h3 class="text-sm font-semibold text-gray-500">数据统计</h3>
</div>
<div class="space-y-3 pl-11">
<div class="flex justify-between items-end">
<span class="text-gray-500 text-sm">总数据表</span>
<span class="text-xl font-bold text-gray-800" th:text="${tables.size()}"></span>
</div>
<div class="w-full bg-white h-1.5 rounded-full overflow-hidden">
<div class="bg-btnBlue h-full rounded-full" style="width: 75%"></div>
</div>
<div class="text-xs text-gray-500"></div>
<div class="flex justify-between items-end">
<span class="text-gray-500 text-sm">总数据量</span>
<span class="text-xl font-bold text-gray-800">348.2</span>
</div>
<div class="w-full bg-white h-1.5 rounded-full overflow-hidden">
<div class="bg-btnBlue h-full rounded-full" style="width: 60%"></div>
</div>
<div class="text-xs text-gray-500">GB</div>
<div class="flex justify-between items-end">
<span class="text-gray-500 text-sm">今日新增</span>
<span class="text-xl font-bold text-gray-800">12</span>
</div>
<div class="w-full bg-white h-1.5 rounded-full overflow-hidden">
<div class="bg-green-500 h-full rounded-full" style="width: 30%"></div>
</div>
<div class="text-xs text-gray-500">张表</div>
</div>
</div>
<!-- 来源系统卡片 -->
<div class="stat-card">
<div class="flex items-center mb-3">
<div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-3">
<i class="fa fa-sitemap text-btnBlue"></i>
</div>
<h3 class="text-sm font-semibold text-gray-500">来源系统</h3>
</div>
<div class="pl-11">
<div class="flex flex-wrap gap-2">
<span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full flex items-center">
<i class="fa fa-building-o mr-1"></i>ERP系统
</span>
<span class="px-2 py-1 bg-yellow-100 text-yellow-800 text-xs rounded-full flex items-center">
<i class="fa fa-archive mr-1"></i>数据仓库
</span>
<span class="px-2 py-1 bg-red-100 text-red-800 text-xs rounded-full flex items-center">
<i class="fa fa-cubes mr-1"></i>业务系统
</span>
</div>
</div>
</div>
</div>
</div>
<!-- 中间内容区域 -->
<div class="w-full flex flex-col h-[calc(100vh-3rem)]">
<!-- 查询区域 -->
<div class="bg-white p-4 rounded-lg shadow-sm mb-4">
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative flex-1">
<input
type="text"
id="searchInput"
placeholder="请输入表名称或描述进行查询..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
</div>
<div class="flex gap-2">
<button id="searchBtn" class="bg-btnBlue hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition duration-200">
<i class="fa fa-search"></i>
<span>查询</span>
</button>
<button id="resetBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg flex items-center gap-2 transition duration-200">
<i class="fa fa-refresh"></i>
<span>重置</span>
</button>
</div>
</div>
</div>
<div class="sticky top-0 z-10 flex justify-between items-center mb-4 bg-cardBlue p-3 rounded-lg">
<h2 class="text-xl font-bold text-gray-800">数据列表</h2>
<p class="text-sm text-gray-500"><span id="totalCount" th:text="${tables.size()}"></span> 条数据</p>
</div>
<!-- 卡片列表区域 -->
<div class="flex-1 bg-white rounded-lg shadow-sm p-4 overflow-y-auto scrollbar-thin">
<!-- 卡片网格 - 每行3个卡片 -->
<div id="tableCardContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- 卡片1 -->
<div class="border border-borderBlue bg-cardBlue rounded-lg p-4 hover:shadow-md transition duration-200 table-card"
th:each="table : ${tables}">
<div class="mb-2">
<div class="flex justify-between items-start">
<h3 class="font-semibold text-gray-800 table-name" th:text="${table.getTableName()}"></h3>
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded"
th:text="${table.getDataSource()}"></span>
</div>
</div>
<div class="mb-3 text-sm text-gray-600 table-desc" th:text="${table.getTableComment()}"></div>
<div class="mb-3 text-xs text-gray-500 space-y-1">
<div class="flex justify-between">
<span>大小:<span th:text="${table.getTableSize()}"></span></span>
<span>创建时间:<span th:text="${table.getCreateTime()}"></span></span>
</div>
<div>
<span>创建人:<span th:text="${table.getCreator()}"></span></span>
</div>
</div>
<div class="flex justify-end">
<button class="text-btnBlue hover:text-blue-700 text-sm font-medium flex items-center gap-1 transition duration-200 detail-btn"
th:data-tableId="${table.getTableId()}">
<span>详情</span>
<i class="fa fa-angle-right"></i>
</button>
</div>
</div>
</div>
<!-- 无数据提示 -->
<div id="emptyTip" class="hidden flex flex-col items-center justify-center h-40 text-gray-500">
<i class="fa fa-search-minus text-4xl mb-2"></i>
<span>未找到匹配的数据表</span>
</div>
</div>
</div>
</div>
<!-- 右侧详情面板 - 已设置为70%宽度 -->
<div class="detail-panel" id="detailPanel">
<div class="p-4 border-b border-gray-200 flex justify-between items-center sticky top-0 bg-white z-10">
<h3 class="text-lg font-semibold text-gray-800">数据详情</h3>
<button id="closeDetailBtn"
class="text-gray-500 hover:text-gray-700 transition-colors p-2 rounded-full hover:bg-gray-100">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<div style="height: calc(100% - 64px); overflow-y: auto;">
<!-- 加载中动画 -->
<div id="loadingMask" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-btnBlue"></div>
<span class="ml-3 text-gray-600">加载中...</span>
</div>
<!-- 详情内容 -->
<iframe id="detailFrame" width="100%" height="100%" frameborder="0" style="min-height: 600px;"></iframe>
</div>
</div>
<!-- 背景遮罩 -->
<div class="overlay" id="overlay"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// 核心元素获取
const searchInput = document.getElementById('searchInput');
const searchBtn = document.getElementById('searchBtn');
const resetBtn = document.getElementById('resetBtn');
const tableCards = document.querySelectorAll('.table-card');
const tableCardContainer = document.getElementById('tableCardContainer');
const emptyTip = document.getElementById('emptyTip');
const totalCountEl = document.getElementById('totalCount');
const originalTotal = totalCountEl.textContent;
// 详情面板相关逻辑
const detailPanel = document.getElementById('detailPanel');
const overlay = document.getElementById('overlay');
const closeDetailBtn = document.getElementById('closeDetailBtn');
const detailFrame = document.getElementById('detailFrame');
const loadingMask = document.getElementById('loadingMask');
const detailBtns = document.querySelectorAll('.detail-btn');
// 搜索功能实现
function handleSearch() {
const searchVal = searchInput.value.trim().toLowerCase();
let matchCount = 0;
tableCards.forEach(card => {
const tableName = card.querySelector('.table-name').textContent.toLowerCase();
const tableDesc = card.querySelector('.table-desc').textContent.toLowerCase();
// 匹配表名或描述
const isMatch = tableName.includes(searchVal) || tableDesc.includes(searchVal);
if (isMatch) {
card.style.display = '';
matchCount++;
} else {
card.style.display = 'none';
}
});
// 更新计数和空数据提示
totalCountEl.textContent = matchCount;
emptyTip.style.display = matchCount === 0 ? 'flex' : 'none';
}
// 重置功能实现
function handleReset() {
searchInput.value = '';
tableCards.forEach(card => {
card.style.display = '';
});
totalCountEl.textContent = originalTotal;
emptyTip.style.display = 'none';
}
// 绑定搜索事件
searchBtn.addEventListener('click', handleSearch);
// 输入框回车触发搜索
searchInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
handleSearch();
}
});
// 绑定重置事件
resetBtn.addEventListener('click', handleReset);
// 打开详情面板
detailBtns.forEach(btn => {
btn.addEventListener('click', function () {
const tableId = this.getAttribute('data-tableId');
// 显示加载状态
loadingMask.style.display = 'flex';
// 加载详情内容
detailFrame.src = `getFieldDetail?tableId=${tableId}`;
// 当iframe加载完成后隐藏加载状态
detailFrame.onload = function () {
loadingMask.style.display = 'none';
};
// 显示详情面板和遮罩
detailPanel.classList.add('open');
overlay.classList.add('show');
// 防止背景滚动
document.body.style.overflow = 'hidden';
});
});
// 关闭详情面板
function closeDetail() {
detailPanel.classList.remove('open');
overlay.classList.remove('show');
detailFrame.src = '';
// 恢复背景滚动
document.body.style.overflow = '';
}
closeDetailBtn.addEventListener('click', closeDetail);
overlay.addEventListener('click', closeDetail);
// 按ESC键关闭详情面板
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && detailPanel.classList.contains('open')) {
closeDetail();
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,313 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据信息展示</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#e3f2fd',
secondary: '#bbdefb',
accent: '#4a90e2', // 加深了主色调,提高对比度
card: '#ffffff',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(74, 144, 226, 0.5);
border-radius: 3px;
}
.card-hover {
transition: transform 0.2s, box-shadow 0.2s;
}
.card-hover:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 表格滚动容器样式 */
.table-container {
max-height: calc(100vh - 180px);
overflow-y: auto;
-ms-overflow-style: thin;
scrollbar-width: thin;
}
.table-container::-webkit-scrollbar {
width: 6px;
}
.table-container::-webkit-scrollbar-thumb {
background-color: rgba(74, 144, 226, 0.5);
border-radius: 3px;
}
/* 表头样式优化 - 提高可读性 */
.sticky-header th {
position: sticky;
top: 0;
z-index: 10;
background-color: #4a90e2; /* 更深的蓝色背景 */
color: white;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); /* 添加文字阴影增强可读性 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* 底部阴影区分表头和内容 */
}
}
</style>
</head>
<body class="bg-primary min-h-screen m-0">
<div class="flex h-screen pb-15">
<!-- 左侧基本信息区域 - 卡片式布局 -->
<div class="w-1/4 bg-secondary p-6 overflow-y-auto scrollbar-thin">
<h2 class="text-xl font-bold mb-6 text-gray-800 border-b-2 border-accent pb-2">基本信息</h2>
<div class="grid grid-cols-1 gap-4">
<!-- 数据表名称卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-database mr-2 text-accent"></i>数据表名称
</h3>
<p class="mt-2 text-gray-600"><span th:text="${tableInfo.getTableName()}"></span></p>
</div>
<!-- 创建时间卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-calendar-plus-o mr-2 text-accent"></i>创建时间
</h3>
<p class="mt-2 text-gray-600">
<span th:text="${tableInfo.getCreateTime()}"></span>
</p>
</div>
<!-- 更新时间卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-calendar-check-o mr-2 text-accent"></i>更新时间
</h3>
<p class="mt-2 text-gray-600">
<span th:text="${tableInfo.getUpdateTime()}"></span>
</p>
</div>
<!-- 数据记录数卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-list-ol mr-2 text-accent"></i>数据记录数
</h3>
<p class="mt-2 text-gray-600">
<span th:text="${tableInfo.getDataRows()}"></span>
</p>
</div>
<!-- 存储引擎卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-cogs mr-2 text-accent"></i>存储引擎
</h3>
<p class="mt-2 text-gray-600">InnoDB</p>
</div>
<!-- 字符集卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-font mr-2 text-accent"></i>字符集
</h3>
<p class="mt-2 text-gray-600">utf8mb4</p>
</div>
<!-- 表描述卡片 -->
<div class="bg-card rounded-lg p-4 shadow-md card-hover">
<h3 class="font-semibold text-gray-700 flex items-center">
<i class="fa fa-file-text-o mr-2 text-accent"></i>表描述
</h3>
<p class="mt-2 text-gray-600">
<span th:text="${tableInfo.getTableComment()}"></span>
</p>
</div>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="w-3/4 p-6 overflow-y-auto scrollbar-thin">
<!-- 切换标签 -->
<div class="flex border-b-2 border-accent mb-6">
<button id="fieldBtn" class="tab-btn active px-6 py-3 text-accent font-medium flex items-center gap-2"
onclick="showTab('fieldInfo')">
<i class="fa fa-table"></i>
<span>字段信息</span>
</button>
<button id="queryBtn" class="tab-btn px-6 py-3 text-gray-600 font-medium flex items-center gap-2"
onclick="showTab('queryInfo')">
<i class="fa fa-search"></i>
<span>查询信息</span>
</button>
<button id="createBtn" class="tab-btn px-6 py-3 text-gray-600 font-medium flex items-center gap-2"
onclick="showTab('createInfo')">
<i class="fa fa-code"></i>
<span>创表信息</span>
</button>
</div>
<!-- 提示消息容器 -->
<div id="notification"
class="fixed top-4 right-4 px-4 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 flex items-center gap-2 z-50"></div>
<!-- 字段信息 - 优化表格滚动和表头可读性 -->
<div id="fieldInfo" class="tab-content block">
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<!-- 表格滚动容器 -->
<div class="table-container">
<table class="min-w-full table-fixed">
<thead class="sticky-header">
<tr>
<!-- 按需求设置固定列宽,剩余宽度分配给名称和说明 -->
<th class="py-3 px-4 text-left w-[120px]">字段序号</th>
<th class="py-3 px-4 text-left w-[220px]">字段名称</th>
<th class="py-3 px-4 text-left w-[120px]">字段类型</th>
<th class="py-3 px-4 text-left w-[120px]">字段长度</th>
<th class="py-3 px-4 text-left grow">字段说明</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200" th:each="field : ${fields}">
<!-- 数据行 -->
<tr class="hover:bg-gray-50">
<td class="py-3 px-4 w-[120px]" th:text="${field.getFieldOrder()}"></td>
<td class="py-3 px-4 w-[220px]" th:text="${field.getFieldName()}"></td>
<td class="py-3 px-4 w-[120px]" th:text="${field.getFieldType()}"></td>
<td class="py-3 px-4 w-[120px]" th:text="${field.getFieldLength()}"></td>
<td class="py-3 px-4 grow" th:text="${field.getFieldRemark()}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 查询信息 -->
<div id="queryInfo" class="tab-content hidden">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-semibold text-gray-800">SELECT 语句</h3>
<button onclick="copyToClipboard('queryText')"
class="bg-accent hover:bg-blue-600 text-white px-4 py-2 rounded flex items-center gap-1 transition-colors">
<i class="fa fa-copy"></i>
<span>复制</span>
</button>
</div>
<div class="bg-gray-800 text-gray-100 p-4 rounded-lg font-mono text-sm h-[calc(100vh-180px)] overflow-auto scrollbar-thin">
<pre id="queryText" th:text="${selectSql}"></pre>
</div>
</div>
<!-- 创表信息 -->
<div id="createInfo" class="tab-content hidden">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-semibold text-gray-800">CREATE 语句</h3>
<button onclick="copyToClipboard('createText')"
class="bg-accent hover:bg-blue-600 text-white px-4 py-2 rounded flex items-center gap-1 transition-colors">
<i class="fa fa-copy"></i>
<span>复制</span>
</button>
</div>
<div class="bg-gray-800 text-gray-100 p-4 rounded-lg font-mono text-sm h-[calc(100vh-180px)] overflow-auto scrollbar-thin">
<pre id="createText" th:text="${createSql}"></pre>
</div>
</div>
</div>
</div>
<script>
// 切换标签显示
function showTab(tabId) {
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.add('hidden');
});
document.getElementById(tabId).classList.remove('hidden');
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('text-accent');
btn.classList.add('text-gray-600');
});
if (tabId === 'fieldInfo') {
document.getElementById('fieldBtn').classList.remove('text-gray-600');
document.getElementById('fieldBtn').classList.add('text-accent');
} else if (tabId === 'queryInfo') {
document.getElementById('queryBtn').classList.remove('text-gray-600');
document.getElementById('queryBtn').classList.add('text-accent');
} else if (tabId === 'createInfo') {
document.getElementById('createBtn').classList.remove('text-gray-600');
document.getElementById('createBtn').classList.add('text-accent');
}
}
// 显示通知消息
function showNotification(message, isSuccess) {
const notification = document.getElementById('notification');
notification.textContent = '';
notification.className = 'fixed top-4 right-4 px-4 py-3 rounded-lg shadow-lg transform translate-x-full transition-transform duration-300 flex items-center gap-2 z-50';
notification.classList.add(isSuccess ? 'bg-green-500' : 'bg-red-500');
notification.classList.add('text-white');
const icon = document.createElement('i');
icon.className = isSuccess ? 'fa fa-check-circle' : 'fa fa-exclamation-circle';
notification.appendChild(icon);
const text = document.createTextNode(message);
notification.appendChild(text);
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 10);
setTimeout(() => {
notification.classList.add('translate-x-full');
}, 3000);
}
// 复制文本到剪贴板并显示提示
function copyToClipboard(elementId) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text)
.then(() => {
showNotification('复制成功!', true);
})
.catch(err => {
console.error('复制失败: ', err);
showNotification('复制失败,请手动复制', false);
});
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,348 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>超大容量快捷图标库300+</title>
<!-- 引入外部资源 -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFC9',
success: '#52C41A',
warning: '#FAAD14',
danger: '#FF4D4F',
purple: '#722ED1',
blue: '#1890FF',
green: '#00B42A',
yellow: '#FF7D00',
indigo: '#4096ff',
pink: '#f5222d',
cyan: '#13c2c2',
orange: '#fa8c16',
dark: '#1D2129',
}
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.icon-card {
@apply flex flex-col items-center p-4 rounded-xl border border-gray-200 hover:border-primary hover:shadow-md transition-all duration-300 cursor-pointer;
}
.icon-container {
@apply w-14 h-14 rounded-full flex items-center justify-center mb-3 text-xl;
}
.icon-search-highlight {
@apply bg-primary/5 border-primary/30;
}
}
</style>
</head>
<body class="bg-gray-50 p-4 md:p-6">
<div class="max-w-7xl mx-auto">
<!-- 头部与搜索区 -->
<div class="mb-8">
<h1 class="text-2xl md:text-3xl font-bold text-dark mb-2">快捷图标库300+图标)</h1>
<p class="text-gray-600 mb-6">海量功能图标与名称组合,支持搜索和筛选</p>
<!-- 搜索与筛选区 -->
<div class="flex flex-col sm:flex-row gap-4 mb-6">
<div class="relative flex-1">
<input
type="text"
id="iconSearch"
placeholder="搜索图标名称或类名数据库、fa-database..."
class="w-full py-3 pl-10 pr-4 rounded-lg border border-gray-200 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary transition-all"
>
<i class="fa fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
</div>
<div class="flex gap-2">
<select id="colorFilter"
class="py-3 px-4 rounded-lg border border-gray-200 focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary">
<option value="all">所有颜色</option>
<option value="primary"> primary</option>
<option value="secondary"> secondary</option>
<option value="success"> success</option>
<option value="warning"> warning</option>
<option value="danger"> danger</option>
<option value="purple"> purple</option>
</select>
<button id="resetFilter" class="py-3 px-4 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
<i class="fa fa-refresh mr-1"></i> 重置
</button>
</div>
</div>
<!-- 统计信息 -->
<div class="text-sm text-gray-500 mb-2">
<span id="iconCount">0</span> 个图标(显示 <span id="visibleCount">0</span> 个)
</div>
</div>
<!-- 图标网格容器 -->
<div id="iconGrid"
class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 md:gap-6">
<!-- 图标将通过JavaScript动态生成 -->
</div>
<!-- 使用说明 -->
<div class="mt-10 p-4 bg-white rounded-lg border border-gray-200">
<h3 class="font-bold mb-3">使用说明</h3>
<ul class="list-disc pl-5 text-gray-600 space-y-2 text-sm">
<li>每个图标下方显示了对应的Font Awesome类名如fa-database</li>
<li>点击图标可复制其HTML代码到剪贴板</li>
<li>支持按名称、类名搜索和按颜色筛选</li>
<li>如需调整颜色可修改容器的bg-*和text-*类如bg-primary/10和text-primary</li>
<li>Font Awesome官方文档<a href="https://fontawesome.com/v4/icons/" class="text-primary hover:underline"
target="_blank">https://fontawesome.com/v4/icons/</a></li>
</ul>
</div>
</div>
<!-- 复制成功提示 -->
<div id="copyToast"
class="fixed bottom-6 right-6 bg-success text-white px-4 py-2 rounded-lg shadow-lg opacity-0 transition-opacity duration-300 flex items-center">
<i class="fa fa-check-circle mr-2"></i> 已复制到剪贴板
</div>
<script>
// 颜色池 - 用于随机分配图标颜色
const colorPool = [
'primary', 'secondary', 'success', 'warning', 'danger',
'purple', 'blue', 'green', 'yellow', 'indigo', 'pink', 'cyan', 'orange'
];
// 基础图标数据50个示例实际使用时可扩展到300+
const baseIcons = [
{name: '数据库管理', icon: 'fa-database'},
{name: '性能监控', icon: 'fa-line-chart'},
{name: '用户管理', icon: 'fa-user-circle'},
{name: '安全中心', icon: 'fa-shield'},
{name: '日志审计', icon: 'fa-file-text'},
{name: '系统设置', icon: 'fa-cog'},
{name: '备份恢复', icon: 'fa-cloud-upload'},
{name: 'API接口', icon: 'fa-plug'},
{name: '告警中心', icon: 'fa-bell'},
{name: '网络配置', icon: 'fa-wifi'},
{name: '存储管理', icon: 'fa-hdd-o'},
{name: '任务调度', icon: 'fa-calendar-check-o'},
{name: '消息中心', icon: 'fa-envelope'},
{name: '报表分析', icon: 'fa-bar-chart'},
{name: '帮助中心', icon: 'fa-question-circle'},
{name: '服务器管理', icon: 'fa-server'},
{name: '代码仓库', icon: 'fa-code'},
{name: '项目管理', icon: 'fa-tasks'},
{name: '团队管理', icon: 'fa-users'},
{name: '密钥管理', icon: 'fa-key'},
{name: '流量分析', icon: 'fa-area-chart'},
{name: '访问控制', icon: 'fa-ban'},
{name: '风险检测', icon: 'fa-exclamation-triangle'},
{name: '国际化设置', icon: 'fa-language'},
{name: '资源下载', icon: 'fa-download'},
{name: '文件上传', icon: 'fa-upload'},
{name: '定时任务', icon: 'fa-clock-o'},
{name: '数据地图', icon: 'fa-map'},
{name: '文档中心', icon: 'fa-book'},
{name: '收藏夹', icon: 'fa-star'},
{name: '邮件系统', icon: 'fa-envelope-o'},
{name: '即时通讯', icon: 'fa-comments'},
{name: '视频会议', icon: 'fa-video-camera'},
{name: '语音通话', icon: 'fa-phone'},
{name: '数据同步', icon: 'fa-exchange'},
{name: '缓存清理', icon: 'fa-trash'},
{name: '病毒扫描', icon: 'fa-bug'},
{name: '防火墙', icon: 'fa-shield'},
{name: '负载均衡', icon: 'fa-balance-scale'},
{name: '容器管理', icon: 'fa-cubes'},
{name: '虚拟机', icon: 'fa-desktop'},
{name: '云存储', icon: 'fa-cloud'},
{name: 'CDN加速', icon: 'fa-bolt'},
{name: 'DNS配置', icon: 'fa-sitemap'},
{name: 'SSL证书', icon: 'fa-lock'},
{name: 'API文档', icon: 'fa-file-code-o'},
{name: '错误跟踪', icon: 'fa-bug'},
{name: '性能分析', icon: 'fa-tachometer'},
{name: '代码审查', icon: 'fa-search'}
];
// 扩展到300个图标通过组合基础图标+功能前缀)
function generate300Icons() {
const prefixes = [
'用户', '系统', '数据', '安全', '网络', '存储', '应用',
'服务', '设备', '终端', '日志', '监控', '分析', '报表',
'配置', '管理', '运维', '开发', '测试', '生产', '备份',
'恢复', '同步', '迁移', '部署', '发布', '更新', '升级',
'降级', '回滚', '审计', '统计', '查询', '过滤', '导出',
'导入', '打印', '分享', '协作', '权限', '角色', '部门'
];
const icons = [...baseIcons];
let currentLength = baseIcons.length;
// 循环生成直到达到300个
while (currentLength < 300) {
// 随机组合前缀和基础图标
const randomPrefix = prefixes[Math.floor(Math.random() * prefixes.length)];
const randomBase = baseIcons[Math.floor(Math.random() * baseIcons.length)];
// 避免重复名称
const newName = `${randomPrefix}${randomBase.name}`;
if (!icons.some(icon => icon.name === newName)) {
icons.push({
name: newName,
icon: randomBase.icon // 复用基础图标
});
currentLength++;
}
}
return icons;
}
// 生成所有图标
const allIcons = generate300Icons();
const iconGrid = document.getElementById('iconGrid');
const iconCountEl = document.getElementById('iconCount');
const visibleCountEl = document.getElementById('visibleCount');
const copyToast = document.getElementById('copyToast');
// 初始化图标网格
function renderIcons(iconsToRender = allIcons) {
// 清空网格
iconGrid.innerHTML = '';
// 渲染图标
iconsToRender.forEach(icon => {
// 随机分配颜色(也可根据需要固定颜色)
const color = colorPool[Math.floor(Math.random() * colorPool.length)];
const iconCard = document.createElement('div');
iconCard.className = 'icon-card';
iconCard.dataset.name = icon.name;
iconCard.dataset.icon = icon.icon;
iconCard.dataset.color = color;
iconCard.innerHTML = `
<div class="icon-container bg-${color}/10 text-${color}">
<i class="fa ${icon.icon}"></i>
</div>
<span class="font-medium text-center">${icon.name}</span>
<div class="text-xs text-gray-500 mt-1">${icon.icon}</div>
`;
// 点击复制功能
iconCard.addEventListener('click', () => {
const html = `
<div class="icon-card">
<div class="icon-container bg-${color}/10 text-${color}">
<i class="fa ${icon.icon}"></i>
</div>
<span class="font-medium">${icon.name}</span>
<div class="text-xs text-gray-500 mt-2">${icon.icon}</div>
</div>
`.trim();
navigator.clipboard.writeText(html).then(() => {
// 显示复制成功提示
copyToast.style.opacity = '1';
setTimeout(() => {
copyToast.style.opacity = '0';
}, 2000);
});
});
iconGrid.appendChild(iconCard);
});
// 更新统计
iconCountEl.textContent = allIcons.length;
visibleCountEl.textContent = iconsToRender.length;
}
// 初始化页面
renderIcons();
// 搜索功能
document.getElementById('iconSearch').addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase().trim();
const colorFilter = document.getElementById('colorFilter').value;
if (!searchTerm && colorFilter === 'all') {
renderIcons();
return;
}
// 筛选图标
const filtered = allIcons.filter(icon => {
const matchesSearch =
icon.name.toLowerCase().includes(searchTerm) ||
icon.icon.toLowerCase().includes(searchTerm);
const matchesColor = colorFilter === 'all' || true; // 颜色筛选在渲染时处理
return matchesSearch && matchesColor;
});
renderIcons(filtered);
// 高亮搜索结果
if (searchTerm) {
document.querySelectorAll('.icon-card').forEach(card => {
const nameMatch = card.dataset.name.toLowerCase().includes(searchTerm);
const iconMatch = card.dataset.icon.toLowerCase().includes(searchTerm);
if (nameMatch || iconMatch) {
card.classList.add('icon-search-highlight');
} else {
card.classList.remove('icon-search-highlight');
}
});
}
// 应用颜色筛选
if (colorFilter !== 'all') {
document.querySelectorAll('.icon-card').forEach(card => {
if (card.dataset.color === colorFilter) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
// 更新可见计数
const visibleCount = document.querySelectorAll(`.icon-card[data-color="${colorFilter}"]`).length;
visibleCountEl.textContent = visibleCount;
}
});
// 颜色筛选
document.getElementById('colorFilter').addEventListener('change', (e) => {
// 触发搜索事件以应用筛选
document.getElementById('iconSearch').dispatchEvent(new Event('input'));
});
// 重置筛选
document.getElementById('resetFilter').addEventListener('click', () => {
document.getElementById('iconSearch').value = '';
document.getElementById('colorFilter').value = 'all';
renderIcons();
});
</script>
</body>
</html>

View File

@@ -0,0 +1,712 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统首页</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#E3F2FD',
secondary: '#BBDEFB',
accent: '#64B5F6',
dark: '#1976D2',
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(100, 181, 246, 0.5);
border-radius: 3px;
}
.status-online {
@apply bg-green-500;
}
.status-offline {
@apply bg-red-500;
}
.status-warning {
@apply bg-yellow-500;
}
/* 自定义提示弹窗样式 */
.custom-toast {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
font-size: 14px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
z-index: 9999;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
}
.toast-success {
background-color: #10B981; /* 成功绿色 */
}
.toast-error {
background-color: #EF4444; /* 错误红色 */
}
.toast-show {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="bg-primary h-screen overflow-hidden flex flex-col">
<!-- 顶部区域 -->
<header class="bg-primary border-b border-accent/30 py-3 px-6 flex items-center justify-between shadow-sm">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 bg-dark rounded-lg flex items-center justify-center text-white">
<i class="fa fa-cogs text-xl"></i>
</div>
<h1 class="text-xl font-bold text-dark"><a th:href="@{/biz/index}">智慧门户</a></h1>
</div>
<!-- 中间模块导航 -->
<nav class="flex items-center space-x-8">
<a href="javascript:loadContent('dataMap')"
class="text-dark hover:text-accent transition-colors duration-200 flex items-center cursor-pointer">
<i class="fa fa-map-o mr-2"></i>
<span>数据地图</span>
</a>
<a href="javascript:loadContent('dataDoc')"
class="text-dark hover:text-accent transition-colors duration-200 flex items-center cursor-pointer">
<i class="fa fa-book mr-2"></i>
<span>文档中心</span>
</a>
</nav>
<!-- 右侧账号信息 -->
<div class="relative">
<button id="userMenuBtn" class="flex items-center space-x-2 focus:outline-none">
<img th:src="@{/images/user.jpg}" alt="用户头像"
class="w-8 h-8 rounded-full border-2 border-accent">
<span class="text-dark font-medium" th:text="${uname}"></span>
<i class="fa fa-chevron-down text-xs text-dark"></i>
</button>
<div id="userMenu" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 hidden z-10">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary transition-colors duration-200">
<i class="fa fa-user mr-2"></i>个人信息
</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-primary transition-colors duration-200">
<i class="fa fa-cog mr-2"></i>系统设置
</a>
<div class="border-t border-gray-200 my-1"></div>
<a href="javascript:void(0);" id="logoutLink"
class="block px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors duration-200">
<i class="fa fa-sign-out mr-2"></i>退出登录
</a>
</div>
</div>
</header>
<!-- 内容区域 -->
<main class="flex-1 bg-primary overflow-hidden flex flex-col">
<!-- 状态栏 -->
<div class="bg-secondary/50 px-6 py-2 flex justify-between items-center border-b border-accent/20">
<div class="flex space-x-6">
<div class="flex items-center text-dark">
<i class="fa fa-clock-o mr-2"></i>
<span id="systemUptime">系统运行时长: <span th:text="${times}"></span> </span>
</div>
<div class="flex items-center text-dark">
<i class="fa fa-users mr-2"></i>
<span>在线人数: <span id="onlineUsers">24</span></span>
</div>
</div>
<div class="text-dark font-medium" id="currentTime"></div>
</div>
<!-- 主题区域 -->
<div class="flex-1 px-6 py-4 overflow-hidden mb-15">
<!-- 内容切换核心容器 -->
<div id="contentContainer" class="h-full bg-white rounded-lg shadow-sm overflow-hidden">
<!-- 默认首页内容 -->
<div id="defaultContent" class="h-full p-4 overflow-hidden">
<div class="flex h-full space-x-4">
<!-- 第一部分 (30%) -->
<div class="w-[30%] bg-secondary/50 rounded-lg shadow-sm flex flex-col overflow-hidden">
<!-- 日历 (50%) -->
<div class="h-[50%] p-4 border-b border-accent/20 overflow-hidden">
<h2 class="text-dark font-semibold mb-3 flex items-center">
<i class="fa fa-calendar mr-2"></i>日历
</h2>
<div id="calendar" class="bg-white/70 rounded-lg p-3 h-[calc(100%-30px)] flex flex-col">
<div class="flex justify-between items-center mb-2">
<button class="text-dark hover:text-accent"><i class="fa fa-chevron-left"></i>
</button>
<h3 class="font-medium text-dark">2025年11月</h3>
<button class="text-dark hover:text-accent"><i class="fa fa-chevron-right"></i>
</button>
</div>
<div class="grid grid-cols-7 gap-1 text-center text-xs text-gray-600 mb-1">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="grid grid-cols-7 gap-1 text-center flex-1"></div>
</div>
</div>
<!-- 主机列表 (50%) -->
<div class="h-[50%] p-4 overflow-hidden">
<h2 class="text-dark font-semibold mb-3 flex items-center">
<i class="fa fa-server mr-2"></i>主机列表
</h2>
<div class="bg-white/70 rounded-lg p-3 h-[calc(100%-30px)] overflow-y-auto scrollbar-thin">
<div class="space-y-3">
<div class="flex items-center justify-between p-2 border-b border-gray-100"
th:each="host : ${hosts}">
<div>
<div class="font-medium text-dark">
<a class="text-primary" style="cursor: pointer;"
data-bs-toggle="modal"
data-bs-target="#serverDetailModal"
th:data-hostid="${host.getHostId()}">
<span th:text="${host.getHostname()}">主机名</span>
</a>
</div>
<div class="text-xs text-gray-500"><p>主机地址:<span
th:text="${host.getIpAddress()}"></span></p>
</div>
<div class="text-xs text-gray-500"><p>在线时间:<span
th:text="${host.getLastOnlineTime()}"></span></p>
</div>
</div>
<div class="flex items-center">
<span class="w-2 h-2 rounded-full status-online mr-2"></span>
<span class="text-sm text-green-600"
th:text="${host.getUstatus() == '1' ? '运行中' : '已离线' }"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 第二部分 (45%) -->
<div class="w-[45%] bg-secondary/50 rounded-lg shadow-sm flex flex-col overflow-hidden">
<div class="h-full p-4 overflow-hidden">
<div class="flex border-b border-accent/20 pb-2 mb-3">
<button id="todoBtn" class="px-4 py-1 bg-accent text-white rounded-md mr-2 font-medium">
<i class="fa fa-tasks mr-1"></i>待办信息
</button>
<button id="notificationBtn"
class="px-4 py-1 bg-white/50 text-dark hover:bg-accent/30 rounded-md font-medium transition-colors duration-200">
<i class="fa fa-bell mr-1"></i>通知信息
</button>
</div>
<!-- 待办信息内容 -->
<div id="todoContent"
class="bg-white/70 rounded-lg p-3 h-[calc(100%-50px)] overflow-y-auto scrollbar-thin">
<div class="space-y-4">
<div class="p-3 border-l-4 border-accent bg-primary/50 rounded-r"
th:each="todo : ${todoViews}" th:data-task-id="${todo.getSubTaskId()}">
<div class="flex justify-between items-start">
<h3 class="font-medium text-dark" th:text="${todo.getTaskName()}"></h3>
<span class="text-xs bg-red-100 text-red-600 px-2 py-0.5 rounded"
th:text="${todo.getPriority()}"></span>
</div>
<p class="text-sm text-gray-600 mt-1" th:text="${todo.getTaskDesc()}"></p>
<div class="flex justify-between items-center mt-2">
<span class="text-xs text-gray-500">截止日期:<span
th:text="${todo.getDeadline()}"></span></span>
<button class="text-xs text-accent hover:underline">处理</button>
</div>
</div>
</div>
</div>
<!-- 通知信息内容 (默认隐藏) -->
<div id="notificationContent"
class="bg-white/70 rounded-lg p-3 h-[calc(100%-50px)] overflow-y-auto scrollbar-thin hidden">
<div class="space-y-4">
<div class="p-3 border-b border-gray-100">
<div class="flex justify-between items-start">
<h3 class="font-medium text-dark">数据库备份成功</h3>
<span class="text-xs text-gray-500">今天 08:30</span>
</div>
<p class="text-sm text-gray-600 mt-1">
主数据库自动备份已完成备份文件大小2.4GB,存储路径:/backup/db/20251115/</p>
</div>
<div class="p-3 border-b border-gray-100">
<div class="flex justify-between items-start">
<h3 class="font-medium text-dark">用户登录提醒</h3>
<span class="text-xs text-gray-500">昨天 16:45</span>
</div>
<p class="text-sm text-gray-600 mt-1">您的账号在新设备Windows 10, Chrome
120登录IP地址113.25.XX.XX如非本人操作请及时修改密码。</p>
</div>
</div>
</div>
</div>
</div>
<!-- 第三部分 (25%) -->
<div class="w-[25%] bg-secondary/50 rounded-lg shadow-sm flex flex-col overflow-hidden">
<!-- 快捷登录 -->
<div class="h-full p-4 overflow-hidden">
<h2 class="text-dark font-semibold mb-3 flex items-center">
<i class="fa fa-rocket mr-2"></i>快捷登录
</h2>
<!-- 搜索框 -->
<div class="relative mb-3">
<input type="text" id="quickSearch" placeholder="搜索快捷登录..."
class="w-full pl-9 pr-3 py-2 bg-white/70 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-accent">
<i class="fa fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
</div>
<!-- 快捷登录图标列表 -->
<div id="quickLinks"
class="bg-white/70 rounded-lg p-4 h-[calc(100%-80px)] overflow-y-auto scrollbar-thin">
<div class="grid grid-cols-4 gap-4">
<!-- 快捷登录项 -->
<a th:each="storage : ${storages}" th:href="${storage.getHomepageUrl()}"
target="_blank"
class="flex flex-col items-center p-3 hover:bg-primary rounded-lg transition-colors duration-200 group">
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 group-hover:bg-blue-600 group-hover:text-white transition-colors duration-200">
<i th:class="${storage.getIconClass()}"></i>
</div>
<span class="text-xs mt-2 text-center text-dark"
th:text="${storage.getSystemName()}"></span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 内容加载iframe默认隐藏 -->
<iframe id="contentFrame" class="w-full h-full border-0 hidden" src=""></iframe>
</div>
</div>
</main>
<!-- 服务器详情模态框 -->
<div class="modal fade" id="serverDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered" style="width: 80%; max-width: 1400px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">服务器详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-0" style="max-height: 60vh; height: 60vh; overflow: hidden;">
<div style="height: 100%; overflow-y: auto; scrollbar-width: thin; position: relative;">
<!-- 加载中动画 -->
<div id="loadingMask"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.8); display: none; justify-content: center; align-items: center; z-index: 100;">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
<iframe id="detailFrame" width="100%" height="100%" frameborder="0"
style="min-height: 600px;"></iframe>
</div>
</div>
</div>
</div>
</div>
<!-- 退出确认弹窗 -->
<div id="logoutModal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300">
<div class="bg-white rounded-lg w-full max-w-md p-6 transform scale-95 transition-transform duration-300">
<h3 class="text-lg font-medium text-gray-900 mb-4">温馨提示</h3>
<p class="text-gray-600 mb-6">您确定要退出当前系统吗?</p>
<div class="flex justify-end gap-3">
<button id="cancelLogout"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
<button id="confirmLogout"
class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors">
确认
</button>
</div>
</div>
</div>
<!-- 处理意见弹窗 -->
<div id="handleTaskModal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300">
<div class="bg-white rounded-lg w-full max-w-md p-6 transform scale-95 transition-transform duration-300">
<h3 class="text-lg font-medium text-gray-900 mb-4">待办信息处理意见:</h3>
<div class="mb-4">
<textarea id="taskOpinion" rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent"
placeholder="请输入处理意见..."></textarea>
</div>
<div class="flex justify-end gap-3">
<button id="cancelHandleTask"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
<button id="confirmHandleTask"
class="px-4 py-2 bg-accent text-white rounded-md hover:bg-dark transition-colors">
确认
</button>
</div>
</div>
</div>
<!-- 引入Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 提示弹窗工具函数 - 新增
function showToast(message, type = 'success') {
// 创建弹窗元素
const toast = document.createElement('div');
toast.className = `custom-toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// 显示弹窗
setTimeout(() => toast.classList.add('toast-show'), 10);
// 3秒后隐藏并移除
setTimeout(() => {
toast.classList.remove('toast-show');
setTimeout(() => document.body.removeChild(toast), 300);
}, 3000);
}
// 动态日历渲染函数
function renderCalendar(year, month) {
const calendarContainer = document.querySelector('#calendar .grid:last-child');
const monthTitle = document.querySelector('#calendar h3');
calendarContainer.innerHTML = '';
monthTitle.textContent = `${year}${month + 1}`;
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const lastDayOfPrevMonth = new Date(year, month, 0).getDate();
// 渲染上月剩余天数
for (let i = firstDay - 1; i >= 0; i--) {
const prevDay = document.createElement('div');
prevDay.className = 'py-2 text-gray-400 text-sm';
prevDay.textContent = lastDayOfPrevMonth - i;
calendarContainer.appendChild(prevDay);
}
// 渲染当月天数
const today = new Date();
const isCurrentMonth = today.getFullYear() === year && today.getMonth() === month;
const currentDate = today.getDate();
for (let i = 1; i <= daysInMonth; i++) {
const day = document.createElement('div');
day.className = 'py-2 text-sm';
if (isCurrentMonth && i === currentDate) {
day.classList.add('bg-accent', 'text-white', 'rounded-full', 'font-medium');
}
day.textContent = i;
calendarContainer.appendChild(day);
}
// 渲染下月起始天数
const totalDays = firstDay + daysInMonth;
const nextMonthDays = totalDays % 7 !== 0 ? 7 - (totalDays % 7) : 0;
for (let i = 1; i <= nextMonthDays; i++) {
const nextDay = document.createElement('div');
nextDay.className = 'py-2 text-gray-400 text-sm';
nextDay.textContent = i;
calendarContainer.appendChild(nextDay);
}
}
// 初始化日历
let currentDate = new Date();
let currentYear = currentDate.getFullYear();
let currentMonth = currentDate.getMonth();
renderCalendar(currentYear, currentMonth);
// 绑定月份切换事件
document.querySelectorAll('#calendar button')[0].addEventListener('click', () => {
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar(currentYear, currentMonth);
});
document.querySelectorAll('#calendar button')[1].addEventListener('click', () => {
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar(currentYear, currentMonth);
});
// 内容加载函数
function loadContent(pageUrl) {
const defaultContent = document.getElementById('defaultContent');
const contentFrame = document.getElementById('contentFrame');
defaultContent.classList.add('hidden');
contentFrame.classList.remove('hidden');
contentFrame.src = pageUrl;
}
// 返回首页函数
function backToHome() {
const defaultContent = document.getElementById('defaultContent');
const contentFrame = document.getElementById('contentFrame');
defaultContent.classList.remove('hidden');
contentFrame.classList.add('hidden');
contentFrame.src = '';
}
// 用户菜单切换
const userMenuBtn = document.getElementById('userMenuBtn');
const userMenu = document.getElementById('userMenu');
userMenuBtn.addEventListener('click', () => userMenu.classList.toggle('hidden'));
document.addEventListener('click', (e) => {
if (!userMenuBtn.contains(e.target) && !userMenu.contains(e.target)) {
userMenu.classList.add('hidden');
}
});
// 待办和通知切换
const todoBtn = document.getElementById('todoBtn');
const notificationBtn = document.getElementById('notificationBtn');
const todoContent = document.getElementById('todoContent');
const notificationContent = document.getElementById('notificationContent');
todoBtn.addEventListener('click', () => {
todoBtn.classList.remove('bg-white/50', 'text-dark');
todoBtn.classList.add('bg-accent', 'text-white');
notificationBtn.classList.remove('bg-accent', 'text-white');
notificationBtn.classList.add('bg-white/50', 'text-dark', 'hover:bg-accent/30');
todoContent.classList.remove('hidden');
notificationContent.classList.add('hidden');
});
notificationBtn.addEventListener('click', () => {
notificationBtn.classList.remove('bg-white/50', 'text-dark', 'hover:bg-accent/30');
notificationBtn.classList.add('bg-accent', 'text-white');
todoBtn.classList.remove('bg-accent', 'text-white');
todoBtn.classList.add('bg-white/50', 'text-dark');
notificationContent.classList.remove('hidden');
todoContent.classList.add('hidden');
});
// 快捷登录搜索功能
const quickSearch = document.getElementById('quickSearch');
const quickLinks = document.querySelectorAll('#quickLinks a');
quickSearch.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
quickLinks.forEach(link => {
const text = link.querySelector('span').textContent.toLowerCase();
link.style.display = text.includes(searchTerm) ? 'flex' : 'none';
});
});
// 更新当前时间
function updateCurrentTime() {
const now = new Date();
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
document.getElementById('currentTime').textContent = now.toLocaleString('zh-CN', options);
}
updateCurrentTime();
setInterval(updateCurrentTime, 1000);
// 服务器详情模态框
document.addEventListener('DOMContentLoaded', function () {
const modal = new bootstrap.Modal(document.getElementById('serverDetailModal'));
const detailFrame = document.getElementById('detailFrame');
document.getElementById('serverDetailModal').addEventListener('show.bs.modal', function (e) {
const hostId = e.relatedTarget.getAttribute('data-hostid');
detailFrame.src = `getServerInfo?hostId=${hostId}`;
});
document.getElementById('serverDetailModal').addEventListener('hide.bs.modal', function () {
detailFrame.src = '';
});
});
// 服务器详情模态框加载动画
const detailModal = document.getElementById('serverDetailModal');
const loadingMask = document.getElementById('loadingMask');
const detailFrame = document.getElementById('detailFrame');
detailModal.addEventListener('show.bs.modal', function (e) {
loadingMask.style.display = 'flex';
const targetUrl = e.relatedTarget.dataset.url;
detailFrame.src = targetUrl;
});
detailFrame.addEventListener('load', function () {
loadingMask.style.display = 'none';
});
detailModal.addEventListener('hide.bs.modal', function () {
detailFrame.src = '';
loadingMask.style.display = 'none';
});
// 退出登录功能
const logoutLink = document.getElementById('logoutLink');
const logoutModal = document.getElementById('logoutModal');
const cancelLogout = document.getElementById('cancelLogout');
const confirmLogout = document.getElementById('confirmLogout');
function showModal() {
logoutModal.classList.remove('opacity-0', 'pointer-events-none');
logoutModal.querySelector('div').classList.remove('scale-95');
logoutModal.querySelector('div').classList.add('scale-100');
document.body.style.overflow = 'hidden';
}
function hideModal() {
logoutModal.classList.add('opacity-0', 'pointer-events-none');
logoutModal.querySelector('div').classList.remove('scale-100');
logoutModal.querySelector('div').classList.add('scale-95');
document.body.style.overflow = '';
}
logoutLink.addEventListener('click', showModal);
cancelLogout.addEventListener('click', hideModal);
logoutModal.addEventListener('click', (e) => {
if (e.target === logoutModal) hideModal();
});
// 处理待办任务弹窗功能
const handleButtons = document.querySelectorAll('#todoContent button.text-accent');
const handleTaskModal = document.getElementById('handleTaskModal');
const cancelHandleTask = document.getElementById('cancelHandleTask');
const confirmHandleTask = document.getElementById('confirmHandleTask');
const taskOpinion = document.getElementById('taskOpinion');
let currentTaskId = null;
handleButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
const taskItem = button.closest('.border-l-4');
currentTaskId = taskItem.dataset.taskId || 'default-task-id';
taskOpinion.value = '';
showHandleModal();
});
});
function showHandleModal() {
handleTaskModal.classList.remove('opacity-0', 'pointer-events-none');
handleTaskModal.querySelector('div').classList.remove('scale-95');
handleTaskModal.querySelector('div').classList.add('scale-100');
document.body.style.overflow = 'hidden';
}
function hideHandleModal() {
handleTaskModal.classList.add('opacity-0', 'pointer-events-none');
handleTaskModal.querySelector('div').classList.remove('scale-100');
handleTaskModal.querySelector('div').classList.add('scale-95');
document.body.style.overflow = '';
currentTaskId = null;
}
cancelHandleTask.addEventListener('click', hideHandleModal);
handleTaskModal.addEventListener('click', (e) => {
if (e.target === handleTaskModal) hideHandleModal();
});
// 确认处理替换alert为弹窗
confirmHandleTask.addEventListener('click', () => {
const opinion = taskOpinion.value.trim();
if (!opinion) {
showToast('请输入处理意见', 'error');
return;
}
fetch('finishTodoSub', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
taskId: currentTaskId,
opinion: opinion
})
})
.then(response => {
if (response.ok) {
showToast('处理成功');
hideHandleModal();
location.reload();
} else {
showToast('处理失败,请重试', 'error');
}
})
.catch(error => {
console.error('处理请求失败:', error);
showToast('网络错误,处理失败', 'error');
});
});
// 确认退出替换alert为弹窗
confirmLogout.addEventListener('click', () => {
fetch('logout', {method: 'POST'})
.then(response => {
if (response.ok) {
sessionStorage.removeItem('token');
window.location.href = '/cApi/login';
} else {
showToast('退出失败,请重试', 'error'); // 替换alert
}
})
.catch(error => {
console.error('退出请求失败:', error);
showToast('网络错误,退出失败', 'error'); // 替换alert
});
});
</script>
</body>
</html>

View File

@@ -38,71 +38,31 @@
}
.bg-glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.bg-glass-hover {
background: rgba(255, 255, 255, 0.12);
transition: background 0.3s ease;
}
.text-shadow {
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.cube {
transform-style: preserve-3d;
animation: rotate 15s infinite linear;
}
.float {
animation: float 6s ease-in-out infinite;
}
.pulse {
animation: pulse 4s infinite;
text-shadow: 0 2px 15px rgba(0, 0, 0, 0.3);
}
.modal-backdrop {
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
}
/* 动画定义 */
@keyframes rotate {
0% {
transform: rotateX(0deg) rotateY(0deg);
}
100% {
transform: rotateX(360deg) rotateY(360deg);
}
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0px);
}
}
@keyframes pulse {
0% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.05);
}
100% {
opacity: 0.6;
transform: scale(1);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.input-focus-ring {
box-shadow: 0 0 0 2px rgba(255, 209, 102, 0.4);
}
}
/* 动画优化 */
@keyframes fadeIn {
from {
opacity: 0;
@@ -125,12 +85,16 @@
}
}
.geo-element {
position: absolute;
border-radius: 8px;
background: rgba(59, 130, 246, 0.15);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-8px);
}
100% {
transform: translateY(0px);
}
}
.error-modal {
@@ -140,31 +104,33 @@
.error-modal.hide {
animation: fadeOut 0.3s ease-in forwards;
}
.feature-card {
animation: float 4s ease-in-out infinite;
}
.feature-card:nth-child(2) {
animation-delay: 1s;
}
.feature-card:nth-child(3) {
animation-delay: 2s;
}
</style>
</head>
<body class="font-inter min-h-screen flex items-center justify-center p-4 md:p-0 overflow-hidden">
<!-- 背景和装饰元素 -->
<body class="font-inter min-h-screen flex items-center justify-center p-4 md:p-0 overflow-x-hidden">
<!-- 背景优化 - 增加渐变叠加层让背景图更协调 -->
<div class="fixed inset-0 bg-gradient-tech z-0"></div>
<div class="geo-element w-32 h-32 top-1/4 left-1/6 float" style="animation-delay: 0s;"></div>
<div class="geo-element w-16 h-16 bottom-1/3 right-1/5 float" style="animation-delay: 1s;"></div>
<div class="geo-element w-24 h-24 top-2/3 left-1/4 float" style="animation-delay: 2s;"></div>
<div class="geo-element w-20 h-20 bottom-1/4 right-1/3 float" style="animation-delay: 1.5s;"></div>
<div class="fixed inset-0 z-0 opacity-30">
<div class="absolute inset-0"
style="background-image: radial-gradient(rgba(255,255,255,0.3) 1px, transparent 1px); background-size: 30px 30px;"></div>
</div>
<div class="fixed top-1/3 right-1/4 w-32 h-32 cube z-0">
<div class="absolute inset-0 bg-primary/20 rounded-lg transform rotate-x-0 rotate-y-0"></div>
<div class="absolute inset-0 bg-accent/20 rounded-lg transform rotate-x-90 rotate-y-0"></div>
<div class="absolute inset-0 bg-secondary/20 rounded-lg transform rotate-x-0 rotate-y-90"></div>
<!-- 科技感装饰元素 -->
<div class="fixed inset-0 z-0 overflow-hidden opacity-20">
<div class="absolute top-1/4 left-1/4 w-96 h-96 rounded-full bg-primary/30 blur-3xl"></div>
<div class="absolute bottom-1/4 right-1/4 w-80 h-80 rounded-full bg-accent/20 blur-3xl"></div>
</div>
<!-- 错误提示弹窗 (默认隐藏) -->
<div id="errorModal" class="fixed inset-0 z-50 flex items-center justify-center hidden">
<!-- 遮罩层 -->
<div class="absolute inset-0 bg-darkBlue/70 modal-backdrop" id="modalBackdrop"></div>
<!-- 弹窗内容 -->
<div class="relative bg-white/10 backdrop-blur-xl border border-white/20 rounded-xl p-6 w-full max-w-md shadow-2xl error-modal">
<div class="absolute inset-0 bg-darkBlue/80 modal-backdrop" id="modalBackdrop"></div>
<div class="relative bg-white/15 backdrop-blur-xl border border-white/25 rounded-xl p-6 w-full max-w-md shadow-2xl error-modal transform transition-all">
<div class="text-center mb-4">
<div class="w-16 h-16 mx-auto bg-red-500/20 rounded-full flex items-center justify-center mb-4">
<i class="fa fa-exclamation-triangle text-red-400 text-2xl"></i>
@@ -173,27 +139,28 @@
<p class="text-white/80" id="modalErrorMessage">账号或密码错误,请重新输入</p>
</div>
<button id="closeModal"
class="w-full mt-4 bg-accent hover:bg-accent/90 text-darkBlue font-medium py-2.5 px-4 rounded-lg transition-all duration-300">
class="w-full mt-4 bg-accent hover:bg-accent/90 text-darkBlue font-medium py-2.5 px-4 rounded-lg transition-all duration-300 shadow-lg shadow-accent/20 hover:shadow-accent/30">
确定
</button>
</div>
</div>
<!-- 登录卡片 -->
<div class="relative w-full max-w-5xl bg-white/10 backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden flex flex-col md:flex-row z-10 border border-white/20">
<!-- 左侧品牌区域 -->
<div class="p-6 md:p-10 lg:p-12 flex-1 flex flex-col justify-between relative">
<div class="absolute -top-10 -left-10 w-40 h-40 bg-accent/10 rounded-full blur-3xl"></div>
<div class="absolute -bottom-20 -right-10 w-60 h-60 bg-primary/20 rounded-full blur-3xl"></div>
<div class="relative">
<p class="text-white/80 text-base md:text-lg max-w-md">
一站式智能管理平台,融合前沿技术,为您提供高效、安全、便捷的服务体验
<!-- 登录卡片 - 科技感背景图(协调不突兀) -->
<div class="relative w-full max-w-5xl bg-white/10 backdrop-blur-xl rounded-2xl shadow-2xl overflow-hidden flex flex-col md:flex-row z-10 border border-white/20"
style="background-image: url('images/login-brand.png'); background-size: cover; background-position: center; background-blend-mode: overlay;">
<!-- 左侧品牌区域 - 半透明遮罩提升可读性 -->
<div class="p-6 md:p-10 lg:p-12 flex-1 flex flex-col justify-between bg-darkBlue/40 backdrop-blur-sm">
<div>
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-white text-shadow mb-6">智慧门户</h2>
<p class="text-white/90 text-base md:text-lg max-w-md leading-relaxed">
一站式智能管理平台,融合前沿技术,为您提供高效、安全、便捷的服务体验
助力数字化转型,创造更大价值
</p>
</div>
<div class="mt-10 md:mt-20 space-y-6 relative">
<div class="bg-glass p-4 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 transform hover:-translate-y-1">
<div class="mt-10 md:mt-20 space-y-6">
<div class="bg-glass bg-glass-hover p-5 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 feature-card">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 rounded-full bg-accent/20 flex items-center justify-center text-accent pulse">
<div class="w-12 h-12 rounded-full bg-accent/20 flex items-center justify-center text-accent">
<i class="fa fa-shield text-xl"></i>
</div>
<div>
@@ -202,9 +169,9 @@
</div>
</div>
</div>
<div class="bg-glass p-4 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 transform hover:-translate-y-1">
<div class="bg-glass bg-glass-hover p-5 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 feature-card">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 rounded-full bg-secondary/20 flex items-center justify-center text-secondary pulse">
<div class="w-12 h-12 rounded-full bg-secondary/20 flex items-center justify-center text-secondary">
<i class="fa fa-bolt text-xl"></i>
</div>
<div>
@@ -213,9 +180,9 @@
</div>
</div>
</div>
<div class="bg-glass p-4 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 transform hover:-translate-y-1">
<div class="bg-glass bg-glass-hover p-5 rounded-xl border border-white/10 hover:border-white/30 transition-all duration-300 feature-card">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center text-primary pulse">
<div class="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center text-primary">
<i class="fa fa-cubes text-xl"></i>
</div>
<div>
@@ -225,20 +192,17 @@
</div>
</div>
</div>
<div class="mt-10 md:mt-16 text-center md:text-left text-white/60 text-sm relative">
<p>&copy; 2023 智慧门户 版权所有</p>
<div class="mt-10 md:mt-16 text-center md:text-left text-white/60 text-sm">
<p>&copy; 2025 智慧门户 版权所有</p>
</div>
</div>
<!-- 右侧登录表单区域 -->
<div class="flex-1 p-6 md:p-10 lg:p-12 bg-white/10 backdrop-blur-xl border-l border-white/20 flex flex-col justify-center">
<!-- 右侧登录表单区域 - 增强遮罩确保表单清晰 -->
<div class="flex-1 p-6 md:p-10 lg:p-12 bg-darkBlue/60 backdrop-blur-xl border-l border-white/20 flex flex-col justify-center">
<div class="max-w-md mx-auto w-full">
<div class="text-center md:text-left mb-10 relative">
<div class="absolute top-0 right-0 md:right-0">
<img th:src="@{/images/login-brand.png}" alt="logo">
</div>
<h1 class="text-[clamp(2rem,5vw,3rem)] font-bold mb-4 text-white text-shadow">智慧门户</h1>
<p class="text-white/70">欢迎登录系统</p>
<div class="text-center md:text-left mb-10">
<h1 class="text-[clamp(2rem,5vw,3rem)] font-bold mb-4 text-white text-shadow">账户登录</h1>
<p class="text-white/70">欢迎回来,请输入您的账号信息</p>
</div>
<form id="loginForm" class="space-y-6">
@@ -250,7 +214,7 @@
<i class="fa fa-user text-white/50"></i>
</div>
<input type="text" id="username" name="username"
class="block w-full pl-12 pr-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-accent/50 focus:border-accent text-white placeholder-white/40 transition-all duration-300 outline-none"
class="block w-full pl-12 pr-4 py-3 bg-white/10 border border-white/20 rounded-lg focus:border-accent focus:input-focus-ring text-white placeholder-white/40 transition-all duration-300 outline-none"
placeholder="请输入账号" required>
</div>
</div>
@@ -259,14 +223,13 @@
<div class="space-y-3">
<div class="flex items-center justify-between">
<label for="password" class="block text-sm font-medium text-white/80">密码</label>
<a href="#" class="text-sm text-accent hover:text-accent/80 transition-colors">忘记密码?</a>
</div>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<i class="fa fa-lock text-white/50"></i>
</div>
<input type="password" id="password" name="password"
class="block w-full pl-12 pr-12 py-3 bg-white/10 border border-white/20 rounded-lg focus:ring-2 focus:ring-accent/50 focus:border-accent text-white placeholder-white/40 transition-all duration-300 outline-none"
class="block w-full pl-12 pr-12 py-3 bg-white/10 border border-white/20 rounded-lg focus:border-accent focus:input-focus-ring text-white placeholder-white/40 transition-all duration-300 outline-none"
placeholder="请输入密码" required>
<button type="button" id="togglePassword"
class="absolute inset-y-0 right-0 pr-4 flex items-center text-white/50 hover:text-white transition-colors">
@@ -286,7 +249,7 @@
<!-- 登录按钮 -->
<button type="submit"
class="w-full bg-accent hover:bg-accent/90 text-darkBlue font-medium py-3 px-4 rounded-lg transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-accent/50 focus:ring-offset-2 focus:ring-offset-white/10">
class="w-full bg-accent hover:bg-accent/90 text-darkBlue font-medium py-3 px-4 rounded-lg transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:ring-offset-2 focus:ring-offset-white/10 shadow-lg shadow-accent/20 hover:shadow-accent/30 transform hover:-translate-y-0.5 active:translate-y-0">
登录
</button>
</form>
@@ -295,6 +258,13 @@
</div>
<script>
if (window.self !== window.top) {
// 跳转到顶层窗口,强制取消嵌套
window.top.location.href = window.self.location.href;
}
// 密码显示/隐藏切换
const togglePassword = document.getElementById('togglePassword');
const password = document.getElementById('password');
@@ -302,13 +272,8 @@
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
password.setAttribute('type', type);
const icon = togglePassword.querySelector('i');
if (type === 'password') {
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
icon.classList.toggle('fa-eye');
icon.classList.toggle('fa-eye-slash');
});
// 错误弹窗相关元素
@@ -321,25 +286,23 @@
function showErrorModal(message = '账号或密码错误,请重新输入') {
modalErrorMessage.textContent = message;
errorModal.classList.remove('hidden');
// 阻止页面滚动
document.body.style.overflow = 'hidden';
}
// 隐藏错误弹窗
function hideErrorModal() {
errorModal.querySelector('.error-modal').classList.add('hide');
const modal = errorModal.querySelector('.error-modal');
modal.classList.add('hide');
setTimeout(() => {
errorModal.classList.add('hidden');
errorModal.querySelector('.error-modal').classList.remove('hide');
// 恢复页面滚动
modal.classList.remove('hide');
document.body.style.overflow = '';
}, 300);
}
// 关闭弹窗事件
// 关闭弹窗事件绑定
closeModal.addEventListener('click', hideErrorModal);
modalBackdrop.addEventListener('click', hideErrorModal);
// 按ESC键关闭弹窗
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !errorModal.classList.contains('hidden')) {
hideErrorModal();
@@ -352,10 +315,16 @@
e.preventDefault();
// 获取表单数据
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const remember = document.getElementById('remember').checked;
// 简单表单验证
if (!username || !password) {
showErrorModal('账号和密码不能为空');
return;
}
// 模拟登录加载状态
const submitButton = loginForm.querySelector('button[type="submit"]');
const originalText = submitButton.innerHTML;
@@ -363,34 +332,29 @@
submitButton.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i> 登录中...';
try {
// 发送登录请求到后端
const response = await fetch('userLogin', { // 替换为实际后端登录接口
// 发送登录请求到后端(替换为实际接口地址)
const response = await fetch('biz/userLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
remember
}),
body: JSON.stringify({username, password, remember}),
credentials: 'include'
});
const result = await response.json();
console.log(result)
if (response.ok && result.success) {
// 登录成功,跳转到首页
window.location.href = '/index'; // 替换为实际首页路径
// 登录成功,跳转到首页(替换为实际首页路径)
window.location.href = 'biz/index';
} else {
// 登录失败,显示错误弹窗
const errorMsg = result.message || '账号或密码错误,请重新输入';
showErrorModal(errorMsg);
// 登录失败,显示错误信息
showErrorModal(result.message || '账号或密码错误,请重新输入');
}
} catch (error) {
// 网络错误等异常情况
// 网络错误处理
console.error('登录请求失败:', error);
showErrorModal('网络异常,请后重试');
showErrorModal('网络异常,请检查网络连接后重试');
} finally {
// 恢复按钮状态
submitButton.disabled = false;
@@ -398,11 +362,11 @@
}
});
// 输入框交互效果
// 输入框交互优化(聚焦时轻微上浮效果
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
input.addEventListener('focus', () => {
input.parentElement.classList.add('scale-[1.02]');
input.parentElement.classList.add('scale-[1.02]', 'transition-transform', 'duration-300');
});
input.addEventListener('blur', () => {
input.parentElement.classList.remove('scale-[1.02]');

View File

@@ -0,0 +1,290 @@
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>系统监控仪表盘</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
<!-- 配置Tailwind自定义颜色和工具类 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#e0f2fe',
secondary: '#bae6fd',
accent: '#38bdf8',
dark: '#0f172a',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(148, 163, 184, 0.5);
border-radius: 3px;
}
}
</style>
</head>
<body class="bg-primary h-full overflow-hidden">
<div class="flex h-[calc(100vh-15px)] px-4 gap-4">
<!-- 左侧系统信息卡片区域 -->
<div class="w-1/3 bg-white rounded-lg p-4 card-shadow flex flex-col gap-4 overflow-y-auto scrollbar-thin">
<!-- 主机运行时间 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-clock-o text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">运行时长</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getUptime()}"></p>
</div>
</div>
</div>
<!-- 系统信息 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-windows text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">操作系统</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getOs()}"></p>
</div>
</div>
</div>
<!-- 内核版本 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-cogs text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">内核版本</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getKernelVersion()}"></p>
</div>
</div>
</div>
<!-- 主机名 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-desktop text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">主机名称</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getHostname()}"></p>
</div>
</div>
</div>
<!-- IP地址 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-ip-address text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">IP地址</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getIpAddress()}"></p>
</div>
</div>
</div>
<!-- CPU型号 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-microchip text-accent text-lg"></i>
</div>
<div>
<h3 class="text-sm text-gray-500">CPU型号</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getCpuModel()}"></p>
</div>
</div>
</div>
<!-- 内存总量 -->
<div class="p-3 bg-white rounded-lg border border-gray-100 card-shadow">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center">
<i class="fa fa-database text-accent text-lg"></i> <!-- 内存总量使用数据库图标,代表存储容量 -->
</div>
<div>
<h3 class="text-sm text-gray-500">内存总量</h3>
<p class="text-lg font-semibold text-dark" th:text="${info.getMemoryTotal()}"></p>
</div>
</div>
</div>
</div>
<!-- 右侧区域 -->
<div class="w-2/3 bg-white rounded-lg p-4 card-shadow flex flex-col gap-4">
<!-- 上方CPU和内存卡片 -->
<div class="flex gap-4">
<!-- CPU使用率 -->
<div class="flex-1 p-3 bg-white rounded-lg border border-gray-100 card-shadow flex flex-col items-center">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
<i class="fa fa-microchip text-accent text-lg"></i>
</div>
<h3 class="text-sm text-gray-500 mb-2">CPU使用率</h3>
<div class="relative w-32 h-32">
<canvas id="cpuChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-xl font-bold text-dark" data-cpu-usage
th:text="${info.getCpuUsage()}"></span>
<p>%</p>
</div>
</div>
</div>
<!-- 内存使用率 -->
<div class="flex-1 p-3 bg-white rounded-lg border border-gray-100 card-shadow flex flex-col items-center">
<div class="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
<i class="fa fa-area-chart text-accent text-lg"></i> <!-- 内存使用率使用面积图图标,代表资源监控 -->
</div>
<h3 class="text-sm text-gray-500 mb-2">内存使用率</h3>
<div class="relative w-32 h-32">
<canvas id="memoryChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<span class="text-xl font-bold text-dark" data-memory-usage
th:text="${info.getMemoryUsage()}"></span>
<p>%</p>
</div>
</div>
</div>
</div>
<!-- 下方磁盘使用率表格 -->
<div class="flex-1 overflow-hidden flex flex-col">
<div class="flex items-center gap-2 mb-2">
<i class="fa fa-hdd-o text-accent"></i> <!-- 磁盘使用率添加硬盘图标 -->
<h3 class="text-sm text-gray-500">磁盘使用率</h3>
</div>
<div class="flex-1 overflow-y-auto scrollbar-thin">
<table class="min-w-full bg-white">
<thead class="bg-gray-50 sticky top-0">
<tr>
<th class="py-2 px-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
设备
</th>
<th class="py-2 px-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
挂载点
</th>
<th class="py-2 px-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
总容量
</th>
<th class="py-2 px-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
已使用
</th>
<th class="py-2 px-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
使用率
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr th:each=" disk : ${devices}">
<td class="py-2 px-3 text-sm text-gray-900" th:text="${disk.getDevice()}"></td>
<td class="py-2 px-3 text-sm text-gray-900" th:text="${disk.getMountPoint()}"></td>
<td class="py-2 px-3 text-sm text-gray-900" th:text="${disk.getTotalSize()}"></td>
<td class="py-2 px-3 text-sm text-gray-900" th:text="${disk.getUsedSize()}"></td>
<td class="py-2 px-3">
<div class="flex items-center">
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-accent h-2.5 rounded-full"
th:style="'width: ' + ${disk.getUsageRate()} + '%;'"></div>
</div>
<span class="ml-2 text-sm font-medium text-gray-700"
th:text="${disk.getUsageRate()}"></span>
<p>%</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// 配置环形进度图的通用选项
const getChartOptions = (value) => {
let color;
if (value < 60) {
color = '#38bdf8'; // 蓝色 - 正常
} else if (value < 80) {
color = '#eab308'; // 黄色 - 警告
} else {
color = '#ef4444'; // 红色 - 危险
}
return {
type: 'doughnut',
data: {
datasets: [{
data: [value, 100 - value],
backgroundColor: [color, '#f1f5f9'],
borderWidth: 0,
cutout: '80%'
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {display: false},
tooltip: {enabled: false}
},
animation: {
animateRotate: true,
animateScale: true
}
}
};
};
// 初始化CPU图表
const cpuUsageElement = document.querySelector('.text-xl.font-bold.text-dark[data-cpu-usage]');
const cpuUsageValue = parseFloat(cpuUsageElement.textContent.replace('%', ''));
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
new Chart(cpuCtx, getChartOptions(cpuUsageValue));
// 初始化内存图表
const memoryUsageElement = document.querySelector('.text-xl.font-bold.text-dark[data-memory-usage]');
const memoryUsageValue = parseFloat(memoryUsageElement.textContent.replace('%', ''));
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
new Chart(memoryCtx, getChartOptions(memoryUsageValue));
</script>
</body>
</html>