You've already forked DataMate
fix: 修复入库可能重复;筛选逻辑优化 (#226)
* 修改数据清洗筛选逻辑-筛选修改为多选 * 修改数据清洗筛选逻辑-筛选修改为多选 * antd 组件库样式定制修改 * fix: 修复入库可能重复 * fix: 算子市场筛选逻辑优化 * fix: 清洗任务创建筛选逻辑优化 * fix: 清洗任务创建筛选逻辑优化 --------- Co-authored-by: chase <byzhangxin11@126.com>
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, " +
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 ">
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
7
frontend/src/theme/components/menus.ts
Normal file
7
frontend/src/theme/components/menus.ts
Normal 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;
|
||||||
6
frontend/src/theme/components/table.ts
Normal file
6
frontend/src/theme/components/table.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const tableTheme = {
|
||||||
|
rowSelectedHoverBg: '#F7F9FB',
|
||||||
|
headerColor: 'rgba(100, 116, 139, 1)',
|
||||||
|
headerBg: '#fff',
|
||||||
|
};
|
||||||
|
export default tableTheme;
|
||||||
10
frontend/src/theme/index.ts
Normal file
10
frontend/src/theme/index.ts
Normal 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;
|
||||||
@@ -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"))
|
||||||
|
|||||||
Reference in New Issue
Block a user