feat(workload): add workload list

This commit is contained in:
zhangheng
2026-05-14 15:12:32 +08:00
parent 781b002a7b
commit 5c3b45b247
12 changed files with 1508 additions and 34 deletions

View File

@@ -13,6 +13,35 @@
box-shadow: none;
}
.pageIntro {
gap: 4px;
}
.breadcrumb {
color: #86909c;
font-size: 13px;
line-height: 20px;
}
.breadcrumbLink {
border: 0;
padding: 0;
background: transparent;
color: #86909c;
font: inherit;
line-height: inherit;
cursor: pointer;
transition: color 0.2s ease;
}
.breadcrumbLink:hover {
color: #1874ff;
}
.breadcrumbCurrent {
color: #4e5969;
}
.modalContent {
overflow: hidden;
border-radius: 8px;
@@ -84,10 +113,517 @@
color: #c9cdd4 !important;
}
.modalForm {
gap: 12px;
}
.modalMetaText {
color: #4e5969;
font-size: 14px;
line-height: 20px;
}
.modalActions {
gap: 12px;
}
.modalForm :global(.mantine-InputWrapper-label) {
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
margin-bottom: 4px;
}
.modalForm :global(.mantine-Switch-label),
.modalForm :global(.mantine-Checkbox-label) {
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.modalForm :global(.mantine-Input-input),
.modalForm :global(.mantine-Select-input),
.modalForm :global(.mantine-DateInput-input),
.modalForm :global(.mantine-NumberInput-input),
.modalForm :global(.mantine-PillsInput-root),
.modalForm :global(.mantine-Textarea-input) {
min-height: 32px;
border-color: #e5e6eb;
border-radius: 4px;
background: #f7f8fa;
color: #1d2129;
font-size: 14px;
box-shadow: none;
transition:
border-color 0.2s ease,
color 0.2s ease,
background-color 0.2s ease;
}
.modalForm :global(.mantine-PillsInput-root) {
padding-inline: 12px;
}
.modalForm :global(.mantine-PillsInput-input) {
min-height: 30px;
background: transparent;
color: #1d2129;
}
.modalForm :global(.mantine-Textarea-input) {
min-height: 96px;
}
.modalForm :global(.mantine-Input-input::placeholder),
.modalForm :global(.mantine-Select-input::placeholder),
.modalForm :global(.mantine-DateInput-input::placeholder),
.modalForm :global(.mantine-NumberInput-input::placeholder),
.modalForm :global(.mantine-PillsInput-input::placeholder),
.modalForm :global(.mantine-Textarea-input::placeholder) {
color: #86909c;
}
.modalForm :global(.mantine-Input-input:focus),
.modalForm :global(.mantine-Select-input:focus),
.modalForm :global(.mantine-DateInput-input:focus),
.modalForm :global(.mantine-NumberInput-input:focus),
.modalForm :global(.mantine-PillsInput-root:focus-within),
.modalForm :global(.mantine-Textarea-input:focus) {
border-color: #1874ff;
background: #ffffff;
box-shadow: 0 0 0 2px rgba(24, 116, 255, 0.12);
}
.modalForm :global(.mantine-Input-input[readonly]),
.modalForm :global(.mantine-DateInput-input[readonly]),
.modalForm :global(.mantine-NumberInput-input[readonly]),
.modalForm :global(.mantine-Textarea-input[readonly]) {
background: #f7f8fa;
color: #86909c;
}
.modalForm :global(.mantine-Input-section),
.modalForm :global(.mantine-Select-section),
.modalForm :global(.mantine-DateInput-section),
.modalForm :global(.mantine-NumberInput-section) {
color: #86909c;
}
.modalForm :global(.mantine-Switch-root) {
--switch-height: 20px;
--switch-width: 34px;
--switch-thumb-size: 14px;
--switch-radius: 999px;
--switch-color: #1874ff;
}
.modalForm :global(.mantine-Checkbox-root) {
--checkbox-color: #1874ff;
--checkbox-radius: 4px;
}
.modalForm :global(.mantine-Checkbox-input) {
border-color: #c9cdd4;
background: #ffffff;
}
.modalForm :global(.mantine-Button-root) {
min-height: 32px;
border-radius: 4px;
padding-inline: 12px;
font-size: 14px;
font-weight: 500;
box-shadow: none;
transition:
background-color 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
}
.modalForm :global(.mantine-Button-root[data-variant="filled"]) {
--button-bg: #1874ff;
--button-hover: #0f6ae8;
--button-color: #ffffff;
--button-bd: transparent;
background: #1874ff !important;
color: #ffffff !important;
}
.modalForm :global(.mantine-Button-root[data-variant="filled"]:hover) {
background: #0f6ae8 !important;
}
.modalForm :global(.mantine-Button-root[data-variant="default"]) {
border-color: #e5e6eb;
background: #ffffff;
color: #4e5969;
}
.modalForm :global(.mantine-Button-root[data-disabled]),
.modalForm :global(.mantine-Button-root:disabled) {
border-color: #e5e6eb !important;
background: #f2f3f5 !important;
color: #c9cdd4 !important;
cursor: not-allowed;
opacity: 1;
}
.modalForm :global(.mantine-Button-root[data-disabled]:hover),
.modalForm :global(.mantine-Button-root:disabled:hover) {
border-color: #e5e6eb !important;
background: #f2f3f5 !important;
color: #c9cdd4 !important;
}
.filterPanel {
position: relative;
}
.detailContent {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
}
.detailForm {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
gap: 12px;
}
.detailIntroGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 20px;
}
.detailSection {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
gap: 12px;
}
.detailActionBar {
padding-top: 12px;
border-top: 1px solid #e5e6eb;
}
.detailScrollArea {
flex: 1;
min-height: 0;
}
.readValue {
min-height: 32px;
border-color: #e5e6eb;
border-radius: 4px;
background: #f7f8fa;
display: flex;
align-items: center;
}
.readValueText {
width: 100%;
color: #1d2129;
font-size: 14px;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.detailForm :global(.mantine-InputWrapper-label) {
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
margin-bottom: 4px;
}
.detailForm :global(.mantine-Input-input),
.detailForm :global(.mantine-Select-input),
.detailForm :global(.mantine-ColorInput-input),
.detailForm :global(.mantine-NumberInput-input),
.detailForm :global(.mantine-PillsInput-root),
.detailForm :global(.mantine-Textarea-input) {
min-height: 32px;
border-color: #e5e6eb;
border-radius: 4px;
background: #f7f8fa;
color: #1d2129;
font-size: 14px;
box-shadow: none;
transition:
border-color 0.2s ease,
color 0.2s ease,
background-color 0.2s ease;
}
.detailForm :global(.mantine-PillsInput-root) {
padding-inline: 12px;
}
.detailForm :global(.mantine-PillsInput-input) {
min-height: 30px;
background: transparent;
color: #1d2129;
}
.detailForm :global(.mantine-Textarea-input) {
min-height: 96px;
}
.detailForm :global(.mantine-Input-input::placeholder),
.detailForm :global(.mantine-Select-input::placeholder),
.detailForm :global(.mantine-ColorInput-input::placeholder),
.detailForm :global(.mantine-NumberInput-input::placeholder),
.detailForm :global(.mantine-PillsInput-input::placeholder),
.detailForm :global(.mantine-Textarea-input::placeholder) {
color: #86909c;
}
.detailForm :global(.mantine-Input-input:focus),
.detailForm :global(.mantine-Select-input:focus),
.detailForm :global(.mantine-ColorInput-input:focus),
.detailForm :global(.mantine-NumberInput-input:focus),
.detailForm :global(.mantine-PillsInput-root:focus-within),
.detailForm :global(.mantine-Textarea-input:focus) {
border-color: #1874ff;
background: #ffffff;
box-shadow: 0 0 0 2px rgba(24, 116, 255, 0.12);
}
.detailForm :global(.mantine-Input-input[readonly]),
.detailForm :global(.mantine-ColorInput-input[readonly]),
.detailForm :global(.mantine-NumberInput-input[readonly]),
.detailForm :global(.mantine-Textarea-input[readonly]) {
background: #f7f8fa;
color: #86909c;
}
.detailForm :global(.mantine-Input-section),
.detailForm :global(.mantine-Select-section),
.detailForm :global(.mantine-ColorInput-section),
.detailForm :global(.mantine-NumberInput-section) {
color: #86909c;
}
.detailForm :global(.mantine-Pill-root) {
background: #e7efff;
color: #1874ff;
}
.detailForm :global(.mantine-Button-root) {
min-height: 32px;
border-radius: 4px;
padding-inline: 12px;
font-size: 14px;
font-weight: 500;
box-shadow: none;
transition:
background-color 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
}
.detailForm :global(.mantine-Button-root[data-variant="filled"]) {
--button-bg: #1874ff;
--button-hover: #0f6ae8;
--button-color: #ffffff;
--button-bd: transparent;
background: #1874ff !important;
color: #ffffff !important;
}
.detailForm :global(.mantine-Button-root[data-variant="filled"]:hover) {
background: #0f6ae8 !important;
}
.detailForm :global(.mantine-Button-root[data-variant="default"]),
.detailForm :global(.mantine-Button-root[data-variant="light"]) {
border-color: #e5e6eb;
background: #ffffff;
color: #4e5969;
}
.detailForm :global(.mantine-ActionIcon-root) {
width: 28px;
height: 28px;
border-radius: 4px;
box-shadow: none;
}
.detailForm :global(.mantine-Table-table) {
border-color: #e5e6eb;
background: #ffffff;
font-size: 14px;
}
.detailForm :global(.mantine-Table-th) {
background: #f7f8fa;
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
white-space: nowrap;
}
.detailForm :global(.mantine-Table-th),
.detailForm :global(.mantine-Table-td) {
border-color: #e5e6eb;
padding: 10px 12px;
}
.detailForm :global(.mantine-Table-td) {
color: #1d2129;
line-height: 20px;
}
.tableViewport {
min-width: 0;
max-width: 100%;
overflow: auto;
overscroll-behavior: contain;
}
.filterStack {
gap: 12px;
}
.filterTopRow {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px 20px;
align-items: center;
}
.filterExpandedGrid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px 20px;
padding-top: 12px;
border-top: 1px solid #e5e6eb;
}
.filterField {
display: flex;
min-width: 0;
align-items: center;
gap: 10px;
}
.filterLabel {
flex: 0 0 var(--setting-inline-filter-label-width, 80px);
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.filterControl {
flex: 1;
min-width: 0;
}
.filterControl :global(.mantine-InputWrapper-root),
.filterControl :global(.mantine-PillsInput-root) {
width: 100%;
}
.filterControl :global(.mantine-InputWrapper-label) {
display: none;
}
.filterActionsAlignEnd {
display: inline-flex;
align-items: center;
justify-self: end;
grid-column: -2 / -1;
}
.collapseToggle {
min-width: auto;
padding-inline: 0;
align-self: center;
justify-self: start;
}
.operationGroup {
display: inline-flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.operationLink {
border: 0;
padding: 0;
background: transparent;
color: #1874ff;
font-size: 13px;
line-height: 20px;
cursor: pointer;
transition:
color 0.2s ease,
opacity 0.2s ease;
}
.operationLinkDanger {
color: #f53f3f;
}
.operationLinkDisabled {
color: #c9cdd4;
cursor: not-allowed;
}
.operationDivider {
width: 1px;
height: 12px;
background: #e5e6eb;
}
.statusBadge {
min-height: 24px;
padding: 0 10px;
border: 0;
border-radius: 999px;
font-size: 12px;
font-weight: 500;
}
.statusPositive {
background: #e8ffea;
color: #00b42a;
}
.statusNegative {
background: #ffece8;
color: #f53f3f;
}
.statusWarning {
background: #fff7e8;
color: #ff7d00;
}
.statusInfo {
background: #e8f3ff;
color: #1874ff;
}
.statusNeutral {
background: #f2f3f5;
color: #4e5969;
}
.filterPanel :global(.mantine-InputWrapper-label) {
color: #4e5969;
font-size: 14px;
@@ -235,6 +771,11 @@
}
.contentPanel :global(.mantine-datatable-pagination) {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
padding: 12px 16px !important;
color: #4e5969;
}
@@ -274,6 +815,21 @@
--pagination-control-fz: 12px;
}
.contentPanel :global(.mantine-datatable-pagination .mantine-Select-input) {
min-height: 28px;
height: 28px;
border-color: #e5e6eb;
border-radius: 4px;
background: #ffffff;
color: #4e5969;
font-size: 12px;
box-shadow: none;
}
.contentPanel :global(.mantine-datatable-pagination .mantine-Select-section) {
color: #86909c;
}
.contentPanel :global(.mantine-Pagination-dots) {
color: #86909c;
}
@@ -327,6 +883,19 @@
margin-bottom: 12px;
}
.listHeaderTitle {
color: #1d2129;
font-size: 16px;
font-weight: 500;
line-height: 24px;
}
.listHeaderCount {
color: #86909c;
font-size: 12px;
line-height: 20px;
}
.sectionHeader {
padding-bottom: 12px;
border-bottom: 1px solid #e5e6eb;
@@ -354,38 +923,42 @@
line-height: 20px;
}
.listHeaderTitle {
color: #1d2129;
font-size: 14px;
font-weight: 500;
line-height: 22px;
}
.listHeaderCount {
color: #86909c;
font-size: 12px;
line-height: 20px;
}
.settingTabsList {
padding: 4px;
border: 1px solid #e5e6eb;
border-radius: 4px;
background: #f7f8fa;
gap: 8px;
border: 1px solid #eef2f6;
border-radius: 10px;
background: #f8f9fb;
}
.settingTabsTab {
min-height: 36px;
border-radius: 8px;
color: #56606a;
font-weight: 600;
min-height: 32px;
border-radius: 4px;
padding: 0 12px;
color: #4e5969;
font-size: 14px;
font-weight: 500;
line-height: 20px;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.settingTabsTab[data-active] {
background: #ffffff;
color: #1f2329;
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.08);
.settingTabsTab[data-active],
.settingTabsTab[data-active]:hover {
background: #1874ff;
color: #ffffff;
}
.settingTabsTabLabel {
color: inherit;
font-size: 14px;
line-height: 20px;
}
.settingTabsTab[data-active] .settingTabsTabLabel,
.settingTabsTab[data-active]:hover .settingTabsTabLabel {
color: #ffffff;
}
.settingTabsPanel {
@@ -394,3 +967,39 @@
min-height: 0;
padding-top: 16px;
}
@media (max-width: 1200px) {
.filterTopRow,
.filterExpandedGrid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.filterActionsAlignEnd {
justify-self: stretch;
}
}
@media (max-width: 720px) {
.detailIntroGrid {
grid-template-columns: 1fr;
}
.filterTopRow,
.filterExpandedGrid {
grid-template-columns: 1fr;
}
.filterField {
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.filterLabel {
flex-basis: auto;
}
.filterActionsAlignEnd {
grid-column: auto;
}
}

View File

@@ -9,18 +9,36 @@ import {
type PaperProps,
type StackProps,
} from "@mantine/core"
import type { PropsWithChildren, ReactNode } from "react"
import {
DataTable,
type DataTableDefaultColumnProps,
type DataTableProps,
} from "mantine-datatable"
import type { CSSProperties, PropsWithChildren, ReactNode } from "react"
import classes from "./PageSurface.module.css"
function mergeClassName(...classNames: Array<string | undefined>) {
return classNames.filter(Boolean).join(" ")
}
export const settingModalClassNames = {
content: classes.modalContent,
header: classes.modalHeader,
title: classes.modalTitle,
body: classes.modalBody,
}
export const settingModalFormClassName = classes.modalForm
export const settingModalActionsClassName = classes.modalActions
export const settingModalMetaTextClassName = classes.modalMetaText
export const settingSurfaceClassNames = classes
type SettingTableViewportProps = PropsWithChildren<{
maxHeight: number | string
className?: string
style?: CSSProperties
}>
export function SettingPage({
className,
...props
@@ -57,6 +75,21 @@ export function SettingPanel({
)
}
export function SettingTableViewport({
maxHeight,
className,
style,
children,
}: SettingTableViewportProps) {
return (
<div
style={{ maxHeight, ...style }}
className={mergeClassName(classes.tableViewport, className)}>
{children}
</div>
)
}
export function SettingFilterPanel({
className,
...props
@@ -183,6 +216,56 @@ export function SettingFilterActions({
)
}
export function SettingFilterStack({
className,
gap = 12,
...props
}: PropsWithChildren<StackProps>) {
return (
<Stack
gap={gap}
className={mergeClassName(classes.filterStack, className)}
{...props}
/>
)
}
type SettingInlineFilterFieldProps = {
label: ReactNode
children: ReactNode
className?: string
labelClassName?: string
controlClassName?: string
labelWidth?: number | string
}
export function SettingInlineFilterField({
label,
children,
className,
labelClassName,
controlClassName,
labelWidth = 80,
}: SettingInlineFilterFieldProps) {
const style = {
"--setting-inline-filter-label-width":
typeof labelWidth === "number" ? `${labelWidth}px` : labelWidth,
} as CSSProperties
return (
<div
style={style}
className={mergeClassName(classes.filterField, className)}>
<Text className={mergeClassName(classes.filterLabel, labelClassName)}>
{label}
</Text>
<div className={mergeClassName(classes.filterControl, controlClassName)}>
{children}
</div>
</div>
)
}
const settingDataTableStyles = {
header: {
backgroundColor: "#F7F8FA",
@@ -192,7 +275,6 @@ const settingDataTableStyles = {
fontWeight: 500,
},
root: {
height: "100%",
maxWidth: "100%",
minWidth: 0,
fontSize: "14px",
@@ -238,12 +320,15 @@ export function SettingDataTable<T = Record<string, unknown>>(
loadingText,
noRecordsText,
minHeight,
maxHeight,
height,
page,
onPageChange,
totalRecords,
recordsPerPage,
recordsPerPageLabel,
paginationText,
scrollAreaProps,
...rest
} = props
@@ -268,7 +353,7 @@ export function SettingDataTable<T = Record<string, unknown>>(
from: number
to: number
totalRecords: number
}) => `${from}-${to} / ${totalRecords}`),
}) => `显示 ${from}-${to} 条,共 ${totalRecords} 条记录`),
}
: {}
@@ -283,6 +368,12 @@ export function SettingDataTable<T = Record<string, unknown>>(
loaderBackgroundBlur: loaderBackgroundBlur ?? 0,
noRecordsText: noRecordsText ?? "暂无数据",
minHeight: minHeight ?? 360,
maxHeight,
height: height ?? (maxHeight !== undefined ? "auto" : undefined),
scrollAreaProps: {
type: "auto",
...scrollAreaProps,
},
styles: styles
? { ...settingDataTableStyles, ...styles }
: settingDataTableStyles,
@@ -296,16 +387,23 @@ export function SettingDataTable<T = Record<string, unknown>>(
export const settingTabsStyles = {
list: {
padding: 4,
backgroundColor: "#F8F9FB",
border: "1px solid #EEF2F6",
borderRadius: 10,
backgroundColor: "#F7F8FA",
border: "1px solid #E5E6EB",
borderRadius: 4,
gap: 8,
},
tab: {
minHeight: 36,
borderRadius: 8,
color: "#56606A",
fontWeight: 600,
minHeight: 32,
borderRadius: 4,
padding: "0 12px",
fontSize: 14,
fontWeight: 500,
lineHeight: "20px",
},
tabLabel: {
color: "inherit",
fontSize: 14,
lineHeight: "20px",
},
panel: {
flex: 1,
@@ -318,5 +416,6 @@ export const settingTabsStyles = {
export const settingTabsClassNames = {
list: classes.settingTabsList,
tab: classes.settingTabsTab,
tabLabel: classes.settingTabsTabLabel,
panel: classes.settingTabsPanel,
} as const