fix: 修复入库可能重复;筛选逻辑优化 (#226)

* 修改数据清洗筛选逻辑-筛选修改为多选

* 修改数据清洗筛选逻辑-筛选修改为多选

* antd 组件库样式定制修改

* fix: 修复入库可能重复

* fix: 算子市场筛选逻辑优化

* fix: 清洗任务创建筛选逻辑优化

* fix: 清洗任务创建筛选逻辑优化

---------

Co-authored-by: chase <byzhangxin11@126.com>
This commit is contained in:
hhhhsc701
2026-01-06 17:57:25 +08:00
committed by GitHub
parent 49cc98941f
commit 7d4dcb756b
19 changed files with 247 additions and 152 deletions

View File

@@ -1,10 +1,8 @@
package com.datamate.operator.application; package com.datamate.operator.application;
import com.datamate.operator.domain.contants.OperatorConstant;
import com.datamate.operator.domain.repository.CategoryRelationRepository; import com.datamate.operator.domain.repository.CategoryRelationRepository;
import com.datamate.operator.domain.repository.CategoryRepository; import com.datamate.operator.domain.repository.CategoryRepository;
import com.datamate.operator.domain.repository.OperatorRepository;
import com.datamate.operator.interfaces.dto.CategoryDto; import com.datamate.operator.interfaces.dto.CategoryDto;
import com.datamate.operator.interfaces.dto.CategoryRelationDto; import com.datamate.operator.interfaces.dto.CategoryRelationDto;
import com.datamate.operator.interfaces.dto.CategoryTreeResponse; import com.datamate.operator.interfaces.dto.CategoryTreeResponse;
@@ -21,7 +19,7 @@ import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class CategoryService { public class CategoryService {
private final OperatorRepository operatorRepo;
private final CategoryRepository categoryRepo; private final CategoryRepository categoryRepo;
@@ -42,7 +40,7 @@ public class CategoryService {
.filter(relation -> !StringUtils.equals(relation.getParentId(), "0")) .filter(relation -> !StringUtils.equals(relation.getParentId(), "0"))
.collect(Collectors.groupingBy(CategoryDto::getParentId)); .collect(Collectors.groupingBy(CategoryDto::getParentId));
List<CategoryTreeResponse> categoryTreeResponses = groupedByParentId.entrySet().stream() return groupedByParentId.entrySet().stream()
.sorted(categoryComparator(nameMap)) .sorted(categoryComparator(nameMap))
.map(entry -> { .map(entry -> {
String parentId = entry.getKey(); String parentId = entry.getKey();
@@ -58,10 +56,6 @@ public class CategoryService {
response.setCount(totalCount.get()); response.setCount(totalCount.get());
return response; return response;
}).collect(Collectors.toCollection(ArrayList::new)); }).collect(Collectors.toCollection(ArrayList::new));
int stars = operatorRepo.countOperatorByStar(true);
categoryTreeResponses.add(buildStarCategoryTree(stars));
return categoryTreeResponses;
} }
private Comparator<Map.Entry<String, List<CategoryDto>>> categoryComparator(Map<String, CategoryDto> categoryMap) { private Comparator<Map.Entry<String, List<CategoryDto>>> categoryComparator(Map<String, CategoryDto> categoryMap) {
@@ -71,21 +65,4 @@ public class CategoryService {
return index1.compareTo(index2); return index1.compareTo(index2);
}; };
} }
private CategoryTreeResponse buildStarCategoryTree(int stars) {
CategoryTreeResponse starResponse = new CategoryTreeResponse();
starResponse.setName("收藏状态");
starResponse.setCount(stars);
starResponse.setId("257b27e0-bba9-11f0-89d7-00155d0a6153");
CategoryDto star = new CategoryDto();
star.setId(OperatorConstant.CATEGORY_STAR_ID);
star.setName("已收藏");
star.setValue("isStar");
star.setCount(stars);
star.setParentId("257b27e0-bba9-11f0-89d7-00155d0a6153");
star.setCreatedAt(LocalDateTime.now());
star.setType("predefined");
starResponse.setCategories(Collections.singletonList(star));
return starResponse;
}
} }

