You've already forked DataMate
polish(operator cards): improve icon color distinction and subtle UI … (#217)
polish(operator cards): improve icon color distinction and subtle UI details
This commit is contained in:
@@ -1,90 +1,101 @@
|
||||
import { Button, List, Tag } from "antd";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Operator } from "../../operator.model";
|
||||
|
||||
export function ListView({ operators = [], pagination, operations }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleViewOperator = (operator: Operator) => {
|
||||
navigate(`/data/operator-market/plugin-detail/${operator.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<List
|
||||
className="p-4 flex-1 overflow-auto mx-4"
|
||||
dataSource={operators}
|
||||
pagination={pagination}
|
||||
renderItem={(operator) => (
|
||||
<List.Item
|
||||
className="hover:bg-gray-50 transition-colors px-6 py-4"
|
||||
actions={[
|
||||
// <Button
|
||||
// key="favorite"
|
||||
// type="text"
|
||||
// size="small"
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// className={
|
||||
// favoriteOperators.has(operator.id)
|
||||
// ? "text-yellow-500 hover:text-yellow-600"
|
||||
// : "text-gray-400 hover:text-yellow-500"
|
||||
// }
|
||||
// icon={
|
||||
// <StarFilled
|
||||
// style={{
|
||||
// fontSize: "16px",
|
||||
// color: favoriteOperators.has(operator.id)
|
||||
// ? "#ffcc00ff"
|
||||
// : "#d1d5db",
|
||||
// cursor: "pointer",
|
||||
// }}
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// />
|
||||
// }
|
||||
// title="收藏"
|
||||
// />,
|
||||
...operations.map((operation) => (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
title={operation.label}
|
||||
icon={operation.icon}
|
||||
danger={operation.danger}
|
||||
onClick={() => operation.onClick(operator)}
|
||||
/>
|
||||
)),
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-sky-300 to-blue-500 rounded-lg flex items-center justify-center">
|
||||
<div className="w-8 h-8 text-white">{operator?.icon}</div>
|
||||
</div>
|
||||
}
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className="font-medium text-gray-900 cursor-pointer hover:text-blue-600"
|
||||
onClick={() => handleViewOperator(operator)}
|
||||
>
|
||||
{operator.name}
|
||||
</span>
|
||||
<Tag color="default">v{operator.version}</Tag>
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<div className="text-gray-600 ">{operator.description}</div>
|
||||
{/* <div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<span>作者: {operator.author}</span>
|
||||
<span>类型: {operator.type}</span>
|
||||
<span>框架: {operator.framework}</span>
|
||||
<span>使用次数: {operator?.usage?.toLocaleString()}</span>
|
||||
</div> */}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
import { Button, List, Tag } from "antd";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Operator } from "../../operator.model";
|
||||
|
||||
export function ListView({ operators = [], pagination, operations }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleViewOperator = (operator: Operator) => {
|
||||
navigate(`/data/operator-market/plugin-detail/${operator.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<List
|
||||
className="p-4 flex-1 overflow-auto mx-4"
|
||||
dataSource={operators}
|
||||
pagination={pagination}
|
||||
renderItem={(operator) => (
|
||||
<List.Item
|
||||
className="hover:bg-gray-50 transition-colors px-6 py-4"
|
||||
actions={[
|
||||
// <Button
|
||||
// key="favorite"
|
||||
// type="text"
|
||||
// size="small"
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// className={
|
||||
// favoriteOperators.has(operator.id)
|
||||
// ? "text-yellow-500 hover:text-yellow-600"
|
||||
// : "text-gray-400 hover:text-yellow-500"
|
||||
// }
|
||||
// icon={
|
||||
// <StarFilled
|
||||
// style={{
|
||||
// fontSize: "16px",
|
||||
// color: favoriteOperators.has(operator.id)
|
||||
// ? "#ffcc00ff"
|
||||
// : "#d1d5db",
|
||||
// cursor: "pointer",
|
||||
// }}
|
||||
// onClick={() => handleToggleFavorite(operator.id)}
|
||||
// />
|
||||
// }
|
||||
// title="收藏"
|
||||
// />,
|
||||
...operations.map((operation) => (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
title={operation.label}
|
||||
icon={operation.icon}
|
||||
danger={operation.danger}
|
||||
onClick={() => operation.onClick(operator)}
|
||||
/>
|
||||
)),
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<div
|
||||
className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
||||
operator?.iconColor
|
||||
? ""
|
||||
: "bg-gradient-to-br from-sky-300 to-blue-500"
|
||||
}`}
|
||||
style={
|
||||
operator?.iconColor
|
||||
? { backgroundColor: operator.iconColor }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<div className="w-[2.8rem] h-[2.8rem] text-white">{operator?.icon}</div>
|
||||
</div>
|
||||
}
|
||||
title={
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className="font-medium text-gray-900 cursor-pointer hover:text-blue-600"
|
||||
onClick={() => handleViewOperator(operator)}
|
||||
>
|
||||
{operator.name}
|
||||
</span>
|
||||
<Tag color="default">v{operator.version}</Tag>
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<div className="text-gray-600 ">{operator.description}</div>
|
||||
{/* <div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<span>作者: {operator.author}</span>
|
||||
<span>类型: {operator.type}</span>
|
||||
<span>框架: {operator.framework}</span>
|
||||
<span>使用次数: {operator?.usage?.toLocaleString()}</span>
|
||||
</div> */}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,59 @@
|
||||
import { Code } from "lucide-react";
|
||||
import { OperatorI } from "./operator.model";
|
||||
import {formatDateTime} from "@/utils/unit.ts";
|
||||
|
||||
export const mapOperator = (op: OperatorI) => {
|
||||
return {
|
||||
...op,
|
||||
icon: <Code className="w-full h-full" />,
|
||||
createdAt: formatDateTime(op?.createdAt) || "--",
|
||||
updatedAt: formatDateTime(op?.updatedAt) || formatDateTime(op?.createdAt) || "--",
|
||||
};
|
||||
};
|
||||
import React from "react";
|
||||
import { Code, FileSliders, Image } from "lucide-react";
|
||||
import { OperatorI } from "./operator.model";
|
||||
import { formatDateTime } from "@/utils/unit.ts";
|
||||
|
||||
const getOperatorVisual = (
|
||||
op: OperatorI
|
||||
): { icon: React.ReactNode; iconColor?: string } => {
|
||||
const type = (op?.type || "").toLowerCase();
|
||||
const categories = (op?.categories || []).map((c) => (c || "").toLowerCase());
|
||||
const inputs = (op?.inputs || "").toLowerCase();
|
||||
const outputs = (op?.outputs || "").toLowerCase();
|
||||
|
||||
const isImageOp =
|
||||
["image", "图像", "图像类"].includes(type) ||
|
||||
categories.some((c) => c.includes("image") || c.includes("图像")) ||
|
||||
inputs.includes("image") ||
|
||||
outputs.includes("image");
|
||||
|
||||
const isTextOp =
|
||||
["text", "文本", "文本类"].includes(type) ||
|
||||
categories.some((c) => c.includes("text") || c.includes("文本")) ||
|
||||
inputs.includes("text") ||
|
||||
outputs.includes("text");
|
||||
|
||||
if (isImageOp) {
|
||||
return {
|
||||
icon: <Image className="w-full h-full" />,
|
||||
iconColor: "#38BDF8", // 图像算子背景色
|
||||
};
|
||||
}
|
||||
|
||||
if (isTextOp) {
|
||||
return {
|
||||
icon: <FileSliders className="w-full h-full" />,
|
||||
iconColor: "#A78BFA", // 文本算子背景色
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
icon: <Code className="w-full h-full" />,
|
||||
iconColor: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const mapOperator = (op: OperatorI) => {
|
||||
const visual = getOperatorVisual(op);
|
||||
|
||||
return {
|
||||
...op,
|
||||
icon: visual.icon,
|
||||
iconColor: visual.iconColor,
|
||||
createdAt: formatDateTime(op?.createdAt) || "--",
|
||||
updatedAt:
|
||||
formatDateTime(op?.updatedAt) ||
|
||||
formatDateTime(op?.createdAt) ||
|
||||
"--",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,62 +1,63 @@
|
||||
export interface ConfigI {
|
||||
type:
|
||||
| "input"
|
||||
| "select"
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "range"
|
||||
| "slider"
|
||||
| "inputNumber"
|
||||
| "switch"
|
||||
| "multiple";
|
||||
value?: number | string | boolean | string[] | number[];
|
||||
required?: boolean;
|
||||
description?: string;
|
||||
key: string;
|
||||
defaultVal: number | string | boolean | string[];
|
||||
options?: string[] | { label: string; value: string }[];
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
properties?: ConfigI[]; // 用于嵌套配置
|
||||
}
|
||||
|
||||
export interface OperatorI {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
version: string;
|
||||
inputs: string;
|
||||
outputs: string;
|
||||
icon: React.ReactNode;
|
||||
description: string;
|
||||
tags: string[];
|
||||
isStar?: boolean;
|
||||
originalId?: string; // 用于标识原始算子ID,便于去重
|
||||
categories: string[]; // 分类列表
|
||||
settings: string;
|
||||
overrides?: { [key: string]: any }; // 用户配置的参数
|
||||
defaultParams?: { [key: string]: any }; // 默认参数
|
||||
configs: {
|
||||
[key: string]: ConfigI;
|
||||
};
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CategoryI {
|
||||
id: number;
|
||||
name: string;
|
||||
count: number; // 该分类下的算子数量
|
||||
type: string; // e.g., "数据源", "数据清洗", "数据分析", "数据可视化"
|
||||
parentId?: number; // 父分类ID,若无父分类则为null
|
||||
value: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CategoryTreeI {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
categories: CategoryI[];
|
||||
}
|
||||
export interface ConfigI {
|
||||
type:
|
||||
| "input"
|
||||
| "select"
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "range"
|
||||
| "slider"
|
||||
| "inputNumber"
|
||||
| "switch"
|
||||
| "multiple";
|
||||
value?: number | string | boolean | string[] | number[];
|
||||
required?: boolean;
|
||||
description?: string;
|
||||
key: string;
|
||||
defaultVal: number | string | boolean | string[];
|
||||
options?: string[] | { label: string; value: string }[];
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
properties?: ConfigI[]; // 用于嵌套配置
|
||||
}
|
||||
|
||||
export interface OperatorI {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
version: string;
|
||||
inputs: string;
|
||||
outputs: string;
|
||||
icon: React.ReactNode;
|
||||
iconColor?: string; // 图标背景色,用于区分不同类型算子
|
||||
description: string;
|
||||
tags: string[];
|
||||
isStar?: boolean;
|
||||
originalId?: string; // 用于标识原始算子ID,便于去重
|
||||
categories: string[]; // 分类列表
|
||||
settings: string;
|
||||
overrides?: { [key: string]: any }; // 用户配置的参数
|
||||
defaultParams?: { [key: string]: any }; // 默认参数
|
||||
configs: {
|
||||
[key: string]: ConfigI;
|
||||
};
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface CategoryI {
|
||||
id: number;
|
||||
name: string;
|
||||
count: number; // 该分类下的算子数量
|
||||
type: string; // e.g., "数据源", "数据清洗", "数据分析", "数据可视化"
|
||||
parentId?: number; // 父分类ID,若无父分类则为null
|
||||
value: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CategoryTreeI {
|
||||
id: string;
|
||||
name: string;
|
||||
count: number;
|
||||
categories: CategoryI[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user