From d510034abb1e72cf70283dc04775c79f81795ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Tue, 24 Mar 2026 18:36:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=AB=AF=E5=B7=A5=E4=BD=9C=E5=8F=B0=E6=80=BB=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Dashboard 统计接口、菜单迁移与权限点 - 管理端工作台页面切换为真实概览数据和趋势图 - 默认首页切换到工作台 --- .../dashboard/DashboardController.java | 31 + .../DashboardDistributionItemVo.java | 87 +++ .../dashboard/DashboardOverviewQuery.java | 17 + .../model/dashboard/DashboardOverviewVo.java | 60 ++ .../model/dashboard/DashboardSummaryVo.java | 57 ++ .../model/dashboard/DashboardTrendItemVo.java | 37 ++ .../service/dashboard/DashboardService.java | 13 + .../dashboard/impl/DashboardServiceImpl.java | 307 ++++++++++ .../V5__dashboard_workspace_menu.sql | 39 ++ easyflow-ui-admin/app/src/api/dashboard.ts | 49 ++ .../app/src/locales/langs/en-US/menus.json | 3 + .../app/src/locales/langs/zh-CN/menus.json | 3 + .../src/views/dashboard/workspace/index.vue | 536 ++++++++++-------- .../packages/@core/preferences/src/config.ts | 2 +- 14 files changed, 1002 insertions(+), 239 deletions(-) create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/dashboard/DashboardController.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardDistributionItemVo.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewQuery.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewVo.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardSummaryVo.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardTrendItemVo.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/DashboardService.java create mode 100644 easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/impl/DashboardServiceImpl.java create mode 100644 easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V5__dashboard_workspace_menu.sql create mode 100644 easyflow-ui-admin/app/src/api/dashboard.ts diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/dashboard/DashboardController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/dashboard/DashboardController.java new file mode 100644 index 0000000..d44df4e --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/dashboard/DashboardController.java @@ -0,0 +1,31 @@ +package tech.easyflow.admin.controller.dashboard; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery; +import tech.easyflow.admin.model.dashboard.DashboardOverviewVo; +import tech.easyflow.admin.service.dashboard.DashboardService; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; + +/** + * 管理端工作台统计接口。 + */ +@RestController +@RequestMapping("/api/v1/dashboard") +public class DashboardController { + + private final DashboardService dashboardService; + + public DashboardController(DashboardService dashboardService) { + this.dashboardService = dashboardService; + } + + @GetMapping("/overview") + @SaCheckPermission("/api/v1/dashboard/query") + public Result overview(DashboardOverviewQuery query) { + return Result.ok(dashboardService.getOverview(SaTokenUtil.getLoginAccount(), query)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardDistributionItemVo.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardDistributionItemVo.java new file mode 100644 index 0000000..fd92bc3 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardDistributionItemVo.java @@ -0,0 +1,87 @@ +package tech.easyflow.admin.model.dashboard; + +/** + * 工作台分布/排行项。 + */ +public class DashboardDistributionItemVo { + + private String key; + + private String label; + + private Long value; + + private Long userTotal; + + private Long activeUserTotal; + + private Long botTotal; + + private Long workflowTotal; + + private Long knowledgeBaseTotal; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Long getValue() { + return value; + } + + public void setValue(Long value) { + this.value = value; + } + + public Long getUserTotal() { + return userTotal; + } + + public void setUserTotal(Long userTotal) { + this.userTotal = userTotal; + } + + public Long getActiveUserTotal() { + return activeUserTotal; + } + + public void setActiveUserTotal(Long activeUserTotal) { + this.activeUserTotal = activeUserTotal; + } + + public Long getBotTotal() { + return botTotal; + } + + public void setBotTotal(Long botTotal) { + this.botTotal = botTotal; + } + + public Long getWorkflowTotal() { + return workflowTotal; + } + + public void setWorkflowTotal(Long workflowTotal) { + this.workflowTotal = workflowTotal; + } + + public Long getKnowledgeBaseTotal() { + return knowledgeBaseTotal; + } + + public void setKnowledgeBaseTotal(Long knowledgeBaseTotal) { + this.knowledgeBaseTotal = knowledgeBaseTotal; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewQuery.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewQuery.java new file mode 100644 index 0000000..6dd2ad8 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewQuery.java @@ -0,0 +1,17 @@ +package tech.easyflow.admin.model.dashboard; + +/** + * 工作台统计查询参数。 + */ +public class DashboardOverviewQuery { + + private String range; + + public String getRange() { + return range; + } + + public void setRange(String range) { + this.range = range; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewVo.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewVo.java new file mode 100644 index 0000000..c9cfa60 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardOverviewVo.java @@ -0,0 +1,60 @@ +package tech.easyflow.admin.model.dashboard; + +import java.util.Date; +import java.util.List; + +/** + * 工作台总览返回对象。 + */ +public class DashboardOverviewVo { + + private DashboardSummaryVo summary; + + private List trends; + + private List distribution; + + private DashboardOverviewQuery query; + + private Date updatedAt; + + public DashboardSummaryVo getSummary() { + return summary; + } + + public void setSummary(DashboardSummaryVo summary) { + this.summary = summary; + } + + public List getTrends() { + return trends; + } + + public void setTrends(List trends) { + this.trends = trends; + } + + public List getDistribution() { + return distribution; + } + + public void setDistribution(List distribution) { + this.distribution = distribution; + } + + public DashboardOverviewQuery getQuery() { + return query; + } + + public void setQuery(DashboardOverviewQuery query) { + this.query = query; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardSummaryVo.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardSummaryVo.java new file mode 100644 index 0000000..52264b7 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardSummaryVo.java @@ -0,0 +1,57 @@ +package tech.easyflow.admin.model.dashboard; + +/** + * 工作台汇总指标。 + */ +public class DashboardSummaryVo { + + private Long userTotal; + + private Long activeUserTotal; + + private Long botTotal; + + private Long workflowTotal; + + private Long knowledgeBaseTotal; + + public Long getUserTotal() { + return userTotal; + } + + public void setUserTotal(Long userTotal) { + this.userTotal = userTotal; + } + + public Long getActiveUserTotal() { + return activeUserTotal; + } + + public void setActiveUserTotal(Long activeUserTotal) { + this.activeUserTotal = activeUserTotal; + } + + public Long getBotTotal() { + return botTotal; + } + + public void setBotTotal(Long botTotal) { + this.botTotal = botTotal; + } + + public Long getWorkflowTotal() { + return workflowTotal; + } + + public void setWorkflowTotal(Long workflowTotal) { + this.workflowTotal = workflowTotal; + } + + public Long getKnowledgeBaseTotal() { + return knowledgeBaseTotal; + } + + public void setKnowledgeBaseTotal(Long knowledgeBaseTotal) { + this.knowledgeBaseTotal = knowledgeBaseTotal; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardTrendItemVo.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardTrendItemVo.java new file mode 100644 index 0000000..27fa840 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/model/dashboard/DashboardTrendItemVo.java @@ -0,0 +1,37 @@ +package tech.easyflow.admin.model.dashboard; + +/** + * 工作台趋势项。 + */ +public class DashboardTrendItemVo { + + private String key; + + private String label; + + private Long activeUserTotal; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Long getActiveUserTotal() { + return activeUserTotal; + } + + public void setActiveUserTotal(Long activeUserTotal) { + this.activeUserTotal = activeUserTotal; + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/DashboardService.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/DashboardService.java new file mode 100644 index 0000000..3f5683f --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/DashboardService.java @@ -0,0 +1,13 @@ +package tech.easyflow.admin.service.dashboard; + +import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery; +import tech.easyflow.admin.model.dashboard.DashboardOverviewVo; +import tech.easyflow.common.entity.LoginAccount; + +/** + * 工作台统计服务。 + */ +public interface DashboardService { + + DashboardOverviewVo getOverview(LoginAccount loginAccount, DashboardOverviewQuery query); +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/impl/DashboardServiceImpl.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/impl/DashboardServiceImpl.java new file mode 100644 index 0000000..3331257 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/service/dashboard/impl/DashboardServiceImpl.java @@ -0,0 +1,307 @@ +package tech.easyflow.admin.service.dashboard.impl; + +import com.easyagents.flow.core.chain.ChainStatus; +import com.mybatisflex.core.query.QueryWrapper; +import com.mybatisflex.core.row.Db; +import com.mybatisflex.core.row.Row; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import tech.easyflow.admin.model.dashboard.DashboardDistributionItemVo; +import tech.easyflow.admin.model.dashboard.DashboardOverviewQuery; +import tech.easyflow.admin.model.dashboard.DashboardOverviewVo; +import tech.easyflow.admin.model.dashboard.DashboardSummaryVo; +import tech.easyflow.admin.model.dashboard.DashboardTrendItemVo; +import tech.easyflow.admin.service.dashboard.DashboardService; +import tech.easyflow.common.constant.Constants; +import tech.easyflow.common.entity.LoginAccount; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.system.entity.SysAccountRole; +import tech.easyflow.system.entity.SysRole; +import tech.easyflow.system.service.SysAccountRoleService; +import tech.easyflow.system.service.SysRoleService; + +import javax.annotation.Resource; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 工作台统计服务实现。 + */ +@Service +public class DashboardServiceImpl implements DashboardService { + + private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault(); + @Resource + private SysAccountRoleService sysAccountRoleService; + @Resource + private SysRoleService sysRoleService; + + @Override + public DashboardOverviewVo getOverview(LoginAccount loginAccount, DashboardOverviewQuery query) { + DashboardQueryContext context = buildContext(loginAccount, query); + + DashboardSummaryVo summary = buildSummary(context); + List trends = buildTrends(context); + List distribution = buildDistribution(context, summary); + + DashboardOverviewVo result = new DashboardOverviewVo(); + result.setSummary(summary); + result.setTrends(trends); + result.setDistribution(distribution); + + DashboardOverviewQuery normalizedQuery = new DashboardOverviewQuery(); + normalizedQuery.setRange(context.range); + result.setQuery(normalizedQuery); + result.setUpdatedAt(new Date()); + return result; + } + + private DashboardSummaryVo buildSummary(DashboardQueryContext context) { + DashboardSummaryVo summary = new DashboardSummaryVo(); + summary.setUserTotal(countScopedTable("tb_sys_account", "a", true, context)); + summary.setActiveUserTotal(countActiveUsers(context)); + summary.setBotTotal(countScopedTable("tb_bot", "b", false, context)); + summary.setWorkflowTotal(countScopedTable("tb_workflow", "w", false, context)); + summary.setKnowledgeBaseTotal(countScopedTable("tb_document_collection", "d", false, context)); + return summary; + } + + private List buildTrends(DashboardQueryContext context) { + List buckets = buildBuckets(context.range); + String bucketFormat = "today".equals(context.range) ? "%Y-%m-%d %H:00:00" : "%Y-%m-%d"; + + Map activeUserMap = queryActiveUserTrend(context, bucketFormat); + + List items = new ArrayList<>(buckets.size()); + for (TimeBucket bucket : buckets) { + long activeUserTotal = activeUserMap.getOrDefault(bucket.key, 0L); + + DashboardTrendItemVo item = new DashboardTrendItemVo(); + item.setKey(bucket.key); + item.setLabel(bucket.label); + item.setActiveUserTotal(activeUserTotal); + items.add(item); + } + return items; + } + + private List buildDistribution(DashboardQueryContext context, DashboardSummaryVo summary) { + return buildResourceDistribution(summary); + } + + private List buildResourceDistribution(DashboardSummaryVo summary) { + List items = new ArrayList<>(); + items.add(buildPlatformItem("userTotal", "用户总量", summary.getUserTotal())); + items.add(buildPlatformItem("activeUserTotal", "活跃用户", summary.getActiveUserTotal())); + items.add(buildPlatformItem("botTotal", "助手数量", summary.getBotTotal())); + items.add(buildPlatformItem("workflowTotal", "工作流数量", summary.getWorkflowTotal())); + items.add(buildPlatformItem("knowledgeBaseTotal", "知识库数量", summary.getKnowledgeBaseTotal())); + return items; + } + + private DashboardDistributionItemVo buildPlatformItem(String key, String label, Long value) { + DashboardDistributionItemVo item = new DashboardDistributionItemVo(); + item.setKey(key); + item.setLabel(label); + item.setValue(defaultLong(value)); + return item; + } + + private Map queryActiveUserTrend(DashboardQueryContext context, String bucketFormat) { + StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); + + sql.append("SELECT DATE_FORMAT(l.created, '").append(bucketFormat).append("') AS bucket_key, ") + .append("COUNT(DISTINCT l.account_id) AS total ") + .append("FROM tb_sys_log l ") + .append("INNER JOIN tb_sys_account a ON a.id = l.account_id AND a.is_deleted IS NULL ") + .append("WHERE l.created >= ? AND l.created < ? "); + params.add(toDate(context.startTime)); + params.add(toDate(context.endTime)); + appendOptionalTenantFilter(sql, params, context.tenantFilterId, "a.tenant_id"); + appendOptionalDeptFilter(sql, params, context.deptFilterId, "a.dept_id"); + sql.append("GROUP BY bucket_key ORDER BY bucket_key ASC"); + + Map data = new HashMap<>(); + for (Row row : Db.selectListBySql(sql.toString(), params.toArray())) { + data.put(asString(row.get("bucket_key")), asLong(row.get("total"))); + } + return data; + } + + private long countScopedTable(String tableName, String alias, boolean containsLogicDelete, DashboardQueryContext context) { + StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); + + sql.append("SELECT COUNT(*) AS total FROM ").append(tableName).append(" ").append(alias).append(" WHERE 1 = 1 "); + if (containsLogicDelete) { + sql.append("AND ").append(alias).append(".is_deleted IS NULL "); + } + appendOptionalTenantFilter(sql, params, context.tenantFilterId, alias + ".tenant_id"); + appendOptionalDeptFilter(sql, params, context.deptFilterId, alias + ".dept_id"); + return queryForLong(sql.toString(), params); + } + + private long countActiveUsers(DashboardQueryContext context) { + StringBuilder sql = new StringBuilder(); + List params = new ArrayList<>(); + + sql.append("SELECT COUNT(DISTINCT l.account_id) AS total ") + .append("FROM tb_sys_log l ") + .append("INNER JOIN tb_sys_account a ON a.id = l.account_id AND a.is_deleted IS NULL ") + .append("WHERE l.created >= ? AND l.created < ? "); + params.add(toDate(context.startTime)); + params.add(toDate(context.endTime)); + appendOptionalTenantFilter(sql, params, context.tenantFilterId, "a.tenant_id"); + appendOptionalDeptFilter(sql, params, context.deptFilterId, "a.dept_id"); + return queryForLong(sql.toString(), params); + } + + private long queryForLong(String sql, List params) { + Object result = Db.selectObject(sql, params.toArray()); + return asLong(result); + } + + private void appendOptionalTenantFilter(StringBuilder sql, List params, BigInteger tenantId, String columnName) { + if (tenantId != null) { + sql.append(" AND ").append(columnName).append(" = ? "); + params.add(tenantId); + } + } + + private void appendOptionalDeptFilter(StringBuilder sql, List params, BigInteger deptId, String columnName) { + if (deptId != null) { + sql.append(" AND ").append(columnName).append(" = ? "); + params.add(deptId); + } + } + + private DashboardQueryContext buildContext(LoginAccount loginAccount, DashboardOverviewQuery query) { + DashboardQueryContext context = new DashboardQueryContext(); + context.range = normalizeRange(query == null ? null : query.getRange()); + context.superAdmin = isSuperAdmin(loginAccount); + + LocalDate today = LocalDate.now(DEFAULT_ZONE_ID); + if ("today".equals(context.range)) { + context.startTime = LocalDateTime.of(today, LocalTime.MIN); + context.endTime = context.startTime.plusDays(1); + } else if ("7d".equals(context.range)) { + context.startTime = LocalDateTime.of(today.minusDays(6), LocalTime.MIN); + context.endTime = LocalDateTime.of(today.plusDays(1), LocalTime.MIN); + } else { + context.startTime = LocalDateTime.of(today.minusDays(29), LocalTime.MIN); + context.endTime = LocalDateTime.of(today.plusDays(1), LocalTime.MIN); + } + + context.tenantFilterId = context.superAdmin ? null : loginAccount.getTenantId(); + + return context; + } + + private boolean isSuperAdmin(LoginAccount loginAccount) { + if (loginAccount == null || loginAccount.getId() == null) { + return false; + } + QueryWrapper roleMappingWrapper = QueryWrapper.create(); + roleMappingWrapper.eq(SysAccountRole::getAccountId, loginAccount.getId()); + List roleIds = sysAccountRoleService.list(roleMappingWrapper) + .stream() + .map(SysAccountRole::getRoleId) + .collect(Collectors.toList()); + if (roleIds.isEmpty()) { + return Constants.SUPER_ADMIN_ID.equals(loginAccount.getId()); + } + + QueryWrapper roleWrapper = QueryWrapper.create(); + roleWrapper.in(SysRole::getId, roleIds); + roleWrapper.eq(SysRole::getRoleKey, Constants.SUPER_ADMIN_ROLE_CODE); + return sysRoleService.count(roleWrapper) > 0; + } + + private String normalizeRange(String range) { + if (!StringUtils.hasText(range)) { + return "7d"; + } + if ("today".equals(range) || "7d".equals(range) || "30d".equals(range)) { + return range; + } + throw new BusinessException("不支持的时间范围: " + range); + } + + private List buildBuckets(String range) { + List buckets = new ArrayList<>(); + LocalDate today = LocalDate.now(DEFAULT_ZONE_ID); + + if ("today".equals(range)) { + DateTimeFormatter keyFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00"); + DateTimeFormatter labelFormatter = DateTimeFormatter.ofPattern("HH:00"); + LocalDateTime start = LocalDateTime.of(today, LocalTime.MIN); + for (int hour = 0; hour < 24; hour++) { + LocalDateTime current = start.plusHours(hour); + buckets.add(new TimeBucket(current.format(keyFormatter), current.format(labelFormatter))); + } + return buckets; + } + + int days = "7d".equals(range) ? 7 : 30; + DateTimeFormatter keyFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter labelFormatter = DateTimeFormatter.ofPattern("MM-dd"); + LocalDate start = today.minusDays(days - 1L); + for (int i = 0; i < days; i++) { + LocalDate current = start.plusDays(i); + buckets.add(new TimeBucket(current.format(keyFormatter), current.format(labelFormatter))); + } + return buckets; + } + + private Date toDate(LocalDateTime dateTime) { + return Date.from(dateTime.atZone(DEFAULT_ZONE_ID).toInstant()); + } + + private long defaultLong(Long value) { + return value == null ? 0L : value; + } + + private String asString(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private long asLong(Object value) { + if (value == null) { + return 0L; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return Long.parseLong(String.valueOf(value)); + } + + private static class DashboardQueryContext { + private String range; + private BigInteger tenantFilterId; + private BigInteger deptFilterId; + private boolean superAdmin; + private LocalDateTime startTime; + private LocalDateTime endTime; + } + + private static class TimeBucket { + private final String key; + private final String label; + + private TimeBucket(String key, String label) { + this.key = key; + this.label = label; + } + } +} diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V5__dashboard_workspace_menu.sql b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V5__dashboard_workspace_menu.sql new file mode 100644 index 0000000..a087c06 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V5__dashboard_workspace_menu.sql @@ -0,0 +1,39 @@ +SET NAMES utf8mb4; + +INSERT INTO `tb_sys_menu` ( + `id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, + `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark` +) VALUES ( + 366200000000000001, 0, 0, 'menus.dashboard.workspace', '/dashboard/workspace', '/dashboard/workspace/index', + 'carbon:workspace', 1, '', 1, 0, '2026-03-24 10:00:00', 1, '2026-03-24 10:00:00', 1, '管理员工作台' +); + +INSERT INTO `tb_sys_menu` ( + `id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, + `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark` +) VALUES ( + 366200000000000002, 366200000000000001, 1, '查询', '', '', '', + 0, '/api/v1/dashboard/query', 1, 0, '2026-03-24 10:00:00', 1, '2026-03-24 10:00:00', 1, '工作台统计查询权限' +); + +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) +SELECT + CASE r.role_key + WHEN 'super_admin' THEN 366200000000000101 + WHEN 'tenant_admin' THEN 366200000000000102 + END AS `id`, + r.id AS `role_id`, + 366200000000000001 AS `menu_id` +FROM `tb_sys_role` r +WHERE r.role_key IN ('super_admin', 'tenant_admin'); + +INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) +SELECT + CASE r.role_key + WHEN 'super_admin' THEN 366200000000000103 + WHEN 'tenant_admin' THEN 366200000000000104 + END AS `id`, + r.id AS `role_id`, + 366200000000000002 AS `menu_id` +FROM `tb_sys_role` r +WHERE r.role_key IN ('super_admin', 'tenant_admin'); diff --git a/easyflow-ui-admin/app/src/api/dashboard.ts b/easyflow-ui-admin/app/src/api/dashboard.ts new file mode 100644 index 0000000..9b9adff --- /dev/null +++ b/easyflow-ui-admin/app/src/api/dashboard.ts @@ -0,0 +1,49 @@ +import { requestClient } from '#/api/request'; + +export type DashboardRange = '7d' | '30d' | 'today'; + +export interface DashboardOverviewQuery { + range?: DashboardRange; +} + +export interface DashboardSummary { + activeUserTotal: number; + botTotal: number; + knowledgeBaseTotal: number; + userTotal: number; + workflowTotal: number; +} + +export interface DashboardTrendItem { + activeUserTotal: number; + key: string; + label: string; +} + +export interface DashboardDistributionItem { + activeUserTotal: number; + botTotal: number; + key: string; + knowledgeBaseTotal: number; + label: string; + userTotal: number; + value: number; + workflowTotal: number; +} + +export interface DashboardOverviewResponse { + distribution: DashboardDistributionItem[]; + query: DashboardOverviewQuery; + summary: DashboardSummary; + trends: DashboardTrendItem[]; + updatedAt: string; +} + +export async function getDashboardOverview(params: DashboardOverviewQuery) { + return requestClient.get( + '/api/v1/dashboard/overview', + { + params, + }, + ); +} diff --git a/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json b/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json index 342b95d..32dded5 100644 --- a/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json +++ b/easyflow-ui-admin/app/src/locales/langs/en-US/menus.json @@ -1,4 +1,7 @@ { + "dashboard": { + "workspace": "Workspace" + }, "system": { "title": "System", "sysAccount": "Account", diff --git a/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json b/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json index 877cd7b..42dbcd2 100644 --- a/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json +++ b/easyflow-ui-admin/app/src/locales/langs/zh-CN/menus.json @@ -1,4 +1,7 @@ { + "dashboard": { + "workspace": "工作台" + }, "system": { "title": "系统管理", "sysAccount": "用户管理", diff --git a/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue b/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue index b05f22c..7f5dc82 100644 --- a/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue +++ b/easyflow-ui-admin/app/src/views/dashboard/workspace/index.vue @@ -1,266 +1,326 @@ diff --git a/easyflow-ui-admin/packages/@core/preferences/src/config.ts b/easyflow-ui-admin/packages/@core/preferences/src/config.ts index 3596b47..278f63d 100644 --- a/easyflow-ui-admin/packages/@core/preferences/src/config.ts +++ b/easyflow-ui-admin/packages/@core/preferences/src/config.ts @@ -19,7 +19,7 @@ const defaultPreferences: Preferences = { contentPaddingTop: 0, defaultAvatar: 'https://unpkg.com/@easyflow/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/ai/bots', + defaultHomePath: '/dashboard/workspace', dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true,