View File

@@ -52,12 +52,12 @@ public class OperatorService {
@Value("${operator.base.path:/operators}") @Value("${operator.base.path:/operators}")
private String operatorBasePath; private String operatorBasePath;
public List<OperatorDto> getOperators(Integer page, Integer size, List<String> categories, public List<OperatorDto> getOperators(Integer page, Integer size, List<List<String>> categories,
String keyword, Boolean isStar) { String keyword, Boolean isStar) {
return operatorViewRepo.findOperatorsByCriteria(page, size, keyword, categories, isStar); return operatorViewRepo.findOperatorsByCriteria(page, size, keyword, categories, isStar);
} }
public int getOperatorsCount(List<String> categories, String keyword, Boolean isStar) { public int getOperatorsCount(List<List<String>> categories, String keyword, Boolean isStar) {
return operatorViewRepo.countOperatorsByCriteria(keyword, categories, isStar); return operatorViewRepo.countOperatorsByCriteria(keyword, categories, isStar);
} }

View File

@@ -8,9 +8,9 @@ import java.util.List;
public interface OperatorViewRepository extends IRepository<OperatorView> { public interface OperatorViewRepository extends IRepository<OperatorView> {
List<OperatorDto> findOperatorsByCriteria(Integer page, Integer size, String keyword, List<OperatorDto> findOperatorsByCriteria(Integer page, Integer size, String keyword,
List<String> categories, Boolean isStar); List<List<String>> categories, Boolean isStar);
Integer countOperatorsByCriteria(String keyword, List<String> categories, Boolean isStar); int countOperatorsByCriteria(String keyword, List<List<String>> categories, Boolean isStar);
OperatorView findOperatorById(String id); OperatorView findOperatorById(String id);
} }

View File

@@ -16,6 +16,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Repository @Repository
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -24,18 +25,36 @@ public class OperatorViewRepositoryImpl extends CrudRepository<OperatorViewMappe
@Override @Override
public List<OperatorDto> findOperatorsByCriteria(Integer page, Integer size, String keyword, public List<OperatorDto> findOperatorsByCriteria(Integer page, Integer size, String keyword,
List<String> categories, Boolean isStar) { List<List<String>> categories, Boolean isStar) {
QueryWrapper<OperatorView> queryWrapper = Wrappers.query(); QueryWrapper<OperatorView> queryWrapper = Wrappers.query();
queryWrapper.in(CollectionUtils.isNotEmpty(categories), "category_id", categories) queryWrapper.eq(isStar != null, "is_star", isStar);
.eq(isStar != null, "is_star", isStar);
if (StringUtils.isNotEmpty(keyword)) { if (StringUtils.isNotEmpty(keyword)) {
queryWrapper.and(w -> queryWrapper.and(w ->
w.like("operator_name", keyword) w.like("operator_name", keyword)
.or() .or()
.like("description", keyword)); .like("description", keyword));
} }
StringBuilder havingSql = new StringBuilder();
if (CollectionUtils.isNotEmpty(categories)) {
queryWrapper.in("category_id", categories.stream().flatMap(List::stream).toList());
int index = 0;
for (List<String> category : categories) {
if (index > 0) {
havingSql.append(" AND ");
}
havingSql.append("SUM(CASE WHEN category_id IN (");
havingSql.append(category.stream()
.map(id -> "'" + id + "'")
.collect(Collectors.joining(",")));
havingSql.append(") THEN 1 ELSE 0 END) > 0");
index++;
}
}
queryWrapper.groupBy("operator_id") queryWrapper.groupBy("operator_id")
.having(!havingSql.isEmpty(), havingSql.toString())
.orderByDesc("created_at"); .orderByDesc("created_at");
Page<OperatorView> queryPage; Page<OperatorView> queryPage;
if (size != null && page != null) { if (size != null && page != null) {
queryPage = new Page<>(page + 1, size); queryPage = new Page<>(page + 1, size);
@@ -48,17 +67,35 @@ public class OperatorViewRepositoryImpl extends CrudRepository<OperatorViewMappe
} }
@Override @Override
public Integer countOperatorsByCriteria(String keyword, List<String> categories, Boolean isStar) { public int countOperatorsByCriteria(String keyword, List<List<String>> categories, Boolean isStar) {
QueryWrapper<OperatorView> queryWrapper = Wrappers.query(); QueryWrapper<OperatorView> queryWrapper = Wrappers.query();
queryWrapper.in(CollectionUtils.isNotEmpty(categories),"category_id", categories) queryWrapper.eq(isStar != null, "is_star", isStar);
.eq(isStar != null, "is_star", isStar);
if (StringUtils.isNotEmpty(keyword)) { if (StringUtils.isNotEmpty(keyword)) {
queryWrapper.and(w -> queryWrapper.and(w ->
w.like("operator_name", keyword) w.like("operator_name", keyword)
.or() .or()
.like("description", keyword)); .like("description", keyword));
} }
return mapper.countOperatorsByCriteria(queryWrapper); StringBuilder havingSql = new StringBuilder();
if (CollectionUtils.isNotEmpty(categories)) {
queryWrapper.in("category_id", categories.stream().flatMap(List::stream).toList());
int index = 0;
for (List<String> category : categories) {
if (index > 0) {
havingSql.append(" AND ");
}
havingSql.append("SUM(CASE WHEN category_id IN (");
havingSql.append(category.stream()
.map(id -> "'" + id + "'")
.collect(Collectors.joining(",")));
havingSql.append(") THEN 1 ELSE 0 END) > 0");
index++;
}
}
queryWrapper.groupBy("operator_id")
.having(!havingSql.isEmpty(), havingSql.toString());
Integer count = mapper.countOperatorsByCriteria(queryWrapper);
return count != null ? count : 0;
} }
@Override @Override

View File

@@ -19,7 +19,7 @@ public interface OperatorViewMapper extends BaseMapper<OperatorView> {
IPage<OperatorView> findOperatorsByCriteria(IPage<OperatorView> page, IPage<OperatorView> findOperatorsByCriteria(IPage<OperatorView> page,
@Param(Constants.WRAPPER) Wrapper<OperatorView> queryWrapper); @Param(Constants.WRAPPER) Wrapper<OperatorView> queryWrapper);
@Select("SELECT COUNT(DISTINCT operator_id) AS count FROM v_operator ${ew.customSqlSegment}") @Select("SELECT COUNT(1) FROM (SELECT 1 FROM v_operator ${ew.customSqlSegment}) AS t")
Integer countOperatorsByCriteria(@Param(Constants.WRAPPER) Wrapper<OperatorView> queryWrapper); Integer countOperatorsByCriteria(@Param(Constants.WRAPPER) Wrapper<OperatorView> queryWrapper);
@Select("SELECT operator_id AS id, operator_name AS name, description, version, inputs, outputs, runtime, " + @Select("SELECT operator_id AS id, operator_name AS name, description, version, inputs, outputs, runtime, " +

View File

@@ -0,0 +1,24 @@
package com.datamate.operator.interfaces.dto;
import com.datamate.common.interfaces.PagedResponse;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
public class CategoryTreePagedResponse extends PagedResponse<CategoryTreeResponse> {
Integer starCount;
public CategoryTreePagedResponse(List<CategoryTreeResponse> content, Integer starCount) {
super(content);
this.starCount = starCount;
}
public static CategoryTreePagedResponse of(List<CategoryTreeResponse> content, Integer starCount) {
return new CategoryTreePagedResponse(content, starCount);
}
}

View File

@@ -15,7 +15,7 @@ import lombok.Setter;
@Getter @Getter
@Setter @Setter
public class OperatorsListPostRequest extends PagingQuery { public class OperatorsListPostRequest extends PagingQuery {
private List<String> categories = new ArrayList<>(); private List<List<String>> categories = new ArrayList<>();
private String keyword; private String keyword;

View File

@@ -2,6 +2,8 @@ package com.datamate.operator.interfaces.rest;
import com.datamate.common.interfaces.PagedResponse; import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.CategoryService; import com.datamate.operator.application.CategoryService;
import com.datamate.operator.domain.repository.OperatorRepository;
import com.datamate.operator.interfaces.dto.CategoryTreePagedResponse;
import com.datamate.operator.interfaces.dto.CategoryTreeResponse; import com.datamate.operator.interfaces.dto.CategoryTreeResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@@ -17,9 +19,11 @@ import java.util.List;
public class CategoryController { public class CategoryController {
private final CategoryService categoryService; private final CategoryService categoryService;
private final OperatorRepository operatorRepo;
@GetMapping("/tree") @GetMapping("/tree")
public PagedResponse<CategoryTreeResponse> categoryTreeGet() { public PagedResponse<CategoryTreeResponse> categoryTreeGet() {
List<CategoryTreeResponse> allCategories = categoryService.getAllCategories(); List<CategoryTreeResponse> allCategories = categoryService.getAllCategories();
return PagedResponse.of(allCategories); return CategoryTreePagedResponse.of(allCategories, operatorRepo.countOperatorByStar(true));
} }
} }

View File

@@ -3,12 +3,10 @@ package com.datamate.operator.interfaces.rest;
import com.datamate.common.infrastructure.common.IgnoreResponseWrap; import com.datamate.common.infrastructure.common.IgnoreResponseWrap;
import com.datamate.common.interfaces.PagedResponse; import com.datamate.common.interfaces.PagedResponse;
import com.datamate.operator.application.OperatorService; import com.datamate.operator.application.OperatorService;
import com.datamate.operator.domain.contants.OperatorConstant;
import com.datamate.operator.interfaces.dto.OperatorDto; import com.datamate.operator.interfaces.dto.OperatorDto;
import com.datamate.operator.interfaces.dto.OperatorsListPostRequest; import com.datamate.operator.interfaces.dto.OperatorsListPostRequest;
import com.datamate.operator.interfaces.dto.UploadOperatorRequest; import com.datamate.operator.interfaces.dto.UploadOperatorRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@@ -27,16 +25,10 @@ public class OperatorController {
@PostMapping("/list") @PostMapping("/list")
public PagedResponse<OperatorDto> operatorsListPost(@RequestBody OperatorsListPostRequest request) { public PagedResponse<OperatorDto> operatorsListPost(@RequestBody OperatorsListPostRequest request) {
Boolean isStar = null; List<List<String>> categories = request.getCategories();
List<String> categories = request.getCategories();
if (CollectionUtils.isNotEmpty(request.getCategories()) &&
request.getCategories().contains(OperatorConstant.CATEGORY_STAR_ID)) {
isStar = true;
categories.remove(OperatorConstant.CATEGORY_STAR_ID);
}
List<OperatorDto> responses = operatorService.getOperators(request.getPage(), request.getSize(), List<OperatorDto> responses = operatorService.getOperators(request.getPage(), request.getSize(),
categories, request.getKeyword(), isStar); categories, request.getKeyword(), request.getIsStar());
int count = operatorService.getOperatorsCount(categories, request.getKeyword(), isStar); int count = operatorService.getOperatorsCount(categories, request.getKeyword(), request.getIsStar());
int totalPages = (count + request.getSize() + 1) / request.getSize(); int totalPages = (count + request.getSize() + 1) / request.getSize();
return PagedResponse.of(responses, request.getPage(), count, totalPages); return PagedResponse.of(responses, request.getPage(), count, totalPages);
} }

View File

@@ -44,7 +44,8 @@ export default function useFetchData<T>(
status: [] as string[], status: [] as string[],
tags: [] as string[], tags: [] as string[],
// 通用分类筛选(如算子市场的分类 ID 列表) // 通用分类筛选(如算子市场的分类 ID 列表)
categories: [] as string[], categories: [] as string[][],
selectedStar: false,
}, },
current: 1, current: 1,
pageSize: 12, pageSize: 12,
@@ -113,11 +114,10 @@ export default function useFetchData<T>(
// 同时执行主要数据获取和额外的轮询函数 // 同时执行主要数据获取和额外的轮询函数
const promises = [ const promises = [
fetchFunc({ fetchFunc({
...Object.fromEntries( categories: filter.categories,
Object.entries(filter).filter(([_, value]) => value != null && value.length > 0)
),
...extraParams, ...extraParams,
keyword, keyword,
isStar: filter.selectedStar ? true : undefined,
type: getFirstOfArray(filter?.type) || undefined, type: getFirstOfArray(filter?.type) || undefined,
status: getFirstOfArray(filter?.status) || undefined, status: getFirstOfArray(filter?.status) || undefined,
tags: filter?.tags?.length ? filter.tags.join(",") : undefined, tags: filter?.tags?.length ? filter.tags.join(",") : undefined,

View File

@@ -2,21 +2,24 @@ import { StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router"; import { RouterProvider } from "react-router";
import router from "./routes/routes"; import router from "./routes/routes";
import { App as AntdApp, Spin } from "antd"; import { App as AntdApp, Spin, ConfigProvider } from "antd";
import "./index.css"; import "./index.css";
import TopLoadingBar from "./components/TopLoadingBar"; import TopLoadingBar from "./components/TopLoadingBar";
import { store } from "./store"; import { store } from "./store";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import theme from "./theme";
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<Provider store={store}> <Provider store={store}>
<ConfigProvider theme={ theme }>
<AntdApp> <AntdApp>
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<TopLoadingBar /> <TopLoadingBar />
<RouterProvider router={router} /> <RouterProvider router={router} />
</Suspense> </Suspense>
</AntdApp> </AntdApp>
</ConfigProvider>
</Provider> </Provider>
</StrictMode> </StrictMode>
); );

View File

@@ -100,57 +100,58 @@ const OperatorLibrary: React.FC<OperatorLibraryProps> = ({
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [showFavorites, setShowFavorites] = useState(false); const [showFavorites, setShowFavorites] = useState(false);
const [favorites, setFavorites] = useState<Set<string>>(new Set()); const [favorites, setFavorites] = useState<Set<string>>(new Set());
const [selectedCategory, setSelectedCategory] = useState<string>("all"); const [selectedCategory, setSelectedCategory] = useState<string[]>([]);
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
new Set([])
);
const [operatorListFiltered, setOperatorListFiltered] = useState<OperatorI[]>([]);
// 按分类分组 // 按分类分组
const groupedOperators = useMemo(() => { const groupedOperators = useMemo(() => {
const groups: { [key: string]: OperatorI[] } = {}; const groups: { [key: string]: any[] } = {};
let operatorFilteredList: OperatorI[];
categoryOptions.forEach((cat: any) => { categoryOptions.forEach((cat: any) => {
groups[cat.name] = { groups[cat.id] = {
...cat, ...cat,
operators: operatorList.filter((op) => op.categories?.includes(cat.id)), operators: operatorList.filter((op) => op.categories?.includes(cat.id)),
}; };
}); });
if (selectedCategory && selectedCategory !== "all") { if (selectedCategory.length) {
Object.keys(groups).forEach((key) => { const groupedFiltered: { [key: string]: any[] } = {};
if (groups[key].id !== selectedCategory) { selectedCategory.forEach((cat: any) => {
delete groups[key]; let parent = groups[cat].type;
if (!groupedFiltered[parent]) {
groupedFiltered[parent] = groups[cat].operators
} else {
groupedFiltered[parent] = Array.from(
new Map([...groupedFiltered[parent], ...groups[cat].operators].map(item => [item.id, item])).values()
);
} }
})
operatorFilteredList = Object.values(groupedFiltered).reduce((acc, currentList) => {
if (acc.length === 0) return [];
const currentIds = new Set(currentList.map(item => item.id));
return acc.filter(item => currentIds.has(item.id));
}); });
} else {
operatorFilteredList = [...operatorList];
} }
if (searchTerm) { if (searchTerm) {
Object.keys(groups).forEach((key) => { operatorFilteredList = operatorFilteredList.filter(operator =>
groups[key].operators = groups[key].operators.filter((operator) =>
operator.name.toLowerCase().includes(searchTerm.toLowerCase()) operator.name.toLowerCase().includes(searchTerm.toLowerCase())
); );
if (groups[key].operators.length === 0) {
delete groups[key];
}
});
} }
if (showFavorites) { if (showFavorites) {
Object.keys(groups).forEach((key) => { operatorFilteredList = operatorFilteredList.filter((operator) =>
groups[key].operators = groups[key].operators.filter((operator) =>
favorites.has(operator.id) favorites.has(operator.id)
); );
if (groups[key].operators.length === 0) {
delete groups[key];
} }
}); setOperatorListFiltered([...operatorFilteredList]);
}
setExpandedCategories(new Set(Object.keys(groups)));
return groups; return groups;
}, [categoryOptions, selectedCategory, searchTerm, showFavorites]); }, [categoryOptions, selectedCategory, searchTerm, showFavorites]);
// 过滤算子 // 过滤算子
const filteredOperators = useMemo(() => { useMemo(() => {
return Object.values(groupedOperators).flatMap( return Object.values(groupedOperators).flatMap(
(category) => category.operators (category) => category.operators
); );
@@ -190,17 +191,37 @@ const OperatorLibrary: React.FC<OperatorLibraryProps> = ({
setSelectedOperators(newSelected); setSelectedOperators(newSelected);
}; };
const handleSelectCategory = (categoryOptions) => {
const groups: Record<string, any> = {};
const tree: any[] = [];
categoryOptions.forEach(item => {
const groupName = item.type;
if (!groups[groupName]) {
const newGroup = {
label: groupName,
title: groupName,
options: []
};
groups[groupName] = newGroup;
tree.push(newGroup);
}
const { type, ...childItem } = item;
groups[groupName].options.push(childItem);
});
return tree;
}
return ( return (
<div className="w-1/4 h-full min-w-3xs flex flex-col"> <div className="w-1/4 h-full min-w-3xs flex flex-col">
<div className="pb-4 border-b border-gray-200"> <div className="pb-4 border-b border-gray-200">
<span className="flex items-center font-semibold text-base"> <span className="flex items-center font-semibold text-base">
<Layers className="w-4 h-4 mr-2" /> <Layers className="w-4 h-4 mr-2" />
({operatorList.length})
</span> </span>
</div> </div>
<div className="flex flex-col h-full pt-4 pr-4 overflow-hidden"> <div className="flex flex-col h-full pt-4 pr-4 overflow-hidden">
{/* 过滤器 */} {/* 过滤器 */}
<div className="flex flex-wrap gap-2 border-b border-gray-100 pb-4"> <div className="flex flex-wrap gap-2 border-b border-gray-100 pb-2">
<Input <Input
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
placeholder="搜索算子名称..." placeholder="搜索算子名称..."
@@ -210,8 +231,10 @@ const OperatorLibrary: React.FC<OperatorLibraryProps> = ({
/> />
<Select <Select
value={selectedCategory} value={selectedCategory}
options={[{ label: "全部分类", value: "all" }, ...categoryOptions]} options={handleSelectCategory(categoryOptions)}
onChange={setSelectedCategory} onChange={setSelectedCategory}
mode="multiple"
allowClear
className="flex-1" className="flex-1"
placeholder="选择分类" placeholder="选择分类"
></Select> ></Select>
@@ -227,53 +250,32 @@ const OperatorLibrary: React.FC<OperatorLibraryProps> = ({
)} )}
</span> </span>
</Tooltip> </Tooltip>
</div> <div className="flex items-center justify-right w-full">
{/* 算子列表 */}
<div className="flex-1 overflow-auto">
{/* 分类算子 */}
<Collapse
ghost
activeKey={Array.from(expandedCategories)}
onChange={(keys) =>
setExpandedCategories(
new Set(Array.isArray(keys) ? keys : [keys])
)
}
>
{Object.entries(groupedOperators).map(([key, category]) => (
<Collapse.Panel
key={key}
header={
<div className="flex items-center justify-between w-full">
<span className="flex items-center gap-2">
<span>{category.name}</span>
<Tag>{category.operators.length}</Tag>
</span>
<Button <Button
type="link" type="link"
size="small" size="small"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleSelectAll(category.operators); handleSelectAll(operatorListFiltered);
}} }}
> >
<Tag>{operatorListFiltered.length}</Tag>
</Button> </Button>
</div> </div>
} </div>
> {/* 算子列表 */}
<div className="flex-1 overflow-auto">
<OperatorList <OperatorList
selectedOperators={selectedOperators} selectedOperators={selectedOperators}
operators={category.operators} operators={operatorListFiltered}
favorites={favorites} favorites={favorites}
toggleOperator={toggleOperator} toggleOperator={toggleOperator}
onDragOperator={handleDragStart} onDragOperator={handleDragStart}
toggleFavorite={toggleFavorite} toggleFavorite={toggleFavorite}
/> />
</Collapse.Panel>
))} {operatorListFiltered.length === 0 && (
</Collapse>
{filteredOperators.length === 0 && (
<div className="text-center py-8 text-gray-400"> <div className="text-center py-8 text-gray-400">
<SearchOutlined className="text-3xl mb-2 opacity-50" /> <SearchOutlined className="text-3xl mb-2 opacity-50" />
<div></div> <div></div>

View File

@@ -37,10 +37,13 @@ export default function OperatorMarketPage() {
const [showFilters, setShowFilters] = useState(true); const [showFilters, setShowFilters] = useState(true);
const [categoriesTree, setCategoriesTree] = useState<CategoryTreeI[]>([]); const [categoriesTree, setCategoriesTree] = useState<CategoryTreeI[]>([]);
const [starCount, setStarCount] = useState(0);
const [selectedStar, setSelectedStar] = useState<boolean>(false);
const initCategoriesTree = async () => { const initCategoriesTree = async () => {
const { data } = await queryCategoryTreeUsingGet({ page: 0, size: 1000 }); const { data } = await queryCategoryTreeUsingGet({ page: 0, size: 1000 });
setCategoriesTree(data.content || []); setCategoriesTree(data.content || []);
setStarCount(data.starCount || 0);
}; };
useEffect(() => { useEffect(() => {
@@ -104,16 +107,7 @@ export default function OperatorMarketPage() {
]; ];
useEffect(() => { useEffect(() => {
const filteredIds = Object.values(selectedFilters).reduce( const filteredIds = Object.values(selectedFilters).filter(item => item.length > 0);
(acc, filter: string[]) => {
if (filter.length) {
acc.push(...filter);
}
return acc;
},
[]
);
// 分类筛选变化时: // 分类筛选变化时:
// 1. 将分类 ID 写入通用 searchParams.filter.categories,确保分页时条件不会丢失 // 1. 将分类 ID 写入通用 searchParams.filter.categories,确保分页时条件不会丢失
@@ -124,9 +118,10 @@ export default function OperatorMarketPage() {
filter: { filter: {
...prev.filter, ...prev.filter,
categories: filteredIds, categories: filteredIds,
selectedStar: selectedStar,
}, },
})); }));
}, [selectedFilters, setSearchParams]); }, [selectedFilters, setSearchParams, selectedStar]);
return ( return (
<div className="h-full flex flex-col gap-4"> <div className="h-full flex flex-col gap-4">
@@ -162,8 +157,11 @@ export default function OperatorMarketPage() {
<Filters <Filters
hideFilter={() => setShowFilters(false)} hideFilter={() => setShowFilters(false)}
categoriesTree={categoriesTree} categoriesTree={categoriesTree}
selectedStar={selectedStar}
starCount={starCount}
selectedFilters={selectedFilters} selectedFilters={selectedFilters}
setSelectedFilters={setSelectedFilters} setSelectedFilters={setSelectedFilters}
setSelectedStar={setSelectedStar}
/> />
</div> </div>
<div className="flex-overflow-auto p-6 "> <div className="flex-overflow-auto p-6 ">

View File

@@ -104,15 +104,21 @@ const FilterSection: React.FC<FilterSectionProps> = ({
interface FiltersProps { interface FiltersProps {
categoriesTree: CategoryTreeI[]; categoriesTree: CategoryTreeI[];
selectedFilters: { [key: string]: string[] }; selectedFilters: { [key: string]: string[] };
selectedStar: boolean;
starCount: number;
hideFilter: () => void; hideFilter: () => void;
setSelectedFilters: (filters: { [key: string]: string[] }) => void; setSelectedFilters: (filters: { [key: string]: string[] }) => void;
setSelectedStar: (item: boolean) => void;
} }
const Filters: React.FC<FiltersProps> = ({ const Filters: React.FC<FiltersProps> = ({
categoriesTree, categoriesTree,
selectedFilters, selectedFilters,
selectedStar,
starCount,
hideFilter, hideFilter,
setSelectedFilters, setSelectedFilters,
setSelectedStar,
}) => { }) => {
const clearAllFilters = () => { const clearAllFilters = () => {
const newFilters = Object.keys(selectedFilters).reduce((acc, key) => { const newFilters = Object.keys(selectedFilters).reduce((acc, key) => {
@@ -126,6 +132,17 @@ const Filters: React.FC<FiltersProps> = ({
(filters) => Array.isArray(filters) && filters.length > 0 (filters) => Array.isArray(filters) && filters.length > 0
); );
const starCategory = {
id: "starStatus",
count: starCount,
name: "收藏状态",
categories: [{
id: "isStar",
count: starCount,
name: "已收藏"
}]
};
return ( return (
<div className="p-6 space-y-4 h-full overflow-y-auto"> <div className="p-6 space-y-4 h-full overflow-y-auto">
{/* Filter Header */} {/* Filter Header */}
@@ -170,6 +187,22 @@ const Filters: React.FC<FiltersProps> = ({
showIcons={false} showIcons={false}
/> />
))} ))}
<FilterSection
key={starCategory.id}
total={starCategory.count}
title={starCategory.name}
options={starCategory.categories.map(cat => ({
key: cat.id.toString(),
label: cat.name,
count: cat.count,
}))}
selectedValues={selectedStar ? ["isStar"] : []}
onSelectionChange={(values) => {
values.length > 0 ? setSelectedStar(true) : setSelectedStar(false);
}}
showIcons={false}
/>
</div> </div>
); );
}; };

View File

@@ -46,11 +46,11 @@ export interface OperatorI {
} }
export interface CategoryI { export interface CategoryI {
id: number; id: string;
name: string; name: string;
count: number; // 该分类下的算子数量 count: number; // 该分类下的算子数量
type: string; // e.g., "数据源", "数据清洗", "数据分析", "数据可视化" type: string; // e.g., "数据源", "数据清洗", "数据分析", "数据可视化"
parentId?: number; // 父分类ID,若无父分类则为null parentId?: string; // 父分类ID,若无父分类则为null
value: string; value: string;
createdAt: string; createdAt: string;
} }

View File

@@ -0,0 +1,7 @@
const menuTheme = {
itemColor: 'rgba(55, 65, 81, 1)',
itemSelectedColor: 'rgba(29, 78, 216, 1)',
itemSelectedBg: 'rgb(219, 234, 254)',
itemBorderRadius: 6,
};
export default menuTheme;

View File

@@ -0,0 +1,6 @@
const tableTheme = {
rowSelectedHoverBg: '#F7F9FB',
headerColor: 'rgba(100, 116, 139, 1)',
headerBg: '#fff',
};
export default tableTheme;

View File

@@ -0,0 +1,10 @@
import menuTheme from "./components/menus";
import tableTheme from "./components/table";
const theme = {
components: {
Menu: menuTheme,
Table: tableTheme,
},
};
export default theme;

View File

@@ -25,7 +25,9 @@ class TaskInfoPersistence:
with open(sql_config_path, 'r', encoding='utf-8') as f: with open(sql_config_path, 'r', encoding='utf-8') as f:
return json.load(f) return json.load(f)
def update_task_result(self, sample, file_id = str(uuid.uuid4())): def update_task_result(self, sample, file_id = None):
if file_id is None:
file_id = str(uuid.uuid4())
instance_id = str(sample.get("instance_id")) instance_id = str(sample.get("instance_id"))
src_file_name = str(sample.get("sourceFileName")) src_file_name = str(sample.get("sourceFileName"))
src_file_type = str(sample.get("sourceFileType")) src_file_type = str(sample.get("sourceFileType"))