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:
Kecheng Sha
2025-12-31 10:54:34 +08:00
committed by GitHub
parent f183b9f2f3
commit 01e1c6c7e9
5 changed files with 679 additions and 601 deletions

View File

@@ -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>
)}
/>
);
}

View File

@@ -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) ||
"--",
};
};

View File

@@ -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[];
}