feat: 展示 AI 资源创建人信息
- 为 Bot、工作流、知识库、插件列表补充创建人名称回填 - 在卡片中展示创建者与创建时间 - 补充后端与前端对应测试
This commit is contained in:
@@ -36,5 +36,17 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-captcha</artifactId>
|
<artifactId>easyflow-common-captcha</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>5.12.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.springframework.util.StringUtils;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||||
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
|
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
|
||||||
import tech.easyflow.ai.entity.*;
|
import tech.easyflow.ai.entity.*;
|
||||||
import tech.easyflow.ai.publish.BotPublishAppService;
|
import tech.easyflow.ai.publish.BotPublishAppService;
|
||||||
@@ -73,6 +74,8 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
private BotPublishAppService botPublishAppService;
|
private BotPublishAppService botPublishAppService;
|
||||||
@Resource
|
@Resource
|
||||||
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
||||||
|
@Resource
|
||||||
|
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||||
|
|
||||||
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
|
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
|
||||||
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
|
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
|
||||||
@@ -305,6 +308,7 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
applyCategoryPermission(queryWrapper);
|
applyCategoryPermission(queryWrapper);
|
||||||
Page<Bot> result = super.queryPage(page, queryWrapper);
|
Page<Bot> result = super.queryPage(page, queryWrapper);
|
||||||
aiResourceApprovalStateService.fillBotApprovalState(result.getRecords());
|
aiResourceApprovalStateService.fillBotApprovalState(result.getRecords());
|
||||||
|
aiResourceCreatorNameSupport.fillBotCreatorNames(result.getRecords());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||||
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
|
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
|
||||||
import tech.easyflow.ai.documentimport.DocumentImportDtos;
|
import tech.easyflow.ai.documentimport.DocumentImportDtos;
|
||||||
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
|
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
|
||||||
@@ -76,6 +77,8 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
private KnowledgePublishAppService knowledgePublishAppService;
|
private KnowledgePublishAppService knowledgePublishAppService;
|
||||||
@Resource
|
@Resource
|
||||||
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
||||||
|
@Resource
|
||||||
|
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||||
|
|
||||||
public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) {
|
public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) {
|
||||||
super(service);
|
super(service);
|
||||||
@@ -310,6 +313,7 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
applyPublishedOnlyFilter(queryWrapper);
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
Page<DocumentCollection> result = super.queryPage(page, queryWrapper);
|
Page<DocumentCollection> result = super.queryPage(page, queryWrapper);
|
||||||
aiResourceApprovalStateService.fillKnowledgeApprovalState(result.getRecords());
|
aiResourceApprovalStateService.fillKnowledgeApprovalState(result.getRecords());
|
||||||
|
aiResourceCreatorNameSupport.fillDocumentCollectionCreatorNames(result.getRecords());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.mybatisflex.core.query.QueryWrapper;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||||
import tech.easyflow.ai.entity.Model;
|
import tech.easyflow.ai.entity.Model;
|
||||||
import tech.easyflow.ai.entity.Plugin;
|
import tech.easyflow.ai.entity.Plugin;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
@@ -66,6 +67,8 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
|
|||||||
private ResourceAccessService resourceAccessService;
|
private ResourceAccessService resourceAccessService;
|
||||||
@Resource
|
@Resource
|
||||||
private WorkflowPluginSnapshotResolver workflowPluginSnapshotResolver;
|
private WorkflowPluginSnapshotResolver workflowPluginSnapshotResolver;
|
||||||
|
@Resource
|
||||||
|
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Result<?> onSaveOrUpdateBefore(Plugin entity, boolean isSave) {
|
protected Result<?> onSaveOrUpdateBefore(Plugin entity, boolean isSave) {
|
||||||
@@ -117,7 +120,11 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
|
|||||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
|
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
|
||||||
} else {
|
} else {
|
||||||
return pluginService.pageByCategory(pageNumber, pageSize, category);
|
Result<Page<Plugin>> result = pluginService.pageByCategory(pageNumber, pageSize, category);
|
||||||
|
if (result != null && result.getData() != null) {
|
||||||
|
aiResourceCreatorNameSupport.fillPluginCreatorNames(result.getData().getRecords());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +157,7 @@ public class PluginController extends BaseCurdController<PluginService, Plugin>
|
|||||||
List<Plugin> totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
List<Plugin> totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||||
boolean availableOnly = isAvailableOnly();
|
boolean availableOnly = isAvailableOnly();
|
||||||
List<Plugin> prepared = pluginService.preparePluginsForCurrentUser(totalList, !availableOnly, availableOnly);
|
List<Plugin> prepared = pluginService.preparePluginsForCurrentUser(totalList, !availableOnly, availableOnly);
|
||||||
|
aiResourceCreatorNameSupport.fillPluginCreatorNames(prepared);
|
||||||
long total = prepared.size();
|
long total = prepared.size();
|
||||||
int fromIndex = Math.max(0, Math.toIntExact((page.getPageNumber() - 1) * page.getPageSize()));
|
int fromIndex = Math.max(0, Math.toIntExact((page.getPageNumber() - 1) * page.getPageSize()));
|
||||||
if (fromIndex >= prepared.size()) {
|
if (fromIndex >= prepared.size()) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.springframework.web.context.request.RequestContextHolder;
|
|||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||||
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
||||||
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
||||||
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
|
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
|
||||||
@@ -93,6 +94,8 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
private WorkflowPublishAppService workflowPublishAppService;
|
private WorkflowPublishAppService workflowPublishAppService;
|
||||||
@Resource
|
@Resource
|
||||||
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
||||||
|
@Resource
|
||||||
|
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||||
|
|
||||||
public WorkflowController(WorkflowService service, ModelService modelService) {
|
public WorkflowController(WorkflowService service, ModelService modelService) {
|
||||||
super(service);
|
super(service);
|
||||||
@@ -434,6 +437,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
applyPublishedOnlyFilter(queryWrapper);
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
Page<Workflow> result = super.queryPage(page, queryWrapper);
|
Page<Workflow> result = super.queryPage(page, queryWrapper);
|
||||||
aiResourceApprovalStateService.fillWorkflowApprovalState(result.getRecords());
|
aiResourceApprovalStateService.fillWorkflowApprovalState(result.getRecords());
|
||||||
|
aiResourceCreatorNameSupport.fillWorkflowCreatorNames(result.getRecords());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package tech.easyflow.admin.controller.ai.support;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import tech.easyflow.ai.entity.Bot;
|
||||||
|
import tech.easyflow.ai.entity.DocumentCollection;
|
||||||
|
import tech.easyflow.ai.entity.Plugin;
|
||||||
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
|
import tech.easyflow.system.service.SysAccountService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为 AI 资源批量补充创建人展示名称。
|
||||||
|
*
|
||||||
|
* <p>该组件只做展示字段填充,不参与权限或查询逻辑。</p>
|
||||||
|
*
|
||||||
|
* @author Codex
|
||||||
|
* @since 2026-04-12
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AiResourceCreatorNameSupport {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysAccountService sysAccountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量填充工作流创建人名称。
|
||||||
|
*
|
||||||
|
* @param workflows 工作流集合
|
||||||
|
*/
|
||||||
|
public void fillWorkflowCreatorNames(Collection<Workflow> workflows) {
|
||||||
|
fillCreatorNames(workflows, Workflow::getCreatedBy, Workflow::setCreatedByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量填充聊天助手创建人名称。
|
||||||
|
*
|
||||||
|
* @param bots 聊天助手集合
|
||||||
|
*/
|
||||||
|
public void fillBotCreatorNames(Collection<Bot> bots) {
|
||||||
|
fillCreatorNames(bots, Bot::getCreatedBy, Bot::setCreatedByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量填充知识库创建人名称。
|
||||||
|
*
|
||||||
|
* @param collections 知识库集合
|
||||||
|
*/
|
||||||
|
public void fillDocumentCollectionCreatorNames(Collection<DocumentCollection> collections) {
|
||||||
|
fillCreatorNames(collections, DocumentCollection::getCreatedBy, DocumentCollection::setCreatedByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量填充插件创建人名称。
|
||||||
|
*
|
||||||
|
* @param plugins 插件集合
|
||||||
|
*/
|
||||||
|
public void fillPluginCreatorNames(Collection<Plugin> plugins) {
|
||||||
|
fillCreatorNames(plugins, Plugin::getCreatedBy, Plugin::setCreatedByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的创建人名称填充逻辑。
|
||||||
|
*
|
||||||
|
* @param resources 资源集合
|
||||||
|
* @param createdByGetter 创建人 ID 提取函数
|
||||||
|
* @param createdByNameSetter 创建人名称回填函数
|
||||||
|
* @param <T> 资源类型
|
||||||
|
*/
|
||||||
|
private <T> void fillCreatorNames(
|
||||||
|
Collection<T> resources,
|
||||||
|
Function<T, Number> createdByGetter,
|
||||||
|
BiConsumer<T, String> createdByNameSetter
|
||||||
|
) {
|
||||||
|
if (resources == null || resources.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LinkedHashSet<BigInteger> creatorIds = resources.stream()
|
||||||
|
.map(createdByGetter)
|
||||||
|
.map(this::toBigInteger)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
if (creatorIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<BigInteger, String> displayNameMap = sysAccountService.resolveDisplayNameMap(creatorIds);
|
||||||
|
for (T resource : resources) {
|
||||||
|
BigInteger creatorId = toBigInteger(createdByGetter.apply(resource));
|
||||||
|
if (creatorId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
createdByNameSetter.accept(resource, displayNameMap.getOrDefault(creatorId, creatorId.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一把不同数值类型转换为 {@link BigInteger}。
|
||||||
|
*
|
||||||
|
* @param value 原始数值
|
||||||
|
* @return 归一化后的 {@link BigInteger}
|
||||||
|
*/
|
||||||
|
private BigInteger toBigInteger(Number value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof BigInteger) {
|
||||||
|
return (BigInteger) value;
|
||||||
|
}
|
||||||
|
return BigInteger.valueOf(value.longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package tech.easyflow.admin.controller.ai;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import org.testng.annotations.BeforeMethod;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||||
|
import tech.easyflow.ai.entity.Bot;
|
||||||
|
import tech.easyflow.ai.service.AiResourceApprovalStateService;
|
||||||
|
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||||
|
import tech.easyflow.ai.service.BotMessageService;
|
||||||
|
import tech.easyflow.ai.service.BotService;
|
||||||
|
import tech.easyflow.ai.service.BotWorkflowService;
|
||||||
|
import tech.easyflow.ai.service.ModelService;
|
||||||
|
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||||
|
import tech.easyflow.system.service.CategoryPermissionService;
|
||||||
|
import tech.easyflow.system.service.SysAccountService;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doNothing;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BotController} 测试。
|
||||||
|
*/
|
||||||
|
public class BotControllerTest {
|
||||||
|
|
||||||
|
private BotService botService;
|
||||||
|
private ModelService modelService;
|
||||||
|
private BotWorkflowService botWorkflowService;
|
||||||
|
private BotDocumentCollectionService botDocumentCollectionService;
|
||||||
|
private BotMessageService botMessageService;
|
||||||
|
private CategoryPermissionService categoryPermissionService;
|
||||||
|
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
||||||
|
private SysAccountService sysAccountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化测试 mock。
|
||||||
|
*/
|
||||||
|
@BeforeMethod
|
||||||
|
public void setUp() {
|
||||||
|
botService = mock(BotService.class);
|
||||||
|
modelService = mock(ModelService.class);
|
||||||
|
botWorkflowService = mock(BotWorkflowService.class);
|
||||||
|
botDocumentCollectionService = mock(BotDocumentCollectionService.class);
|
||||||
|
botMessageService = mock(BotMessageService.class);
|
||||||
|
categoryPermissionService = mock(CategoryPermissionService.class);
|
||||||
|
aiResourceApprovalStateService = mock(AiResourceApprovalStateService.class);
|
||||||
|
sysAccountService = mock(SysAccountService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证分页结果会补充创建人展示名称,且只做一次批量账号查询。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldFillCreatedByNameForPageRecords() {
|
||||||
|
TestBotController controller = new TestBotController(
|
||||||
|
botService,
|
||||||
|
modelService,
|
||||||
|
botWorkflowService,
|
||||||
|
botDocumentCollectionService,
|
||||||
|
botMessageService
|
||||||
|
);
|
||||||
|
AiResourceCreatorNameSupport creatorNameSupport = new AiResourceCreatorNameSupport();
|
||||||
|
setField(creatorNameSupport, "sysAccountService", sysAccountService);
|
||||||
|
setField(controller, "categoryPermissionService", categoryPermissionService);
|
||||||
|
setField(controller, "aiResourceApprovalStateService", aiResourceApprovalStateService);
|
||||||
|
setField(controller, "aiResourceCreatorNameSupport", creatorNameSupport);
|
||||||
|
|
||||||
|
Bot bot = new Bot();
|
||||||
|
bot.setId(BigInteger.valueOf(101));
|
||||||
|
bot.setCreatedBy(BigInteger.valueOf(7));
|
||||||
|
Page<Bot> page = new Page<>(Collections.singletonList(bot), 1, 10, 1);
|
||||||
|
|
||||||
|
when(categoryPermissionService.getCurrentAccess("BOT"))
|
||||||
|
.thenReturn(new RoleCategoryAccessSnapshot("BOT", BigInteger.ONE, false, true, Collections.emptySet()));
|
||||||
|
when(botService.page(any(Page.class), any(QueryWrapper.class))).thenReturn(page);
|
||||||
|
when(sysAccountService.resolveDisplayNameMap(Collections.singleton(BigInteger.valueOf(7))))
|
||||||
|
.thenReturn(Map.of(BigInteger.valueOf(7), "管理员"));
|
||||||
|
doNothing().when(aiResourceApprovalStateService).fillBotApprovalState(page.getRecords());
|
||||||
|
|
||||||
|
Page<Bot> result = controller.invokeQueryPage(new Page<>(1, 10), QueryWrapper.create());
|
||||||
|
|
||||||
|
Assert.assertEquals(result.getRecords().get(0).getCreatedByName(), "管理员");
|
||||||
|
verify(sysAccountService).resolveDisplayNameMap(Collections.singleton(BigInteger.valueOf(7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过反射设置字段值。
|
||||||
|
*
|
||||||
|
* @param target 目标对象
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @param value 字段值
|
||||||
|
*/
|
||||||
|
private static void setField(Object target, String fieldName, Object value) {
|
||||||
|
Class<?> current = target.getClass();
|
||||||
|
while (current != null) {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field field = current.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(target, value);
|
||||||
|
return;
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
current = current.getSuperclass();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException("设置测试字段失败: " + fieldName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("未找到字段: " + fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暴露受保护分页方法的测试控制器。
|
||||||
|
*/
|
||||||
|
private static class TestBotController extends BotController {
|
||||||
|
|
||||||
|
TestBotController(
|
||||||
|
BotService service,
|
||||||
|
ModelService modelService,
|
||||||
|
BotWorkflowService botWorkflowService,
|
||||||
|
BotDocumentCollectionService botDocumentCollectionService,
|
||||||
|
BotMessageService botMessageService
|
||||||
|
) {
|
||||||
|
super(service, modelService, botWorkflowService, botDocumentCollectionService, botMessageService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用受保护的分页查询方法。
|
||||||
|
*
|
||||||
|
* @param page 分页对象
|
||||||
|
* @param queryWrapper 查询条件
|
||||||
|
* @return 查询结果
|
||||||
|
*/
|
||||||
|
Page<Bot> invokeQueryPage(Page<Bot> page, QueryWrapper queryWrapper) {
|
||||||
|
return super.queryPage(page, queryWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ public class Bot extends BotBase {
|
|||||||
@Column(ignore = true)
|
@Column(ignore = true)
|
||||||
private String displayPublishStatus;
|
private String displayPublishStatus;
|
||||||
|
|
||||||
|
@Column(ignore = true)
|
||||||
|
private String createdByName;
|
||||||
|
|
||||||
public boolean isAnonymousEnabled() {
|
public boolean isAnonymousEnabled() {
|
||||||
Map<String, Object> options = getOptions();
|
Map<String, Object> options = getOptions();
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
@@ -62,4 +65,22 @@ public class Bot extends BotBase {
|
|||||||
this.displayPublishStatus = displayPublishStatus;
|
this.displayPublishStatus = displayPublishStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取创建人展示名称。
|
||||||
|
*
|
||||||
|
* @return 创建人展示名称
|
||||||
|
*/
|
||||||
|
public String getCreatedByName() {
|
||||||
|
return createdByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置创建人展示名称。
|
||||||
|
*
|
||||||
|
* @param createdByName 创建人展示名称
|
||||||
|
*/
|
||||||
|
public void setCreatedByName(String createdByName) {
|
||||||
|
this.createdByName = createdByName;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class DocumentCollection extends DocumentCollectionBase implements Visibi
|
|||||||
@Column(ignore = true)
|
@Column(ignore = true)
|
||||||
private String displayPublishStatus;
|
private String displayPublishStatus;
|
||||||
|
|
||||||
|
@Column(ignore = true)
|
||||||
|
private String createdByName;
|
||||||
|
|
||||||
public static final String TYPE_DOCUMENT = "DOCUMENT";
|
public static final String TYPE_DOCUMENT = "DOCUMENT";
|
||||||
public static final String TYPE_FAQ = "FAQ";
|
public static final String TYPE_FAQ = "FAQ";
|
||||||
|
|
||||||
@@ -169,4 +172,22 @@ public class DocumentCollection extends DocumentCollectionBase implements Visibi
|
|||||||
public void setDisplayPublishStatus(String displayPublishStatus) {
|
public void setDisplayPublishStatus(String displayPublishStatus) {
|
||||||
this.displayPublishStatus = displayPublishStatus;
|
this.displayPublishStatus = displayPublishStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取创建人展示名称。
|
||||||
|
*
|
||||||
|
* @return 创建人展示名称
|
||||||
|
*/
|
||||||
|
public String getCreatedByName() {
|
||||||
|
return createdByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置创建人展示名称。
|
||||||
|
*
|
||||||
|
* @param createdByName 创建人展示名称
|
||||||
|
*/
|
||||||
|
public void setCreatedByName(String createdByName) {
|
||||||
|
this.createdByName = createdByName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public class Plugin extends PluginBase {
|
|||||||
@com.mybatisflex.annotation.Column(ignore = true)
|
@com.mybatisflex.annotation.Column(ignore = true)
|
||||||
private String reasonMessage;
|
private String reasonMessage;
|
||||||
|
|
||||||
|
@com.mybatisflex.annotation.Column(ignore = true)
|
||||||
|
private String createdByName;
|
||||||
|
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return this.getName();
|
return this.getName();
|
||||||
}
|
}
|
||||||
@@ -74,4 +77,22 @@ public class Plugin extends PluginBase {
|
|||||||
public void setReasonMessage(String reasonMessage) {
|
public void setReasonMessage(String reasonMessage) {
|
||||||
this.reasonMessage = reasonMessage;
|
this.reasonMessage = reasonMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取创建人展示名称。
|
||||||
|
*
|
||||||
|
* @return 创建人展示名称
|
||||||
|
*/
|
||||||
|
public String getCreatedByName() {
|
||||||
|
return createdByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置创建人展示名称。
|
||||||
|
*
|
||||||
|
* @param createdByName 创建人展示名称
|
||||||
|
*/
|
||||||
|
public void setCreatedByName(String createdByName) {
|
||||||
|
this.createdByName = createdByName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public class Workflow extends WorkflowBase implements VisibilityResource {
|
|||||||
@Column(ignore = true)
|
@Column(ignore = true)
|
||||||
private String displayPublishStatus;
|
private String displayPublishStatus;
|
||||||
|
|
||||||
|
@Column(ignore = true)
|
||||||
|
private String createdByName;
|
||||||
|
|
||||||
public Tool toFunction(boolean needEnglishName) {
|
public Tool toFunction(boolean needEnglishName) {
|
||||||
return new WorkflowTool(this, needEnglishName);
|
return new WorkflowTool(this, needEnglishName);
|
||||||
}
|
}
|
||||||
@@ -57,4 +60,22 @@ public class Workflow extends WorkflowBase implements VisibilityResource {
|
|||||||
public void setDisplayPublishStatus(String displayPublishStatus) {
|
public void setDisplayPublishStatus(String displayPublishStatus) {
|
||||||
this.displayPublishStatus = displayPublishStatus;
|
this.displayPublishStatus = displayPublishStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取创建人展示名称。
|
||||||
|
*
|
||||||
|
* @return 创建人展示名称
|
||||||
|
*/
|
||||||
|
public String getCreatedByName() {
|
||||||
|
return createdByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置创建人展示名称。
|
||||||
|
*
|
||||||
|
* @param createdByName 创建人展示名称
|
||||||
|
*/
|
||||||
|
public void setCreatedByName(String createdByName) {
|
||||||
|
this.createdByName = createdByName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,17 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-module-log</artifactId>
|
<artifactId>easyflow-module-log</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>5.12.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import tech.easyflow.system.entity.vo.SysAccountImportResultVo;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户表 服务层。
|
* 用户表 服务层。
|
||||||
@@ -19,6 +20,14 @@ import java.util.Collection;
|
|||||||
*/
|
*/
|
||||||
public interface SysAccountService extends IService<SysAccount> {
|
public interface SysAccountService extends IService<SysAccount> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量解析账号展示名称。
|
||||||
|
*
|
||||||
|
* @param accountIds 账号 ID 集合
|
||||||
|
* @return 账号 ID 到展示名称的映射,名称优先使用昵称,其次登录名,最后回退为 ID 字符串
|
||||||
|
*/
|
||||||
|
Map<BigInteger, String> resolveDisplayNameMap(Collection<BigInteger> accountIds);
|
||||||
|
|
||||||
void syncRelations(SysAccount entity);
|
void syncRelations(SysAccount entity);
|
||||||
|
|
||||||
SysAccount getByUsername(String userKey);
|
SysAccount getByUsername(String userKey);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@@ -110,6 +111,57 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
|||||||
@Resource
|
@Resource
|
||||||
private PlatformTransactionManager transactionManager;
|
private PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量解析账号展示名称。
|
||||||
|
*
|
||||||
|
* @param accountIds 账号 ID 集合
|
||||||
|
* @return 账号 ID 到展示名称的映射
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<BigInteger, String> resolveDisplayNameMap(Collection<BigInteger> accountIds) {
|
||||||
|
if (accountIds == null || accountIds.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
LinkedHashSet<BigInteger> normalizedIds = accountIds.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
if (normalizedIds.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.select(SysAccount::getId, SysAccount::getNickname, SysAccount::getLoginName)
|
||||||
|
.in(SysAccount::getId, normalizedIds);
|
||||||
|
List<SysAccount> accounts = list(queryWrapper);
|
||||||
|
LinkedHashMap<BigInteger, String> displayNameMap = new LinkedHashMap<>(normalizedIds.size());
|
||||||
|
for (SysAccount account : accounts) {
|
||||||
|
if (account == null || account.getId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
displayNameMap.put(account.getId(), resolveDisplayName(account));
|
||||||
|
}
|
||||||
|
normalizedIds.forEach(id -> displayNameMap.putIfAbsent(id, id.toString()));
|
||||||
|
return displayNameMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析单个账号的展示名称。
|
||||||
|
*
|
||||||
|
* @param account 账号实体
|
||||||
|
* @return 展示名称
|
||||||
|
*/
|
||||||
|
private String resolveDisplayName(SysAccount account) {
|
||||||
|
String nickname = trimToNull(account.getNickname());
|
||||||
|
if (StringUtil.hasText(nickname)) {
|
||||||
|
return nickname;
|
||||||
|
}
|
||||||
|
String loginName = trimToNull(account.getLoginName());
|
||||||
|
if (StringUtil.hasText(loginName)) {
|
||||||
|
return loginName;
|
||||||
|
}
|
||||||
|
return account.getId() == null ? "" : account.getId().toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void syncRelations(SysAccount entity) {
|
public void syncRelations(SysAccount entity) {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package tech.easyflow.system.service.impl;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import tech.easyflow.system.entity.SysAccount;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SysAccountServiceImpl} 测试。
|
||||||
|
*/
|
||||||
|
public class SysAccountServiceImplTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证展示名解析优先级与缺失数据回退。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldResolveDisplayNameByNicknameThenLoginNameThenId() {
|
||||||
|
SysAccountServiceImpl service = spy(new SysAccountServiceImpl());
|
||||||
|
|
||||||
|
SysAccount nicknameAccount = new SysAccount();
|
||||||
|
nicknameAccount.setId(BigInteger.ONE);
|
||||||
|
nicknameAccount.setNickname("管理员");
|
||||||
|
nicknameAccount.setLoginName("admin");
|
||||||
|
|
||||||
|
SysAccount loginNameAccount = new SysAccount();
|
||||||
|
loginNameAccount.setId(BigInteger.valueOf(2));
|
||||||
|
loginNameAccount.setNickname(" ");
|
||||||
|
loginNameAccount.setLoginName("operator");
|
||||||
|
|
||||||
|
doReturn(List.of(nicknameAccount, loginNameAccount))
|
||||||
|
.when(service)
|
||||||
|
.list(any(QueryWrapper.class));
|
||||||
|
|
||||||
|
Map<BigInteger, String> displayNameMap = service.resolveDisplayNameMap(
|
||||||
|
List.of(BigInteger.ONE, BigInteger.valueOf(2), BigInteger.valueOf(3))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("管理员", displayNameMap.get(BigInteger.ONE));
|
||||||
|
assertEquals("operator", displayNameMap.get(BigInteger.valueOf(2)));
|
||||||
|
assertEquals("3", displayNameMap.get(BigInteger.valueOf(3)));
|
||||||
|
verify(service, times(1)).list(any(QueryWrapper.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
ElText,
|
ElText,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export type ActionPlacement = 'inline' | 'menu';
|
export type ActionPlacement = 'inline' | 'menu';
|
||||||
export type ActionTone = 'danger' | 'default';
|
export type ActionTone = 'danger' | 'default';
|
||||||
|
|
||||||
@@ -147,6 +149,23 @@ function hasVisibleActions(item: any) {
|
|||||||
resolveInlineActions(item).length > 0 || resolveMenuActions(item).length > 0
|
resolveInlineActions(item).length > 0 || resolveMenuActions(item).length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveMetaItems(item: any) {
|
||||||
|
const metaItems: Array<{ label: string; value: string }> = [];
|
||||||
|
if (item.createdByName) {
|
||||||
|
metaItems.push({
|
||||||
|
label: $t('aiResource.createdBy'),
|
||||||
|
value: String(item.createdByName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.created) {
|
||||||
|
metaItems.push({
|
||||||
|
label: $t('aiResource.created'),
|
||||||
|
value: String(item.created),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return metaItems;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -203,6 +222,19 @@ function hasVisibleActions(item: any) {
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="resolveMetaItems(item).length > 0"
|
||||||
|
class="card-meta-row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-for="meta in resolveMetaItems(item)"
|
||||||
|
:key="meta.label"
|
||||||
|
class="card-meta-item"
|
||||||
|
>
|
||||||
|
<span class="card-meta-label">{{ meta.label }}:</span>
|
||||||
|
<span class="card-meta-value">{{ meta.value }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -355,13 +387,17 @@ function hasVisibleActions(item: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: grid;
|
||||||
gap: 14px;
|
grid-template-columns: 44px minmax(0, 1fr) auto;
|
||||||
|
column-gap: 14px;
|
||||||
|
row-gap: 10px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-avatar {
|
.card-avatar {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
background: hsl(var(--surface-subtle));
|
background: hsl(var(--surface-subtle));
|
||||||
border: 1px solid hsl(var(--line-subtle));
|
border: 1px solid hsl(var(--line-subtle));
|
||||||
}
|
}
|
||||||
@@ -371,6 +407,8 @@ function hasVisibleActions(item: any) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,8 +432,41 @@ function hasVisibleActions(item: any) {
|
|||||||
color: hsl(var(--text-muted));
|
color: hsl(var(--text-muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-meta-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: hsl(var(--text-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta-value {
|
||||||
|
min-width: 0;
|
||||||
|
text-align: left;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.card-corner-tag {
|
.card-corner-tag {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
grid-column: 3;
|
||||||
|
grid-row: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ vi.mock('@easyflow/access', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('#/locales', () => ({
|
||||||
|
$t: (key: string) => {
|
||||||
|
const labelMap: Record<string, string> = {
|
||||||
|
'aiResource.created': '创建时间',
|
||||||
|
'aiResource.createdBy': '创建者',
|
||||||
|
};
|
||||||
|
return labelMap[key] || key;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('cardList', () => {
|
describe('cardList', () => {
|
||||||
function mountCardList(props: Record<string, unknown>) {
|
function mountCardList(props: Record<string, unknown>) {
|
||||||
return mount(CardList, {
|
return mount(CardList, {
|
||||||
@@ -110,4 +120,55 @@ describe('cardList', () => {
|
|||||||
expect(wrapper.get('.card-item').attributes('role')).toBeUndefined();
|
expect(wrapper.get('.card-item').attributes('role')).toBeUndefined();
|
||||||
expect(legacyAction).toHaveBeenCalledTimes(1);
|
expect(legacyAction).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('同时展示创建人与创建时间', () => {
|
||||||
|
const wrapper = mountCardList({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'bot-1',
|
||||||
|
title: '演示卡片',
|
||||||
|
description: '用于验证元信息展示',
|
||||||
|
createdByName: '管理员',
|
||||||
|
created: '2026-04-12 10:00:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const metaText = wrapper.get('.card-meta-row').text();
|
||||||
|
|
||||||
|
expect(metaText).toContain('创建者:管理员');
|
||||||
|
expect(metaText).toContain('创建时间:2026-04-12 10:00:00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('缺少创建人时仅展示创建时间', () => {
|
||||||
|
const wrapper = mountCardList({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'bot-1',
|
||||||
|
title: '演示卡片',
|
||||||
|
description: '用于验证元信息展示',
|
||||||
|
created: '2026-04-12 10:00:00',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const metaText = wrapper.get('.card-meta-row').text();
|
||||||
|
|
||||||
|
expect(metaText).toContain('创建时间:2026-04-12 10:00:00');
|
||||||
|
expect(metaText).not.toContain('创建者:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('缺少创建信息时不渲染元信息区', () => {
|
||||||
|
const wrapper = mountCardList({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 'bot-1',
|
||||||
|
title: '演示卡片',
|
||||||
|
description: '用于验证元信息展示',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('.card-meta-row').exists()).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user