Register admin chart business blocks
This commit is contained in:
@@ -19,6 +19,7 @@ import com.ruoyi.generator.domain.front.dto.block.BusinessBlockConfigField;
|
||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
|
||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
|
||||
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -32,6 +33,9 @@ public class BusinessBlockRegistryService
|
||||
|
||||
private volatile List<BusinessBlockDefinition> cachedDefinitions;
|
||||
|
||||
@Autowired
|
||||
private ChartDatasetValidator chartDatasetValidator;
|
||||
|
||||
public List<BusinessBlockDefinition> listDefinitions()
|
||||
{
|
||||
if (cachedDefinitions == null)
|
||||
@@ -92,6 +96,11 @@ public class BusinessBlockRegistryService
|
||||
}
|
||||
|
||||
public void validateLayout(String layoutJson, List<GenTable> tables)
|
||||
{
|
||||
validateLayout(layoutJson, tables, "frontend");
|
||||
}
|
||||
|
||||
public void validateLayout(String layoutJson, List<GenTable> tables, String surface)
|
||||
{
|
||||
PageBusinessBlockLayout layout = parseLayout(layoutJson);
|
||||
if (!BUSINESS_BLOCK_CANVAS.equals(layout.getCanvas()))
|
||||
@@ -105,7 +114,7 @@ public class BusinessBlockRegistryService
|
||||
continue;
|
||||
}
|
||||
BusinessBlockDefinition definition = findDefinition(instance.getBlockCode());
|
||||
validateInstanceConfig(definition, instance, tables);
|
||||
validateInstanceConfig(definition, instance, tables, surface);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +156,11 @@ public class BusinessBlockRegistryService
|
||||
}
|
||||
|
||||
public void validateEmbeddedBlocks(String layoutJson, List<GenTable> tables)
|
||||
{
|
||||
validateEmbeddedBlocks(layoutJson, tables, "frontend");
|
||||
}
|
||||
|
||||
public void validateEmbeddedBlocks(String layoutJson, List<GenTable> tables, String surface)
|
||||
{
|
||||
Map<String, List<BusinessBlockInstance>> zones = parseEmbeddedBlockZones(layoutJson);
|
||||
for (List<BusinessBlockInstance> blocks : zones.values())
|
||||
@@ -166,7 +180,7 @@ public class BusinessBlockRegistryService
|
||||
{
|
||||
throw new ServiceException("业务块只能放在业务块页面:" + instance.getBlockCode());
|
||||
}
|
||||
validateInstanceConfig(definition, instance, tables);
|
||||
validateInstanceConfig(definition, instance, tables, surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,6 +232,37 @@ public class BusinessBlockRegistryService
|
||||
}
|
||||
}
|
||||
|
||||
public void validateInstanceConfig(BusinessBlockDefinition definition, BusinessBlockInstance instance,
|
||||
List<GenTable> tables, String surface)
|
||||
{
|
||||
String normalizedSurface = "admin".equals(surface) ? "admin" : "frontend";
|
||||
if (definition == null || definition.getSurfaces() == null
|
||||
|| !definition.getSurfaces().contains(normalizedSurface))
|
||||
{
|
||||
String name = definition == null
|
||||
? StringUtils.defaultString(instance == null ? null : instance.getBlockCode())
|
||||
: StringUtils.defaultIfEmpty(definition.getName(), definition.getCode());
|
||||
String allowedSurface = definition != null && definition.getSurfaces() != null
|
||||
&& definition.getSurfaces().contains("admin") ? "后台" : "前台";
|
||||
throw new ServiceException("业务块“" + name + "”仅支持" + allowedSurface + "页面");
|
||||
}
|
||||
if ("chart".equals(definition.getKind()))
|
||||
{
|
||||
chartValidator().validate(definition, instance, tables);
|
||||
return;
|
||||
}
|
||||
validateInstanceConfig(definition, instance, tables);
|
||||
}
|
||||
|
||||
private ChartDatasetValidator chartValidator()
|
||||
{
|
||||
if (chartDatasetValidator == null)
|
||||
{
|
||||
chartDatasetValidator = new ChartDatasetValidator();
|
||||
}
|
||||
return chartDatasetValidator;
|
||||
}
|
||||
|
||||
private List<BusinessBlockDefinition> loadDefinitions()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -22,7 +22,9 @@ import com.ruoyi.generator.domain.front.dto.block.ChartDimensionConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartFilterConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartMetricConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartSortConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ChartDatasetValidator
|
||||
{
|
||||
public static final String DATASET_SCHEMA = "single-table-aggregate-v1";
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
@@ -36,8 +37,13 @@ import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;
|
||||
public class FrontendPageDesignService
|
||||
{
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final String MENU_DIRECTORY_PAGE_TYPE = "menu_directory";
|
||||
private static final String HOME_PAGE_TYPE = "business_block";
|
||||
private static final String HOME_PAGE_NAME = "\u4e3b\u9875";
|
||||
private static final String FRONTEND_HOME_PAGE_CODE = "home";
|
||||
private static final String ADMIN_HOME_PAGE_CODE = "admin_home";
|
||||
private static final Set<String> PAGE_TYPES = new HashSet<String>(
|
||||
Arrays.asList("list", "detail", "form", "current_user_list", "business_block"));
|
||||
Arrays.asList("list", "detail", "form", "current_user_list", "business_block", MENU_DIRECTORY_PAGE_TYPE));
|
||||
private static final Set<String> REGION_TYPES = new HashSet<String>(
|
||||
Arrays.asList("query", "toolbar", "table", "dialogForm", "detail", "form", "cardList", "actions"));
|
||||
private static final Set<String> BUILTIN_ACTIONS = new HashSet<String>(
|
||||
@@ -65,7 +71,8 @@ public class FrontendPageDesignService
|
||||
public List<FrontendPageDesignResponse> listPageDesigns(Long userId, Long projectId, String scope)
|
||||
{
|
||||
frontProjectService.getProject(userId, projectId);
|
||||
return toResponses(pageDesignMapper.selectDesignsByProjectIdAndScope(projectId, normalizeScope(scope)));
|
||||
String normalizedScope = normalizeScope(scope);
|
||||
return toScopedResponses(pageDesignMapper.selectDesignsByProjectIdAndScope(projectId, normalizedScope), normalizedScope);
|
||||
}
|
||||
|
||||
public FrontendPageDesignResponse getPageDesign(Long userId, Long projectId, Long designId)
|
||||
@@ -109,7 +116,7 @@ public class FrontendPageDesignService
|
||||
Map<String, FrontProjectPageDesign> existingByPageCode = new LinkedHashMap<String, FrontProjectPageDesign>();
|
||||
for (FrontProjectPageDesign design : existing)
|
||||
{
|
||||
if (StringUtils.isNotEmpty(design.getPageCode()))
|
||||
if (shouldExposeDesign(design, FRONTEND_SCOPE) && StringUtils.isNotEmpty(design.getPageCode()))
|
||||
{
|
||||
existingByPageCode.put(design.getPageCode(), design);
|
||||
}
|
||||
@@ -118,13 +125,20 @@ public class FrontendPageDesignService
|
||||
Map<String, TableBundle> tablesByName = loadTables(projectId);
|
||||
List<FrontProjectPageDesign> merged = new ArrayList<FrontProjectPageDesign>();
|
||||
Set<String> blueprintPageCodes = new LinkedHashSet<String>();
|
||||
FrontProjectPageDesign homeDesign = getOrCreateHomeDesign(projectId, userId, FRONTEND_SCOPE, existingByPageCode);
|
||||
merged.add(homeDesign);
|
||||
blueprintPageCodes.add(homeDesign.getPageCode());
|
||||
for (AppPageDesign page : blueprint.getFrontendPages())
|
||||
{
|
||||
if (page == null || StringUtils.isEmpty(page.getCode()))
|
||||
if (page == null || StringUtils.isEmpty(page.getCode()) || isAdminLikePage(page))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
blueprintPageCodes.add(page.getCode());
|
||||
if (isHomePageCode(page.getCode(), FRONTEND_SCOPE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FrontProjectPageDesign design = existingByPageCode.get(page.getCode());
|
||||
if (design == null)
|
||||
{
|
||||
@@ -137,7 +151,7 @@ public class FrontendPageDesignService
|
||||
List<FrontendPageDesignResponse> detached = new ArrayList<FrontendPageDesignResponse>();
|
||||
for (FrontProjectPageDesign design : existing)
|
||||
{
|
||||
if (!blueprintPageCodes.contains(design.getPageCode()))
|
||||
if (shouldExposeDesign(design, FRONTEND_SCOPE) && !blueprintPageCodes.contains(design.getPageCode()))
|
||||
{
|
||||
detached.add(FrontendPageDesignResponse.from(design));
|
||||
}
|
||||
@@ -172,6 +186,9 @@ public class FrontendPageDesignService
|
||||
Map<String, TableBundle> unmatchedTables = new LinkedHashMap<String, TableBundle>(tablesByName);
|
||||
List<FrontProjectPageDesign> merged = new ArrayList<FrontProjectPageDesign>();
|
||||
Set<String> expectedPageCodes = new LinkedHashSet<String>();
|
||||
FrontProjectPageDesign homeDesign = getOrCreateHomeDesign(projectId, userId, ADMIN_SCOPE, existingByPageCode);
|
||||
merged.add(homeDesign);
|
||||
expectedPageCodes.add(homeDesign.getPageCode());
|
||||
if (blueprint.getAdminMenus() != null)
|
||||
{
|
||||
for (AppMenuDesign menu : blueprint.getAdminMenus())
|
||||
@@ -180,6 +197,11 @@ public class FrontendPageDesignService
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (isHomePageCode(menu.getCode(), ADMIN_SCOPE))
|
||||
{
|
||||
expectedPageCodes.add(menu.getCode());
|
||||
continue;
|
||||
}
|
||||
TableBundle table = findAdminMenuTable(menu, unmatchedTables);
|
||||
if (table == null)
|
||||
{
|
||||
@@ -253,6 +275,7 @@ public class FrontendPageDesignService
|
||||
create.setRoutePath(request.getRoutePath());
|
||||
create.setPageType(defaultText(request.getPageType(), "list"));
|
||||
create.setTableName(request.getTableName());
|
||||
normalizeMenuDirectoryMeta(create);
|
||||
create.setStatus(defaultText(request.getStatus(), "0"));
|
||||
create.setRemark(request.getRemark());
|
||||
create.setVersion(1);
|
||||
@@ -272,7 +295,8 @@ public class FrontendPageDesignService
|
||||
}
|
||||
String layoutJson = defaultText(request.getLayoutJson(),
|
||||
defaultLayoutJson(create.getPageType(), table, normalizedScope));
|
||||
String actionJson = defaultText(request.getActionJson(), defaultActionJson(table, normalizedScope));
|
||||
String actionJson = defaultText(request.getActionJson(),
|
||||
isMenuDirectory(create.getPageType()) ? "{}" : defaultActionJson(table, normalizedScope));
|
||||
create.setLayoutJson(normalizeLayout(layoutJson, table, create.getPageType(), create.getPageScope(), tablesByName));
|
||||
create.setActionJson(normalizeAction(actionJson, table, create.getPageScope()));
|
||||
pageDesignMapper.insertFrontProjectPageDesign(create);
|
||||
@@ -315,6 +339,7 @@ public class FrontendPageDesignService
|
||||
update.setRoutePath(defaultText(request.getRoutePath(), current.getRoutePath()));
|
||||
update.setPageType(defaultText(request.getPageType(), current.getPageType()));
|
||||
update.setTableName(defaultText(request.getTableName(), current.getTableName()));
|
||||
normalizeMenuDirectoryMeta(update);
|
||||
update.setStatus(defaultText(request.getStatus(), current.getStatus()));
|
||||
update.setRemark(defaultText(request.getRemark(), current.getRemark()));
|
||||
update.setVersion(currentVersion + 1);
|
||||
@@ -385,15 +410,83 @@ public class FrontendPageDesignService
|
||||
design.setMenuCode(page.getMenuCode());
|
||||
design.setPageName(defaultText(page.getName(), page.getCode()));
|
||||
design.setRoutePath(defaultText(page.getPath(), "/" + page.getCode()));
|
||||
design.setPageType(defaultText(page.getPageType(), "list"));
|
||||
String pageType = defaultText(page.getPageType(), "list");
|
||||
design.setPageType(pageType);
|
||||
design.setTableName(page.getTableName());
|
||||
design.setLayoutJson(defaultLayoutJson(defaultText(page.getPageType(), "list"), table, scope));
|
||||
design.setLayoutJson(defaultLayoutJson(pageType, table, scope, page));
|
||||
design.setActionJson(defaultActionJson(table, scope));
|
||||
design.setStatus("0");
|
||||
design.setVersion(1);
|
||||
return design;
|
||||
}
|
||||
|
||||
private FrontProjectPageDesign getOrCreateHomeDesign(Long projectId, Long userId, String scope,
|
||||
Map<String, FrontProjectPageDesign> existingByPageCode)
|
||||
{
|
||||
String pageCode = homePageCode(scope);
|
||||
FrontProjectPageDesign design = existingByPageCode.get(pageCode);
|
||||
if (design != null)
|
||||
{
|
||||
return design;
|
||||
}
|
||||
design = newDefaultHomeDesign(projectId, userId, scope);
|
||||
pageDesignMapper.insertFrontProjectPageDesign(design);
|
||||
existingByPageCode.put(pageCode, design);
|
||||
return design;
|
||||
}
|
||||
|
||||
private FrontProjectPageDesign newDefaultHomeDesign(Long projectId, Long userId, String scope)
|
||||
{
|
||||
String normalizedScope = normalizeScope(scope);
|
||||
String pageCode = homePageCode(normalizedScope);
|
||||
FrontProjectPageDesign design = new FrontProjectPageDesign();
|
||||
design.setProjectId(projectId);
|
||||
design.setUserId(userId);
|
||||
design.setPageScope(normalizedScope);
|
||||
design.setPageCode(pageCode);
|
||||
design.setMenuCode(pageCode);
|
||||
design.setPageName(HOME_PAGE_NAME);
|
||||
design.setRoutePath(homeRoutePath(normalizedScope));
|
||||
design.setPageType(HOME_PAGE_TYPE);
|
||||
design.setTableName("");
|
||||
design.setLayoutJson(defaultHomeLayoutJson(normalizedScope, pageCode));
|
||||
design.setActionJson(emptyActionJson());
|
||||
design.setStatus("0");
|
||||
design.setVersion(1);
|
||||
return design;
|
||||
}
|
||||
|
||||
private String defaultHomeLayoutJson(String scope, String pageCode)
|
||||
{
|
||||
Map<String, Object> layout = new LinkedHashMap<String, Object>();
|
||||
layout.put("canvas", BusinessBlockRegistryService.BUSINESS_BLOCK_CANVAS);
|
||||
layout.put("blocks", new ArrayList<Object>());
|
||||
layout.put("navigation", defaultHomeNavigation(pageCode));
|
||||
return writeJson(layout);
|
||||
}
|
||||
|
||||
private Map<String, Object> defaultHomeNavigation(String pageCode)
|
||||
{
|
||||
Map<String, Object> navigation = new LinkedHashMap<String, Object>();
|
||||
navigation.put("source", "pageDesigns");
|
||||
navigation.put("position", "top");
|
||||
navigation.put("display", "dropdown");
|
||||
navigation.put("showInMenu", true);
|
||||
navigation.put("menuLevel", "primary");
|
||||
navigation.put("menuCode", pageCode);
|
||||
navigation.put("menuName", HOME_PAGE_NAME);
|
||||
navigation.put("menuOrder", Integer.valueOf(1));
|
||||
return navigation;
|
||||
}
|
||||
|
||||
private String emptyActionJson()
|
||||
{
|
||||
Map<String, Object> action = new LinkedHashMap<String, Object>();
|
||||
action.put("toolbarActions", new ArrayList<Object>());
|
||||
action.put("rowActions", new ArrayList<Object>());
|
||||
return writeJson(action);
|
||||
}
|
||||
|
||||
private AppPageDesign adminPageFromMenu(AppMenuDesign menu, FrontProjectTable table)
|
||||
{
|
||||
AppPageDesign page = new AppPageDesign();
|
||||
@@ -466,7 +559,8 @@ public class FrontendPageDesignService
|
||||
{
|
||||
throw new ServiceException("页面名称不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(design.getRoutePath()))
|
||||
boolean menuDirectory = isMenuDirectory(design.getPageType());
|
||||
if (StringUtils.isEmpty(design.getRoutePath()) && !menuDirectory)
|
||||
{
|
||||
throw new ServiceException("页面路由不能为空");
|
||||
}
|
||||
@@ -474,12 +568,23 @@ public class FrontendPageDesignService
|
||||
{
|
||||
throw new ServiceException("页面类型不支持: " + design.getPageType());
|
||||
}
|
||||
if (StringUtils.isEmpty(design.getTableName()) && !"business_block".equals(design.getPageType()))
|
||||
if (StringUtils.isEmpty(design.getTableName()) && !"business_block".equals(design.getPageType()) && !menuDirectory)
|
||||
{
|
||||
throw new ServiceException("页面必须绑定数据表");
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeMenuDirectoryMeta(FrontProjectPageDesign design)
|
||||
{
|
||||
if (!isMenuDirectory(design.getPageType()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
design.setRoutePath("");
|
||||
design.setTableName("");
|
||||
design.setMenuCode(defaultText(design.getMenuCode(), design.getPageCode()));
|
||||
}
|
||||
|
||||
private Map<String, TableBundle> loadTables(Long projectId)
|
||||
{
|
||||
Map<String, TableBundle> result = new LinkedHashMap<String, TableBundle>();
|
||||
@@ -507,6 +612,13 @@ public class FrontendPageDesignService
|
||||
|
||||
private String defaultLayoutJson(String pageType, TableBundle table, String scope)
|
||||
{
|
||||
if (isMenuDirectory(pageType))
|
||||
{
|
||||
Map<String, Object> layout = new LinkedHashMap<String, Object>();
|
||||
layout.put("canvas", "menu-directory-v1");
|
||||
layout.put("navigation", defaultMenuDirectoryNavigation(null, null));
|
||||
return writeJson(layout);
|
||||
}
|
||||
if ("business_block".equals(pageType))
|
||||
{
|
||||
Map<String, Object> businessLayout = new LinkedHashMap<String, Object>();
|
||||
@@ -525,6 +637,39 @@ public class FrontendPageDesignService
|
||||
return writeJson(layout);
|
||||
}
|
||||
|
||||
private String defaultLayoutJson(String pageType, TableBundle table, String scope, AppPageDesign page)
|
||||
{
|
||||
Map<String, Object> layout = readMap(defaultLayoutJson(pageType, table, scope), "椤甸潰甯冨眬JSON鏍煎紡閿欒");
|
||||
applyAssociatedPageNavigationDefaults(layout, pageType, page == null ? null : page.getParentPageCode());
|
||||
return writeJson(layout);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void applyAssociatedPageNavigationDefaults(Map<String, Object> layout, String pageType, String parentPageCode)
|
||||
{
|
||||
if (layout == null || isMenuDirectory(pageType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!isAssociatedPageType(pageType) && StringUtils.isEmpty(parentPageCode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Object navigationObject = layout.get("navigation");
|
||||
Map<String, Object> navigation = navigationObject instanceof Map
|
||||
? new LinkedHashMap<String, Object>((Map<String, Object>) navigationObject)
|
||||
: new LinkedHashMap<String, Object>();
|
||||
if (isAssociatedPageType(pageType))
|
||||
{
|
||||
navigation.put("showInMenu", false);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(parentPageCode))
|
||||
{
|
||||
navigation.put("parentPageCode", parentPageCode);
|
||||
}
|
||||
layout.put("navigation", navigation);
|
||||
}
|
||||
|
||||
private Map<String, Object> region(String id, String type, List<String> fields,
|
||||
List<String> actions, List<String> rowActions)
|
||||
{
|
||||
@@ -662,6 +807,10 @@ public class FrontendPageDesignService
|
||||
private String normalizeLayout(String layoutJson, TableBundle table, String pageType, String scope,
|
||||
Map<String, TableBundle> tablesByName)
|
||||
{
|
||||
if (isMenuDirectory(pageType))
|
||||
{
|
||||
return normalizeMenuDirectoryLayout(layoutJson);
|
||||
}
|
||||
if (StringUtils.isEmpty(layoutJson))
|
||||
{
|
||||
return defaultLayoutJson(pageType, table, scope);
|
||||
@@ -676,7 +825,7 @@ public class FrontendPageDesignService
|
||||
if (BusinessBlockRegistryService.BUSINESS_BLOCK_CANVAS.equals(canvas))
|
||||
{
|
||||
String normalizedLayout = writeJson(layout);
|
||||
businessBlockRegistryService.validateLayout(normalizedLayout, toGenTables(tablesByName));
|
||||
businessBlockRegistryService.validateLayout(normalizedLayout, toGenTables(tablesByName), scope);
|
||||
return normalizedLayout;
|
||||
}
|
||||
Object regionsObject = layout.get("regions");
|
||||
@@ -709,10 +858,52 @@ public class FrontendPageDesignService
|
||||
normalizeActionCodeList(region, "actions", table, scope);
|
||||
normalizeActionCodeList(region, "rowActions", table, scope);
|
||||
}
|
||||
businessBlockRegistryService.validateEmbeddedBlocks(writeJson(layout), toGenTables(tablesByName));
|
||||
businessBlockRegistryService.validateEmbeddedBlocks(writeJson(layout), toGenTables(tablesByName), scope);
|
||||
return writeJson(layout);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String normalizeMenuDirectoryLayout(String layoutJson)
|
||||
{
|
||||
Map<String, Object> layout = StringUtils.isEmpty(layoutJson)
|
||||
? new LinkedHashMap<String, Object>()
|
||||
: readMap(layoutJson, "页面布局JSON格式错误");
|
||||
Object navigationObject = layout.get("navigation");
|
||||
Map<String, Object> navigation = navigationObject instanceof Map
|
||||
? new LinkedHashMap<String, Object>((Map<String, Object>) navigationObject)
|
||||
: new LinkedHashMap<String, Object>();
|
||||
Map<String, Object> defaults = defaultMenuDirectoryNavigation(
|
||||
stringValue(navigation.get("menuCode")),
|
||||
stringValue(navigation.get("menuName")));
|
||||
defaults.putAll(navigation);
|
||||
defaults.put("showInMenu", true);
|
||||
defaults.put("menuLevel", "primary");
|
||||
defaults.put("nodeType", "directory");
|
||||
layout.put("canvas", "menu-directory-v1");
|
||||
layout.put("navigation", defaults);
|
||||
return writeJson(layout);
|
||||
}
|
||||
|
||||
private Map<String, Object> defaultMenuDirectoryNavigation(String menuCode, String menuName)
|
||||
{
|
||||
Map<String, Object> navigation = new LinkedHashMap<String, Object>();
|
||||
navigation.put("source", "pageDesigns");
|
||||
navigation.put("position", "top");
|
||||
navigation.put("display", "dropdown");
|
||||
navigation.put("showInMenu", true);
|
||||
navigation.put("menuLevel", "primary");
|
||||
navigation.put("nodeType", "directory");
|
||||
if (StringUtils.isNotEmpty(menuCode))
|
||||
{
|
||||
navigation.put("menuCode", menuCode);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(menuName))
|
||||
{
|
||||
navigation.put("menuName", menuName);
|
||||
}
|
||||
return navigation;
|
||||
}
|
||||
|
||||
private void normalizeSingleField(Map<String, Object> region, String key, Map<String, String> aliases)
|
||||
{
|
||||
Object value = region.get(key);
|
||||
@@ -752,11 +943,16 @@ public class FrontendPageDesignService
|
||||
"admin-form-group-v1")));
|
||||
result.put("business_block", new LinkedHashSet<String>(Arrays.asList(
|
||||
BusinessBlockRegistryService.BUSINESS_BLOCK_CANVAS)));
|
||||
result.put(MENU_DIRECTORY_PAGE_TYPE, new LinkedHashSet<String>(Arrays.asList("menu-directory-v1")));
|
||||
return result;
|
||||
}
|
||||
|
||||
private String defaultLayoutCanvas(String pageType, String scope)
|
||||
{
|
||||
if (isMenuDirectory(pageType))
|
||||
{
|
||||
return "menu-directory-v1";
|
||||
}
|
||||
if (ADMIN_SCOPE.equals(scope))
|
||||
{
|
||||
if ("detail".equals(pageType))
|
||||
@@ -779,6 +975,14 @@ public class FrontendPageDesignService
|
||||
|
||||
private boolean layoutCanvasAllowed(String pageType, String canvas, String scope)
|
||||
{
|
||||
if (isMenuDirectory(pageType))
|
||||
{
|
||||
return "menu-directory-v1".equals(canvas);
|
||||
}
|
||||
if ("business_block".equals(pageType))
|
||||
{
|
||||
return BusinessBlockRegistryService.BUSINESS_BLOCK_CANVAS.equals(canvas);
|
||||
}
|
||||
if (ADMIN_SCOPE.equals(scope) && canvas != null && !canvas.startsWith("admin-"))
|
||||
{
|
||||
return false;
|
||||
@@ -966,6 +1170,8 @@ public class FrontendPageDesignService
|
||||
column.setColumnName(source.getColumnName());
|
||||
column.setJavaField(source.getJavaField());
|
||||
column.setColumnComment(source.getColumnComment());
|
||||
column.setJavaType(source.getJavaType());
|
||||
column.setColumnType(source.getColumnType());
|
||||
columns.add(column);
|
||||
}
|
||||
table.setColumns(columns);
|
||||
@@ -1065,6 +1271,67 @@ public class FrontendPageDesignService
|
||||
return responses;
|
||||
}
|
||||
|
||||
private List<FrontendPageDesignResponse> toScopedResponses(List<FrontProjectPageDesign> designs, String scope)
|
||||
{
|
||||
List<FrontendPageDesignResponse> responses = new ArrayList<FrontendPageDesignResponse>();
|
||||
for (FrontProjectPageDesign design : safeDesigns(designs))
|
||||
{
|
||||
if (shouldExposeDesign(design, scope))
|
||||
{
|
||||
responses.add(FrontendPageDesignResponse.from(design));
|
||||
}
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
private boolean shouldExposeDesign(FrontProjectPageDesign design, String scope)
|
||||
{
|
||||
if (design == null || !scopeMatches(design.getPageScope(), scope))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return !FRONTEND_SCOPE.equals(normalizeScope(scope)) || !isAdminLikeDesign(design);
|
||||
}
|
||||
|
||||
private boolean isAdminLikeDesign(FrontProjectPageDesign design)
|
||||
{
|
||||
return isAdminRoutePath(design.getRoutePath()) || layoutCanvasStartsWith(design.getLayoutJson(), "admin-");
|
||||
}
|
||||
|
||||
private boolean isAdminLikePage(AppPageDesign page)
|
||||
{
|
||||
return page != null && isAdminRoutePath(page.getPath());
|
||||
}
|
||||
|
||||
private boolean layoutCanvasStartsWith(String layoutJson, String prefix)
|
||||
{
|
||||
if (StringUtils.isEmpty(layoutJson))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
Map<String, Object> layout = OBJECT_MAPPER.readValue(layoutJson, new TypeReference<Map<String, Object>>() { });
|
||||
String canvas = stringValue(layout.get("canvas"));
|
||||
return canvas != null && canvas.startsWith(prefix);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdminRoutePath(String routePath)
|
||||
{
|
||||
if (StringUtils.isEmpty(routePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
String normalized = routePath.trim().replace('\\', '/').toLowerCase(Locale.ROOT);
|
||||
return "admin".equals(normalized) || normalized.startsWith("admin/")
|
||||
|| "/admin".equals(normalized) || normalized.startsWith("/admin/");
|
||||
}
|
||||
|
||||
private String stringValue(Object value)
|
||||
{
|
||||
return value == null ? null : String.valueOf(value);
|
||||
@@ -1075,11 +1342,36 @@ public class FrontendPageDesignService
|
||||
return StringUtils.isEmpty(value) ? fallback : value;
|
||||
}
|
||||
|
||||
private boolean isMenuDirectory(String pageType)
|
||||
{
|
||||
return MENU_DIRECTORY_PAGE_TYPE.equals(pageType);
|
||||
}
|
||||
|
||||
private boolean isAssociatedPageType(String pageType)
|
||||
{
|
||||
return "detail".equals(pageType);
|
||||
}
|
||||
|
||||
private String normalizeScope(String scope)
|
||||
{
|
||||
return ADMIN_SCOPE.equals(scope) ? ADMIN_SCOPE : FRONTEND_SCOPE;
|
||||
}
|
||||
|
||||
private String homePageCode(String scope)
|
||||
{
|
||||
return ADMIN_SCOPE.equals(normalizeScope(scope)) ? ADMIN_HOME_PAGE_CODE : FRONTEND_HOME_PAGE_CODE;
|
||||
}
|
||||
|
||||
private String homeRoutePath(String scope)
|
||||
{
|
||||
return ADMIN_SCOPE.equals(normalizeScope(scope)) ? "/admin/home" : "/home";
|
||||
}
|
||||
|
||||
private boolean isHomePageCode(String pageCode, String scope)
|
||||
{
|
||||
return StringUtils.isNotEmpty(pageCode) && pageCode.equals(homePageCode(scope));
|
||||
}
|
||||
|
||||
private boolean scopeMatches(String designScope, String expectedScope)
|
||||
{
|
||||
String normalizedDesignScope = StringUtils.isEmpty(designScope) ? FRONTEND_SCOPE : designScope;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"code": "admin_bar_chart",
|
||||
"name": "柱状图",
|
||||
"category": "数据图表",
|
||||
"description": "按分类维度对比一个到三个聚合指标,支持横向和堆叠展示。",
|
||||
"kind": "chart",
|
||||
"surfaces": ["admin"],
|
||||
"placements": ["embedded", "page"],
|
||||
"chartType": "bar",
|
||||
"datasetSchema": "single-table-aggregate-v1",
|
||||
"version": 2,
|
||||
"defaultSpan": 6,
|
||||
"configs": [],
|
||||
"templates": [
|
||||
{ "key": "frontendComponent", "template": "/business-blocks/chart/templates/frontend/AdminChartBlock.vue.vm", "outputPath": "src/views/pages/${pageCode}/blocks/${instanceCode}/${InstanceClassName}.vue" },
|
||||
{ "key": "frontendApi", "template": "/business-blocks/chart/templates/frontend/chartApi.js.vm", "outputPath": "src/api/business-blocks/${instanceCode}.js" },
|
||||
{ "key": "controller", "template": "/business-blocks/chart/templates/backend/AdminChartController.java.vm", "outputPath": "src/main/java/${packagePath}/controller/block/${InstanceClassName}Controller.java" },
|
||||
{ "key": "service", "template": "/business-blocks/chart/templates/backend/AdminChartService.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/I${InstanceClassName}Service.java" },
|
||||
{ "key": "serviceImpl", "template": "/business-blocks/chart/templates/backend/AdminChartServiceImpl.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/impl/${InstanceClassName}ServiceImpl.java" },
|
||||
{ "key": "mapper", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.java.vm", "outputPath": "src/main/java/${packagePath}/mapper/block/${InstanceClassName}Mapper.java" },
|
||||
{ "key": "mapperXml", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.xml.vm", "outputPath": "src/main/resources/mapper/block/${InstanceClassName}Mapper.xml" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"code": "admin_line_chart",
|
||||
"name": "折线图",
|
||||
"category": "数据图表",
|
||||
"description": "按时间或分类维度展示一个到三个指标的变化趋势。",
|
||||
"kind": "chart",
|
||||
"surfaces": ["admin"],
|
||||
"placements": ["embedded", "page"],
|
||||
"chartType": "line",
|
||||
"datasetSchema": "single-table-aggregate-v1",
|
||||
"version": 2,
|
||||
"defaultSpan": 12,
|
||||
"configs": [],
|
||||
"templates": [
|
||||
{ "key": "frontendComponent", "template": "/business-blocks/chart/templates/frontend/AdminChartBlock.vue.vm", "outputPath": "src/views/pages/${pageCode}/blocks/${instanceCode}/${InstanceClassName}.vue" },
|
||||
{ "key": "frontendApi", "template": "/business-blocks/chart/templates/frontend/chartApi.js.vm", "outputPath": "src/api/business-blocks/${instanceCode}.js" },
|
||||
{ "key": "controller", "template": "/business-blocks/chart/templates/backend/AdminChartController.java.vm", "outputPath": "src/main/java/${packagePath}/controller/block/${InstanceClassName}Controller.java" },
|
||||
{ "key": "service", "template": "/business-blocks/chart/templates/backend/AdminChartService.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/I${InstanceClassName}Service.java" },
|
||||
{ "key": "serviceImpl", "template": "/business-blocks/chart/templates/backend/AdminChartServiceImpl.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/impl/${InstanceClassName}ServiceImpl.java" },
|
||||
{ "key": "mapper", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.java.vm", "outputPath": "src/main/java/${packagePath}/mapper/block/${InstanceClassName}Mapper.java" },
|
||||
{ "key": "mapperXml", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.xml.vm", "outputPath": "src/main/resources/mapper/block/${InstanceClassName}Mapper.xml" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"code": "admin_metric_chart",
|
||||
"name": "指标卡",
|
||||
"category": "数据图表",
|
||||
"description": "展示一个核心聚合数值,适合订单数、销售额、待办数等后台指标。",
|
||||
"kind": "chart",
|
||||
"surfaces": ["admin"],
|
||||
"placements": ["embedded", "page"],
|
||||
"chartType": "metric",
|
||||
"datasetSchema": "single-table-aggregate-v1",
|
||||
"version": 2,
|
||||
"defaultSpan": 4,
|
||||
"configs": [],
|
||||
"templates": [
|
||||
{ "key": "frontendComponent", "template": "/business-blocks/chart/templates/frontend/AdminChartBlock.vue.vm", "outputPath": "src/views/pages/${pageCode}/blocks/${instanceCode}/${InstanceClassName}.vue" },
|
||||
{ "key": "frontendApi", "template": "/business-blocks/chart/templates/frontend/chartApi.js.vm", "outputPath": "src/api/business-blocks/${instanceCode}.js" },
|
||||
{ "key": "controller", "template": "/business-blocks/chart/templates/backend/AdminChartController.java.vm", "outputPath": "src/main/java/${packagePath}/controller/block/${InstanceClassName}Controller.java" },
|
||||
{ "key": "service", "template": "/business-blocks/chart/templates/backend/AdminChartService.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/I${InstanceClassName}Service.java" },
|
||||
{ "key": "serviceImpl", "template": "/business-blocks/chart/templates/backend/AdminChartServiceImpl.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/impl/${InstanceClassName}ServiceImpl.java" },
|
||||
{ "key": "mapper", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.java.vm", "outputPath": "src/main/java/${packagePath}/mapper/block/${InstanceClassName}Mapper.java" },
|
||||
{ "key": "mapperXml", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.xml.vm", "outputPath": "src/main/resources/mapper/block/${InstanceClassName}Mapper.xml" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"code": "admin_pie_chart",
|
||||
"name": "饼图",
|
||||
"category": "数据图表",
|
||||
"description": "展示分类维度下一个聚合指标的占比,可切换实心或环形。",
|
||||
"kind": "chart",
|
||||
"surfaces": ["admin"],
|
||||
"placements": ["embedded", "page"],
|
||||
"chartType": "pie",
|
||||
"datasetSchema": "single-table-aggregate-v1",
|
||||
"version": 2,
|
||||
"defaultSpan": 6,
|
||||
"configs": [],
|
||||
"templates": [
|
||||
{ "key": "frontendComponent", "template": "/business-blocks/chart/templates/frontend/AdminChartBlock.vue.vm", "outputPath": "src/views/pages/${pageCode}/blocks/${instanceCode}/${InstanceClassName}.vue" },
|
||||
{ "key": "frontendApi", "template": "/business-blocks/chart/templates/frontend/chartApi.js.vm", "outputPath": "src/api/business-blocks/${instanceCode}.js" },
|
||||
{ "key": "controller", "template": "/business-blocks/chart/templates/backend/AdminChartController.java.vm", "outputPath": "src/main/java/${packagePath}/controller/block/${InstanceClassName}Controller.java" },
|
||||
{ "key": "service", "template": "/business-blocks/chart/templates/backend/AdminChartService.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/I${InstanceClassName}Service.java" },
|
||||
{ "key": "serviceImpl", "template": "/business-blocks/chart/templates/backend/AdminChartServiceImpl.java.vm", "outputPath": "src/main/java/${packagePath}/service/block/impl/${InstanceClassName}ServiceImpl.java" },
|
||||
{ "key": "mapper", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.java.vm", "outputPath": "src/main/java/${packagePath}/mapper/block/${InstanceClassName}Mapper.java" },
|
||||
{ "key": "mapperXml", "template": "/business-blocks/chart/templates/backend/AdminChartMapper.xml.vm", "outputPath": "src/main/resources/mapper/block/${InstanceClassName}Mapper.xml" }
|
||||
]
|
||||
}
|
||||
@@ -12,6 +12,9 @@ import com.ruoyi.generator.domain.GenTable;
|
||||
import com.ruoyi.generator.domain.GenTableColumn;
|
||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockDefinition;
|
||||
import com.ruoyi.generator.domain.front.dto.block.BusinessBlockInstance;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartDatasetConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartDimensionConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.ChartMetricConfig;
|
||||
import com.ruoyi.generator.domain.front.dto.block.PageBusinessBlockLayout;
|
||||
|
||||
public class BusinessBlockRegistryServiceTest
|
||||
@@ -70,6 +73,34 @@ public class BusinessBlockRegistryServiceTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminSurfaceIncludesFourChartBlocks()
|
||||
{
|
||||
java.util.List<BusinessBlockDefinition> definitions = service.listDefinitions("admin");
|
||||
|
||||
assertTrue(hasCode(definitions, "admin_metric_chart"));
|
||||
assertTrue(hasCode(definitions, "admin_line_chart"));
|
||||
assertTrue(hasCode(definitions, "admin_bar_chart"));
|
||||
assertTrue(hasCode(definitions, "admin_pie_chart"));
|
||||
assertEquals(4, countKind(definitions, "chart"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void frontendSurfaceExcludesAdminCharts()
|
||||
{
|
||||
assertEquals(0, countKind(service.listDefinitions("frontend"), "chart"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chartValidationRejectsAdminChartOnFrontendPage()
|
||||
{
|
||||
ServiceException exception = expectServiceException(() ->
|
||||
service.validateInstanceConfig(service.findDefinition("admin_line_chart"),
|
||||
validLineChartInstance(), chartTables(), "frontend"));
|
||||
|
||||
assertTrue(exception.getMessage().contains("后台"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseLayoutReadsBusinessBlockInstances()
|
||||
{
|
||||
@@ -210,6 +241,34 @@ public class BusinessBlockRegistryServiceTest
|
||||
return instance;
|
||||
}
|
||||
|
||||
private BusinessBlockInstance validLineChartInstance()
|
||||
{
|
||||
BusinessBlockInstance instance = new BusinessBlockInstance();
|
||||
instance.setId("admin_line_chart_001");
|
||||
instance.setBlockCode("admin_line_chart");
|
||||
instance.setName("近 30 天销售趋势");
|
||||
instance.setSpan(Integer.valueOf(12));
|
||||
|
||||
ChartDimensionConfig dimension = new ChartDimensionConfig();
|
||||
dimension.setField("create_time");
|
||||
dimension.setTimeUnit("day");
|
||||
|
||||
ChartMetricConfig metric = new ChartMetricConfig();
|
||||
metric.setField("amount");
|
||||
metric.setAggregate("sum");
|
||||
metric.setAlias("salesAmount");
|
||||
metric.setLabel("销售额");
|
||||
|
||||
ChartDatasetConfig dataset = new ChartDatasetConfig();
|
||||
dataset.setSchema("single-table-aggregate-v1");
|
||||
dataset.setTable("shop_order");
|
||||
dataset.setDimension(dimension);
|
||||
dataset.setMetrics(Collections.singletonList(metric));
|
||||
dataset.setLimit(Integer.valueOf(100));
|
||||
instance.setDataset(dataset);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private java.util.List<GenTable> tables()
|
||||
{
|
||||
return Arrays.asList(
|
||||
@@ -228,6 +287,15 @@ public class BusinessBlockRegistryServiceTest
|
||||
column("price", "price")));
|
||||
}
|
||||
|
||||
private java.util.List<GenTable> chartTables()
|
||||
{
|
||||
GenTable order = table("shop_order",
|
||||
typedColumn("id", "id", "Long", "bigint"),
|
||||
typedColumn("amount", "amount", "BigDecimal", "decimal(10,2)"),
|
||||
typedColumn("create_time", "createTime", "Date", "datetime"));
|
||||
return Collections.singletonList(order);
|
||||
}
|
||||
|
||||
private GenTable table(String tableName, GenTableColumn... columns)
|
||||
{
|
||||
GenTable table = new GenTable();
|
||||
@@ -244,6 +312,39 @@ public class BusinessBlockRegistryServiceTest
|
||||
return column;
|
||||
}
|
||||
|
||||
private GenTableColumn typedColumn(String columnName, String javaField, String javaType, String columnType)
|
||||
{
|
||||
GenTableColumn column = column(columnName, javaField);
|
||||
column.setJavaType(javaType);
|
||||
column.setColumnType(columnType);
|
||||
return column;
|
||||
}
|
||||
|
||||
private boolean hasCode(java.util.List<BusinessBlockDefinition> definitions, String code)
|
||||
{
|
||||
for (BusinessBlockDefinition definition : definitions)
|
||||
{
|
||||
if (code.equals(definition.getCode()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int countKind(java.util.List<BusinessBlockDefinition> definitions, String kind)
|
||||
{
|
||||
int count = 0;
|
||||
for (BusinessBlockDefinition definition : definitions)
|
||||
{
|
||||
if (kind.equals(definition.getKind()))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private ServiceException expectServiceException(ThrowingRunnable runnable)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.generator.service.front;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -56,7 +57,8 @@ public class FrontendPageDesignServiceTest
|
||||
public void initializeCreatesMissingDesignFromFrontendPages()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectDesignsByProjectId(10L)).thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(bookTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(99L)).thenReturn(Arrays.asList(column("title", "title", "1", "1", "1"),
|
||||
column("status", "status", "1", "1", "1")));
|
||||
@@ -64,11 +66,101 @@ public class FrontendPageDesignServiceTest
|
||||
FrontendPageDesignInitResponse response = service.initializePageDesigns(7L, 10L);
|
||||
|
||||
List<FrontendPageDesignResponse> designs = response.getDesigns();
|
||||
assertEquals(2, designs.size());
|
||||
assertEquals("home", designs.get(0).getPageCode());
|
||||
assertEquals("book_catalog_page", designs.get(1).getPageCode());
|
||||
assertTrue(designs.get(1).getLayoutJson().contains("\"type\":\"query\""));
|
||||
assertTrue(designs.get(1).getLayoutJson().contains("\"title\""));
|
||||
verify(pageDesignMapper, times(2)).insertFrontProjectPageDesign(any(FrontProjectPageDesign.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeCreatesFrontendHomePageBeforeBlueprintPages()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(bookTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(99L)).thenReturn(Arrays.asList(
|
||||
column("title", "title", "1", "1", "1"),
|
||||
column("status", "status", "1", "1", "1")));
|
||||
|
||||
FrontendPageDesignInitResponse response = service.initializePageDesigns(7L, 10L, "frontend");
|
||||
|
||||
List<FrontendPageDesignResponse> designs = response.getDesigns();
|
||||
assertEquals(2, designs.size());
|
||||
assertEquals("home", designs.get(0).getPageCode());
|
||||
assertEquals("\u4e3b\u9875", designs.get(0).getPageName());
|
||||
assertEquals("/home", designs.get(0).getRoutePath());
|
||||
assertEquals("business_block", designs.get(0).getPageType());
|
||||
assertEquals("frontend", designs.get(0).getPageScope());
|
||||
assertTrue(designs.get(0).getLayoutJson().contains("\"menuOrder\":1"));
|
||||
assertEquals("book_catalog_page", designs.get(1).getPageCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeCopiesParentPageCodeToHiddenDetailNavigation()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(projectWithAssociatedDetailPage());
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(bookTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(99L)).thenReturn(Arrays.asList(
|
||||
column("id", "id", "1", "1", "1"),
|
||||
column("book_title", "bookTitle", "1", "1", "1")));
|
||||
|
||||
service.initializePageDesigns(7L, 10L, "frontend");
|
||||
|
||||
ArgumentCaptor<FrontProjectPageDesign> captor = ArgumentCaptor.forClass(FrontProjectPageDesign.class);
|
||||
verify(pageDesignMapper, times(3)).insertFrontProjectPageDesign(captor.capture());
|
||||
FrontProjectPageDesign detail = captor.getAllValues().get(2);
|
||||
assertEquals("book_detail_page", detail.getPageCode());
|
||||
assertTrue(detail.getLayoutJson().contains("\"parentPageCode\":\"book_catalog_page\""));
|
||||
assertTrue(detail.getLayoutJson().contains("\"showInMenu\":false"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeFrontendDesignsSkipsAdminBlueprintPages()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(projectWithMixedFrontendPages());
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(bookTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(99L)).thenReturn(Arrays.asList(
|
||||
column("title", "title", "1", "1", "1"),
|
||||
column("status", "status", "1", "1", "1")));
|
||||
|
||||
FrontendPageDesignInitResponse response = service.initializePageDesigns(7L, 10L, "frontend");
|
||||
|
||||
List<FrontendPageDesignResponse> designs = response.getDesigns();
|
||||
assertEquals(2, designs.size());
|
||||
assertEquals("home", designs.get(0).getPageCode());
|
||||
assertEquals("book_catalog_page", designs.get(1).getPageCode());
|
||||
assertEquals("/books", designs.get(1).getRoutePath());
|
||||
ArgumentCaptor<FrontProjectPageDesign> captor = ArgumentCaptor.forClass(FrontProjectPageDesign.class);
|
||||
verify(pageDesignMapper, times(2)).insertFrontProjectPageDesign(captor.capture());
|
||||
assertEquals("home", captor.getAllValues().get(0).getPageCode());
|
||||
assertEquals("book_catalog_page", captor.getAllValues().get(1).getPageCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listFrontendDesignsExcludesAdminRouteLeaks()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
FrontProjectPageDesign catalog = currentDesign();
|
||||
FrontProjectPageDesign leakedAdmin = currentDesign();
|
||||
leakedAdmin.setDesignId(89L);
|
||||
leakedAdmin.setPageCode("book_admin");
|
||||
leakedAdmin.setPageName("Book Admin");
|
||||
leakedAdmin.setRoutePath("/admin/books");
|
||||
leakedAdmin.setLayoutJson("{\"canvas\":\"admin-list-v1\",\"navigation\":{\"showInMenu\":true}}");
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Arrays.asList(catalog, leakedAdmin));
|
||||
|
||||
List<FrontendPageDesignResponse> designs = service.listPageDesigns(7L, 10L, "frontend");
|
||||
|
||||
assertEquals(1, designs.size());
|
||||
assertEquals("book_catalog_page", designs.get(0).getPageCode());
|
||||
assertTrue(designs.get(0).getLayoutJson().contains("\"type\":\"query\""));
|
||||
assertTrue(designs.get(0).getLayoutJson().contains("\"title\""));
|
||||
verify(pageDesignMapper).insertFrontProjectPageDesign(any(FrontProjectPageDesign.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,13 +178,18 @@ public class FrontendPageDesignServiceTest
|
||||
FrontendPageDesignInitResponse response = service.initializePageDesigns(7L, 10L, "admin");
|
||||
|
||||
List<FrontendPageDesignResponse> designs = response.getDesigns();
|
||||
assertEquals(2, designs.size());
|
||||
assertEquals(3, designs.size());
|
||||
assertEquals("admin", designs.get(0).getPageScope());
|
||||
assertEquals("book_admin", designs.get(0).getPageCode());
|
||||
assertEquals("book_info", designs.get(0).getTableName());
|
||||
assertEquals("admin_customer_info", designs.get(1).getPageCode());
|
||||
assertEquals("customer_info", designs.get(1).getTableName());
|
||||
assertTrue(designs.get(1).getLayoutJson().contains("\"canvas\":\"admin-list-v1\""));
|
||||
assertEquals("admin_home", designs.get(0).getPageCode());
|
||||
assertEquals("\u4e3b\u9875", designs.get(0).getPageName());
|
||||
assertEquals("/admin/home", designs.get(0).getRoutePath());
|
||||
assertEquals("business_block", designs.get(0).getPageType());
|
||||
assertTrue(designs.get(0).getLayoutJson().contains("\"menuOrder\":1"));
|
||||
assertEquals("book_admin", designs.get(1).getPageCode());
|
||||
assertEquals("book_info", designs.get(1).getTableName());
|
||||
assertEquals("admin_customer_info", designs.get(2).getPageCode());
|
||||
assertEquals("customer_info", designs.get(2).getTableName());
|
||||
assertTrue(designs.get(2).getLayoutJson().contains("\"canvas\":\"admin-list-v1\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,6 +221,30 @@ public class FrontendPageDesignServiceTest
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("bookTitle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPageDesignPersistsMenuDirectoryWithoutRouteOrTable()
|
||||
{
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectDesignsByProjectIdAndScope(10L, "frontend"))
|
||||
.thenReturn(Collections.<FrontProjectPageDesign>emptyList());
|
||||
FrontendPageDesignRequest request = new FrontendPageDesignRequest();
|
||||
request.setPageCode("library_directory");
|
||||
request.setMenuCode("library_directory");
|
||||
request.setPageName("Library");
|
||||
request.setPageType("menu_directory");
|
||||
request.setLayoutJson("{\"navigation\":{\"showInMenu\":true,\"menuLevel\":\"primary\",\"nodeType\":\"directory\",\"menuCode\":\"library_directory\",\"menuName\":\"Library\"}}");
|
||||
|
||||
service.createPageDesign(7L, 10L, request, "frontend");
|
||||
|
||||
ArgumentCaptor<FrontProjectPageDesign> captor = ArgumentCaptor.forClass(FrontProjectPageDesign.class);
|
||||
verify(pageDesignMapper).insertFrontProjectPageDesign(captor.capture());
|
||||
assertEquals("menu_directory", captor.getValue().getPageType());
|
||||
assertEquals("", captor.getValue().getRoutePath());
|
||||
assertEquals("", captor.getValue().getTableName());
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"nodeType\":\"directory\""));
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"menuLevel\":\"primary\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletePageDesignRemovesOwnedDesign()
|
||||
{
|
||||
@@ -349,6 +470,73 @@ public class FrontendPageDesignServiceTest
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"blockCode\":\"cart\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePersistsAdminHomeBusinessBlockWithoutTable()
|
||||
{
|
||||
FrontProjectPageDesign current = currentAdminHomeDesign();
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectFrontProjectPageDesignById(88L)).thenReturn(current);
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.<FrontProjectTable>emptyList());
|
||||
FrontendPageDesignRequest request = new FrontendPageDesignRequest();
|
||||
request.setVersion(1);
|
||||
request.setPageName("\u4e3b\u9875");
|
||||
request.setRoutePath("/admin/home");
|
||||
request.setPageType("business_block");
|
||||
request.setLayoutJson("{\"canvas\":\"business-blocks-v1\",\"blocks\":[],\"navigation\":{\"showInMenu\":true,\"menuOrder\":1}}");
|
||||
request.setActionJson("{\"toolbarActions\":[],\"rowActions\":[]}");
|
||||
|
||||
service.updatePageDesign(7L, 10L, 88L, request, "admin");
|
||||
|
||||
ArgumentCaptor<FrontProjectPageDesign> captor = ArgumentCaptor.forClass(FrontProjectPageDesign.class);
|
||||
verify(pageDesignMapper).updateFrontProjectPageDesign(captor.capture());
|
||||
assertEquals("admin", captor.getValue().getPageScope());
|
||||
assertEquals("", captor.getValue().getTableName());
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"canvas\":\"business-blocks-v1\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updatePersistsValidAdminChartLayout()
|
||||
{
|
||||
FrontProjectPageDesign current = currentAdminHomeDesign();
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectFrontProjectPageDesignById(88L)).thenReturn(current);
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(orderTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(203L)).thenReturn(Arrays.asList(
|
||||
typedColumn("id", "id", "Long", "bigint"),
|
||||
typedColumn("amount", "amount", "BigDecimal", "decimal(10,2)"),
|
||||
typedColumn("status", "status", "String", "varchar(32)"),
|
||||
typedColumn("create_time", "createTime", "Date", "datetime")));
|
||||
FrontendPageDesignRequest request = adminChartRequest(validAdminChartLayout());
|
||||
|
||||
service.updatePageDesign(7L, 10L, 88L, request, "admin");
|
||||
|
||||
ArgumentCaptor<FrontProjectPageDesign> captor = ArgumentCaptor.forClass(FrontProjectPageDesign.class);
|
||||
verify(pageDesignMapper).updateFrontProjectPageDesign(captor.capture());
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"blockCode\":\"admin_line_chart\""));
|
||||
assertTrue(captor.getValue().getLayoutJson().contains("\"table\":\"shop_order\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRejectsAdminChartOnFrontendPage()
|
||||
{
|
||||
FrontProjectPageDesign current = currentBusinessBlockDesign();
|
||||
current.setTableName("shop_order");
|
||||
when(frontProjectService.getProject(7L, 10L)).thenReturn(project());
|
||||
when(pageDesignMapper.selectFrontProjectPageDesignById(88L)).thenReturn(current);
|
||||
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.singletonList(orderTable()));
|
||||
when(frontProjectColumnMapper.selectColumnsByTableId(203L)).thenReturn(Arrays.asList(
|
||||
typedColumn("id", "id", "Long", "bigint"),
|
||||
typedColumn("amount", "amount", "BigDecimal", "decimal(10,2)"),
|
||||
typedColumn("status", "status", "String", "varchar(32)"),
|
||||
typedColumn("create_time", "createTime", "Date", "datetime")));
|
||||
FrontendPageDesignRequest request = adminChartRequest(validAdminChartLayout());
|
||||
|
||||
ServiceException exception = expectServiceException(() ->
|
||||
service.updatePageDesign(7L, 10L, 88L, request, "frontend"));
|
||||
|
||||
assertTrue(exception.getMessage().contains("后台"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRejectsBusinessBlockMissingRequiredConfig()
|
||||
{
|
||||
@@ -402,6 +590,30 @@ public class FrontendPageDesignServiceTest
|
||||
return project;
|
||||
}
|
||||
|
||||
private FrontProject projectWithMixedFrontendPages()
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(10L);
|
||||
project.setUserId(7L);
|
||||
project.setAppBlueprint("{\"frontendPages\":["
|
||||
+ "{\"code\":\"book_catalog_page\",\"name\":\"Book Catalog\",\"menuCode\":\"book_catalog\",\"path\":\"/books\",\"pageType\":\"list\",\"tableName\":\"book_info\"},"
|
||||
+ "{\"code\":\"book_admin\",\"name\":\"Book Admin\",\"menuCode\":\"book_admin\",\"path\":\"/admin/books\",\"pageType\":\"list\",\"tableName\":\"book_info\"}"
|
||||
+ "]}");
|
||||
return project;
|
||||
}
|
||||
|
||||
private FrontProject projectWithAssociatedDetailPage()
|
||||
{
|
||||
FrontProject project = new FrontProject();
|
||||
project.setProjectId(10L);
|
||||
project.setUserId(7L);
|
||||
project.setAppBlueprint("{\"frontendPages\":["
|
||||
+ "{\"code\":\"book_catalog_page\",\"name\":\"Book Catalog\",\"menuCode\":\"book_catalog\",\"path\":\"/books\",\"pageType\":\"list\",\"tableName\":\"book_info\"},"
|
||||
+ "{\"code\":\"book_detail_page\",\"name\":\"Book Detail\",\"parentPageCode\":\"book_catalog_page\",\"path\":\"/books/:id\",\"pageType\":\"detail\",\"tableName\":\"book_info\"}"
|
||||
+ "]}");
|
||||
return project;
|
||||
}
|
||||
|
||||
private FrontProjectTable bookTable()
|
||||
{
|
||||
FrontProjectTable table = new FrontProjectTable();
|
||||
@@ -452,6 +664,18 @@ public class FrontendPageDesignServiceTest
|
||||
return table;
|
||||
}
|
||||
|
||||
private FrontProjectTable orderTable()
|
||||
{
|
||||
FrontProjectTable table = new FrontProjectTable();
|
||||
table.setTableId(203L);
|
||||
table.setProjectId(10L);
|
||||
table.setTableName("shop_order");
|
||||
table.setAllowAdd("1");
|
||||
table.setAllowEdit("1");
|
||||
table.setAllowRemove("1");
|
||||
return table;
|
||||
}
|
||||
|
||||
private FrontProjectColumn column(String columnName, String javaField, String isQuery, String isList, String isEdit)
|
||||
{
|
||||
FrontProjectColumn column = new FrontProjectColumn();
|
||||
@@ -464,6 +688,14 @@ public class FrontendPageDesignServiceTest
|
||||
return column;
|
||||
}
|
||||
|
||||
private FrontProjectColumn typedColumn(String columnName, String javaField, String javaType, String columnType)
|
||||
{
|
||||
FrontProjectColumn column = column(columnName, javaField, "0", "1", "0");
|
||||
column.setJavaType(javaType);
|
||||
column.setColumnType(columnType);
|
||||
return column;
|
||||
}
|
||||
|
||||
private FrontProjectPageDesign currentDesign()
|
||||
{
|
||||
FrontProjectPageDesign design = new FrontProjectPageDesign();
|
||||
@@ -489,6 +721,21 @@ public class FrontendPageDesignServiceTest
|
||||
return design;
|
||||
}
|
||||
|
||||
private FrontProjectPageDesign currentAdminHomeDesign()
|
||||
{
|
||||
FrontProjectPageDesign design = currentDesign();
|
||||
design.setPageScope("admin");
|
||||
design.setPageCode("admin_home");
|
||||
design.setMenuCode("admin_home");
|
||||
design.setPageName("\u4e3b\u9875");
|
||||
design.setRoutePath("/admin/home");
|
||||
design.setPageType("business_block");
|
||||
design.setTableName("");
|
||||
design.setLayoutJson("{\"canvas\":\"business-blocks-v1\",\"blocks\":[],\"navigation\":{\"showInMenu\":true,\"menuOrder\":1}}");
|
||||
design.setActionJson("{\"toolbarActions\":[],\"rowActions\":[]}");
|
||||
return design;
|
||||
}
|
||||
|
||||
private FrontendPageDesignRequest businessBlockRequest(String layoutJson)
|
||||
{
|
||||
FrontendPageDesignRequest request = new FrontendPageDesignRequest();
|
||||
@@ -502,6 +749,19 @@ public class FrontendPageDesignServiceTest
|
||||
return request;
|
||||
}
|
||||
|
||||
private FrontendPageDesignRequest adminChartRequest(String layoutJson)
|
||||
{
|
||||
FrontendPageDesignRequest request = new FrontendPageDesignRequest();
|
||||
request.setVersion(1);
|
||||
request.setPageName("\u4e3b\u9875");
|
||||
request.setRoutePath("/admin/home");
|
||||
request.setPageType("business_block");
|
||||
request.setTableName("");
|
||||
request.setLayoutJson(layoutJson);
|
||||
request.setActionJson("{\"toolbarActions\":[],\"rowActions\":[]}");
|
||||
return request;
|
||||
}
|
||||
|
||||
private String validBusinessBlockLayout()
|
||||
{
|
||||
return "{\"canvas\":\"business-blocks-v1\",\"blocks\":[{\"id\":\"cart_001\",\"blockCode\":\"cart\",\"config\":{"
|
||||
@@ -517,6 +777,24 @@ public class FrontendPageDesignServiceTest
|
||||
+ "}}]}";
|
||||
}
|
||||
|
||||
private String validAdminChartLayout()
|
||||
{
|
||||
return "{\"canvas\":\"business-blocks-v1\",\"blocks\":[{"
|
||||
+ "\"id\":\"admin_line_chart_001\","
|
||||
+ "\"blockCode\":\"admin_line_chart\","
|
||||
+ "\"name\":\"近 30 天销售趋势\","
|
||||
+ "\"span\":12,"
|
||||
+ "\"dataset\":{"
|
||||
+ "\"schema\":\"single-table-aggregate-v1\","
|
||||
+ "\"table\":\"shop_order\","
|
||||
+ "\"dimension\":{\"field\":\"create_time\",\"timeUnit\":\"day\"},"
|
||||
+ "\"metrics\":[{\"field\":\"amount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\",\"label\":\"销售额\"}],"
|
||||
+ "\"filters\":[{\"field\":\"status\",\"operator\":\"eq\",\"value\":\"PAID\"}],"
|
||||
+ "\"sort\":{\"by\":\"dimension\",\"order\":\"asc\"},"
|
||||
+ "\"limit\":100},"
|
||||
+ "\"display\":{\"showLegend\":true}}]}";
|
||||
}
|
||||
|
||||
private ServiceException expectBusinessBlockUpdateFailure(String layoutJson)
|
||||
{
|
||||
FrontProjectPageDesign current = currentBusinessBlockDesign();
|
||||
|
||||
Reference in New Issue
Block a user