refactor: style css -> tailwindcss
This commit is contained in:
@@ -33,7 +33,6 @@ import {
|
||||
} from "./state/schedulesStore";
|
||||
import { useSchedulesStore } from "./state/schedulesStore";
|
||||
import { edgePath, nodeToForm, scheduleToForm } from "./utils";
|
||||
import "../../styles/schedule.css";
|
||||
|
||||
type Notice = {
|
||||
tone: "success" | "error" | "info";
|
||||
@@ -78,7 +77,7 @@ function RunNowButton({
|
||||
const positionDraftCount = useSchedulesStore((s) => s.positionDraftCount);
|
||||
return (
|
||||
<button
|
||||
className="schedule-action--run"
|
||||
className="inline-flex min-h-[32px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-emerald-600 bg-emerald-600 px-2 text-[12px] font-semibold text-white enabled:hover:border-emerald-700 enabled:hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50 min-[1281px]:px-3"
|
||||
type="button"
|
||||
disabled={
|
||||
!schedule
|
||||
@@ -139,7 +138,7 @@ const ScheduleEdge = memo(function ScheduleEdge({
|
||||
if (!path) return null;
|
||||
return (
|
||||
<g
|
||||
className={selectedEdgeId === edge.edge_id ? "is-selected" : ""}
|
||||
data-state={selectedEdgeId === edge.edge_id ? "selected" : "idle"}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
useSchedulesStore.getState().setSelectedEdgeId(edge.edge_id);
|
||||
@@ -150,9 +149,16 @@ const ScheduleEdge = memo(function ScheduleEdge({
|
||||
edge,
|
||||
})}
|
||||
>
|
||||
<path className="schedule-edge-hit" d={path} />
|
||||
<path
|
||||
className="schedule-edge-line"
|
||||
className="schedule-edge-hit fill-none stroke-transparent [stroke-width:14] cursor-pointer [pointer-events:stroke]"
|
||||
d={path}
|
||||
/>
|
||||
<path
|
||||
className={`schedule-edge-line fill-none ${
|
||||
selectedEdgeId === edge.edge_id
|
||||
? "stroke-[#df8142] [stroke-width:3.4]"
|
||||
: "stroke-[#4e92db] [stroke-width:2.2]"
|
||||
}`}
|
||||
d={path}
|
||||
markerEnd="url(#schedule-arrow)"
|
||||
/>
|
||||
@@ -338,23 +344,28 @@ export default function SchedulePage({
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="schedule-page">
|
||||
<header className="schedule-toolbar">
|
||||
<div>
|
||||
<strong>图形化调度</strong>
|
||||
<span>
|
||||
<section className="relative flex min-h-0 flex-1 flex-col gap-[10px] overflow-hidden p-[10px]">
|
||||
<header className="flex min-h-[56px] items-center justify-between rounded-[7px] border border-line bg-white px-[14px] shadow-[0_2px_7px_rgb(25_46_73_/_3%)]">
|
||||
<div className="flex items-baseline gap-[10px]">
|
||||
<strong className="text-[16px] text-[#1c2d42]">图形化调度</strong>
|
||||
<span className="hidden text-[11px] text-[#8b99a9] 2xl:inline">
|
||||
{schedule
|
||||
? `版本 ${schedule.workflow_version} · ${schedule.dag_validation.valid ? "DAG 有效" : "待校验"}`
|
||||
: "创建调度后开始编排"}
|
||||
<PositionDraftBadge />
|
||||
</span>
|
||||
</div>
|
||||
<div className="schedule-toolbar__actions">
|
||||
<button type="button" onClick={() => useSchedulesStore.getState().openCreateDialog()}>
|
||||
<Icon name="plus" size={15} />新建
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="inline-flex h-[32px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-[#dce5ee] bg-white px-2 text-[12px] font-semibold text-[#526477] enabled:hover:border-[#aac6e5] enabled:hover:bg-[#f6faff] enabled:hover:text-[#126aca] disabled:cursor-not-allowed disabled:opacity-50 min-[1281px]:px-3"
|
||||
type="button"
|
||||
onClick={() => useSchedulesStore.getState().openCreateDialog()}
|
||||
>
|
||||
<Icon name="plus" size={14} />
|
||||
<span>新建</span>
|
||||
</button>
|
||||
<button
|
||||
className="schedule-action--primary"
|
||||
className="inline-flex min-h-[32px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-brand bg-brand px-2 text-[12px] font-semibold text-white enabled:hover:border-brand-strong enabled:hover:bg-brand-strong disabled:cursor-not-allowed disabled:opacity-50 min-[1281px]:px-3"
|
||||
type="button"
|
||||
disabled={!schedule || Boolean(busy)}
|
||||
onClick={() => useSchedulesStore.getState().saveSchedule()}
|
||||
@@ -362,6 +373,7 @@ export default function SchedulePage({
|
||||
<Icon name="check" size={15} />保存配置
|
||||
</button>
|
||||
<button
|
||||
className="inline-flex min-h-[32px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-[#dce5ee] bg-white px-2 text-[12px] font-semibold text-[#526477] enabled:hover:border-[#aac6e5] enabled:hover:bg-[#f6faff] enabled:hover:text-[#126aca] disabled:cursor-not-allowed disabled:opacity-50 min-[1281px]:px-3"
|
||||
type="button"
|
||||
disabled={!schedule || Boolean(busy)}
|
||||
onClick={() => useSchedulesStore.getState().checkDag()}
|
||||
@@ -370,7 +382,7 @@ export default function SchedulePage({
|
||||
</button>
|
||||
<RunNowButton schedule={schedule} busy={busy} />
|
||||
<button
|
||||
className="schedule-action--danger"
|
||||
className="inline-flex min-h-[32px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-red-200 bg-white px-2 text-[12px] font-semibold text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-50 min-[1281px]:px-3"
|
||||
type="button"
|
||||
disabled={!schedule || Boolean(busy)}
|
||||
onClick={() => useSchedulesStore.getState().removeSchedule(schedule ?? undefined)}
|
||||
@@ -380,8 +392,8 @@ export default function SchedulePage({
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="schedule-workbench">
|
||||
<aside className="schedule-left">
|
||||
<div className="grid min-h-0 flex-1 gap-[10px] [grid-template-columns:225px_minmax(360px,1fr)_270px] min-[1281px]:[grid-template-columns:250px_minmax(380px,1fr)_292px] 2xl:[grid-template-columns:278px_minmax(420px,1fr)_316px]">
|
||||
<aside className="grid min-h-0 overflow-hidden rounded-[7px] border border-line bg-white shadow-[0_2px_7px_rgb(25_46_73_/_3%)] [grid-template-rows:minmax(210px,42%)_minmax(260px,58%)]">
|
||||
<ScheduleList
|
||||
schedules={filteredSchedules}
|
||||
selectedScheduleId={schedule?.schedule_id ?? null}
|
||||
@@ -401,14 +413,21 @@ export default function SchedulePage({
|
||||
/>
|
||||
</aside>
|
||||
|
||||
<main className="schedule-center">
|
||||
<main className="flex min-h-0 flex-col overflow-hidden rounded-[7px] border border-line bg-white shadow-[0_2px_7px_rgb(25_46_73_/_3%)]">
|
||||
<ScheduleCanvasHeader
|
||||
linkSourceId={linkSourceId}
|
||||
onCancelLink={() => setLinkSourceId(null)}
|
||||
/>
|
||||
<div
|
||||
className={`schedule-canvas${linkSourceId ? " is-linking" : ""}`}
|
||||
className="relative min-h-0 flex-1 overflow-auto bg-bg-canvas data-[state=linking]:cursor-crosshair"
|
||||
data-state={linkSourceId ? "linking" : "idle"}
|
||||
ref={canvasRef}
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(#e7edf3 1px, transparent 1px), linear-gradient(90deg, #e7edf3 1px, transparent 1px), linear-gradient(#f1f4f7 1px, transparent 1px), linear-gradient(90deg, #f1f4f7 1px, transparent 1px)",
|
||||
backgroundSize: "80px 80px, 80px 80px, 16px 16px, 16px 16px",
|
||||
backgroundPosition: "-1px -1px",
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
@@ -427,12 +446,12 @@ export default function SchedulePage({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="schedule-canvas__surface"
|
||||
className="relative"
|
||||
style={{ width: CANVAS_WIDTH, height: CANVAS_HEIGHT }}
|
||||
>
|
||||
{schedule && (
|
||||
<svg
|
||||
className="schedule-edges"
|
||||
className="absolute inset-0 z-[1] h-full w-full overflow-visible"
|
||||
viewBox={`0 0 ${CANVAS_WIDTH} ${CANVAS_HEIGHT}`}
|
||||
aria-label="调度连线"
|
||||
>
|
||||
@@ -461,9 +480,8 @@ export default function SchedulePage({
|
||||
|
||||
{schedule?.nodes.map((node) => (
|
||||
<div
|
||||
className={`schedule-node${
|
||||
selectedNodeId === node.node_id ? " is-selected" : ""
|
||||
}`}
|
||||
className={`schedule-node absolute z-[2] flex cursor-grab touch-none select-none flex-col rounded-[8px] border border-[#91b7de] bg-white p-[11px_12px_9px] text-[#34516f] shadow-[0_7px_18px_rgb(36_75_117_/_11%)] active:cursor-grabbing data-[state=selected]:border-brand data-[state=selected]:shadow-[0_0_0_3px_rgb(25_120_212_/_11%),0_8px_22px_rgb(36_75_117_/_13%)]`}
|
||||
data-state={selectedNodeId === node.node_id ? "selected" : "idle"}
|
||||
key={node.node_id}
|
||||
style={{
|
||||
left: node.position_x,
|
||||
@@ -481,7 +499,7 @@ export default function SchedulePage({
|
||||
})}
|
||||
>
|
||||
<button
|
||||
className="schedule-node__port schedule-node__port--in"
|
||||
className="absolute left-[-8px] top-[calc(50%-7px)] z-[4] h-[14px] w-[14px] cursor-crosshair rounded-full border-2 border-[#4e92db] bg-white p-0 hover:border-[#176ec5] hover:bg-[#4e92db] hover:shadow-[0_0_0_4px_rgb(45_124_207_/_16%)]"
|
||||
type="button"
|
||||
aria-label={`连接到${node.node_name}`}
|
||||
title={linkSourceId ? "点击完成连线" : "输入端口"}
|
||||
@@ -491,15 +509,24 @@ export default function SchedulePage({
|
||||
void useSchedulesStore.getState().connectTo(node.node_id);
|
||||
}}
|
||||
/>
|
||||
<div className="schedule-node__heading">
|
||||
<span className={`schedule-node__type schedule-node__type--${node.version.script_type}`}>
|
||||
<div className="flex items-center gap-[7px]">
|
||||
<span
|
||||
className={`grid h-[25px] w-[25px] flex-none place-items-center rounded-[5px] ${
|
||||
node.version.script_type === "notebook"
|
||||
? "bg-[#fff0ef] text-[#da5c52]"
|
||||
: "bg-[#eaf3ff] text-[#316fbf]"
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
name={node.version.script_type === "notebook" ? "notebook" : "python"}
|
||||
size={14}
|
||||
/>
|
||||
</span>
|
||||
<strong>{node.node_name}</strong>
|
||||
<strong className="min-w-0 flex-1 truncate text-[12px] text-[#243b53]">
|
||||
{node.node_name}
|
||||
</strong>
|
||||
<button
|
||||
className="grid h-[23px] w-[23px] cursor-pointer place-items-center rounded-[4px] border-0 bg-transparent p-0 text-[#8b99a7] hover:bg-[#edf5fd] hover:text-brand"
|
||||
type="button"
|
||||
aria-label="删除节点"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
@@ -512,15 +539,18 @@ export default function SchedulePage({
|
||||
<Icon name="settings" size={14} />
|
||||
</button>
|
||||
</div>
|
||||
<p>{node.version.script_name}</p>
|
||||
<footer>
|
||||
<span>{node.node_key}</span>
|
||||
<b>{node.version.version_label}</b>
|
||||
<p className="mt-2 mb-1.5 truncate text-[10px] text-[#7e8d9d]">
|
||||
{node.version.script_name}
|
||||
</p>
|
||||
<footer className="flex items-center justify-between border-t border-line-soft pt-1.5">
|
||||
<span className="max-w-[145px] truncate font-mono text-[9px] text-[#8d9baa]">
|
||||
{node.node_key}
|
||||
</span>
|
||||
<b className="text-[10px] text-brand">{node.version.version_label}</b>
|
||||
</footer>
|
||||
<button
|
||||
className={`schedule-node__port schedule-node__port--out${
|
||||
linkSourceId === node.node_id ? " is-active" : ""
|
||||
}`}
|
||||
className="absolute right-[-8px] top-[calc(50%-7px)] z-[4] h-[14px] w-[14px] cursor-crosshair rounded-full border-2 border-[#4e92db] bg-white p-0 hover:border-[#176ec5] hover:bg-[#4e92db] hover:shadow-[0_0_0_4px_rgb(45_124_207_/_16%)] data-[state=active]:border-[#176ec5] data-[state=active]:bg-[#4e92db] data-[state=active]:shadow-[0_0_0_4px_rgb(45_124_207_/_16%)]"
|
||||
data-state={linkSourceId === node.node_id ? "active" : "idle"}
|
||||
type="button"
|
||||
aria-label={`从${node.node_name}开始连线`}
|
||||
title="输出端口"
|
||||
@@ -540,39 +570,50 @@ export default function SchedulePage({
|
||||
))}
|
||||
|
||||
{!schedule ? (
|
||||
<div className="schedule-canvas-empty">
|
||||
<div className="sticky left-1/2 top-[46%] z-0 mx-auto mt-[190px] flex w-[350px] flex-col items-center text-center text-[#9aa8b6] [&>svg]:mb-3 [&>svg]:text-[#8cb4de]">
|
||||
<Icon name="schedule" size={34} />
|
||||
<strong>还没有选中调度方案</strong>
|
||||
<p>点击"新建"创建一个调度,然后拖入稳定版本脚本。</p>
|
||||
<button type="button" onClick={() => useSchedulesStore.getState().openCreateDialog()}>
|
||||
<strong className="text-[14px] text-[#567089]">还没有选中调度方案</strong>
|
||||
<p className="my-[7px_13px] text-[11px]">点击"新建"创建一个调度,然后拖入稳定版本脚本。</p>
|
||||
<button
|
||||
className="inline-flex min-h-[32px] cursor-pointer items-center gap-1 rounded-[6px] border border-[#2580da] bg-[#2580da] px-3 text-[11px] text-white"
|
||||
type="button"
|
||||
onClick={() => useSchedulesStore.getState().openCreateDialog()}
|
||||
>
|
||||
<Icon name="plus" size={15} />新建调度
|
||||
</button>
|
||||
</div>
|
||||
) : schedule.nodes.length === 0 ? (
|
||||
<div className="schedule-canvas-empty">
|
||||
<div className="sticky left-1/2 top-[46%] z-0 mx-auto mt-[190px] flex w-[350px] flex-col items-center text-center text-[#9aa8b6] [&>svg]:mb-3 [&>svg]:text-[#8cb4de]">
|
||||
<Icon name="release" size={34} />
|
||||
<strong>从稳定版本开始编排</strong>
|
||||
<p>把左侧脚本卡片拖到这里,或双击卡片快速加入。</p>
|
||||
<strong className="text-[14px] text-[#567089]">从稳定版本开始编排</strong>
|
||||
<p className="my-[7px_13px] text-[11px]">把左侧脚本卡片拖到这里,或双击卡片快速加入。</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<footer className="schedule-canvas-status">
|
||||
<span className={schedule?.dag_validation.valid ? "is-valid" : ""}>
|
||||
<footer className="flex min-h-[35px] items-center gap-[14px] border-t border-[#e4eaf0] bg-white px-3 text-[10px] text-[#8b99a9]">
|
||||
<span
|
||||
data-state={schedule?.dag_validation.valid ? "valid" : "invalid"}
|
||||
className="text-[#b17a48] data-[state=valid]:text-[#16875b]"
|
||||
>
|
||||
{schedule?.dag_validation.valid ? "✓ DAG 有效" : "○ DAG 待校验"}
|
||||
</span>
|
||||
<span>{schedule?.nodes.length ?? 0} 个节点</span>
|
||||
<span>{schedule?.edges.length ?? 0} 条连线</span>
|
||||
{selectedEdge && (
|
||||
<button type="button" onClick={() => useSchedulesStore.getState().removeEdge(selectedEdge)}>
|
||||
<button
|
||||
className="ml-auto inline-flex min-h-[24px] cursor-pointer items-center justify-center gap-[5px] rounded-[6px] border border-[#dce5ee] bg-white px-2 text-[10px] font-semibold text-[#bd504b] hover:border-[#aac6e5] hover:bg-[#f6faff] hover:text-[#126aca]"
|
||||
type="button"
|
||||
onClick={() => useSchedulesStore.getState().removeEdge(selectedEdge)}
|
||||
>
|
||||
删除选中连线
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<aside className="schedule-right">
|
||||
<div className="schedule-inspector-scroll">
|
||||
<aside className="grid min-h-0 overflow-hidden rounded-[7px] border border-line bg-white shadow-[0_2px_7px_rgb(25_46_73_/_3%)] [grid-template-rows:repeat(2,minmax(0,1fr))]">
|
||||
<div className="min-h-0 overflow-auto">
|
||||
{selectedNode ? (
|
||||
<NodeInspector
|
||||
node={selectedNode}
|
||||
@@ -606,14 +647,14 @@ export default function SchedulePage({
|
||||
|
||||
{contextMenu && (
|
||||
<div
|
||||
className="schedule-context-menu"
|
||||
className="fixed z-[60] min-w-[168px] rounded-[8px] border border-[#d9e3ec] bg-white p-[5px] shadow-[0_14px_36px_rgb(14_35_57_/_22%)]"
|
||||
role="menu"
|
||||
style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
{contextMenu.kind === "schedule-list" && (
|
||||
<button
|
||||
className="schedule-context-menu__action"
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#173a5e] hover:bg-[#eef6ff]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().openCreateDialog()}
|
||||
@@ -625,7 +666,7 @@ export default function SchedulePage({
|
||||
{contextMenu.kind === "schedule" && (
|
||||
<>
|
||||
<button
|
||||
className="schedule-context-menu__action"
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#173a5e] hover:bg-[#eef6ff]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().openCreateDialog()}
|
||||
@@ -634,7 +675,7 @@ export default function SchedulePage({
|
||||
新建调度方案
|
||||
</button>
|
||||
<button
|
||||
className="schedule-context-menu__action"
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#173a5e] hover:bg-[#eef6ff]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().renameSchedule(contextMenu.schedule)}
|
||||
@@ -643,6 +684,7 @@ export default function SchedulePage({
|
||||
改名
|
||||
</button>
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#c23b3b] hover:bg-[#fff0f0]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().removeSchedule(contextMenu.schedule)}
|
||||
@@ -655,6 +697,7 @@ export default function SchedulePage({
|
||||
{contextMenu.kind === "artifact" && null}
|
||||
{contextMenu.kind === "node" && (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#c23b3b] hover:bg-[#fff0f0]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().removeNode(contextMenu.node)}
|
||||
@@ -665,6 +708,7 @@ export default function SchedulePage({
|
||||
)}
|
||||
{contextMenu.kind === "edge" && (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-[9px] rounded-[5px] border-0 bg-transparent p-[9px_10px] text-left text-[12px] text-[#c23b3b] hover:bg-[#fff0f0]"
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={() => useSchedulesStore.getState().removeEdge(contextMenu.edge)}
|
||||
@@ -690,7 +734,7 @@ export default function SchedulePage({
|
||||
<h2 id="create-schedule-title">新建调度方案</h2>
|
||||
</div>
|
||||
<button
|
||||
className="icon-button"
|
||||
className="grid h-[34px] w-[34px] cursor-pointer place-items-center rounded-[7px] border border-[#dfe6ed] bg-white hover:border-[#b9c9da] hover:bg-[#f7faff]"
|
||||
type="button"
|
||||
aria-label="关闭"
|
||||
disabled={Boolean(busy)}
|
||||
@@ -737,8 +781,11 @@ export default function SchedulePage({
|
||||
)}
|
||||
|
||||
{busy && (
|
||||
<div className="schedule-busy" aria-live="polite">
|
||||
<span />
|
||||
<div
|
||||
className="absolute bottom-[22px] right-[22px] z-[20] flex min-h-[38px] items-center gap-2 rounded-[7px] border border-[#c9dcee] bg-white px-[13px] text-[11px] text-[#4e657d] shadow-[0_8px_25px_rgb(28_56_86_/_15%)]"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span className="h-[15px] w-[15px] rounded-full border-2 border-[#b9d4ef] border-t-brand" />
|
||||
{busy === "run-now" ? "正在创建运行…" : "正在同步调度配置…"}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user