Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
A
audit_material_manage
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
audit_material_manage
Commits
a4933ad8
Commit
a4933ad8
authored
Sep 03, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refine material center controls and surfaces
parent
811086b9
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
98 additions
and
107 deletions
+98
-107
xinyuan-design-system.md
docs/xinyuan-design-system.md
+15
-0
DataTable.jsx
src/components/DataTable.jsx
+3
-19
XinyuanAutocomplete.jsx
src/components/XinyuanAutocomplete.jsx
+9
-0
index.jsx
src/pages/project_manager_xy/resource-booking/index.jsx
+71
-59
index.jsx
src/pages/project_manager_xy/resource-stat/index.jsx
+0
-29
No files found.
docs/xinyuan-design-system.md
View file @
a4933ad8
...
...
@@ -53,6 +53,11 @@ paths are not valid delivery mechanisms.
Filters use a direct HeroUI
`Surface`
; lists remain unframed around the
DataGrid. Cards are reserved for discrete business objects and analytical tools.
Material archive filters use an explicit-submit rhythm: draft fields stay local
until
`查询`
, with a compact secondary reset icon button immediately before the
primary red query button and the native Disclosure
`展开`
control to its right.
Reset and category Tab changes submit their corresponding complete filter state;
typing alone never reloads the list.
## Component Rules
...
...
@@ -60,7 +65,14 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
their documented
`@heroui-pro/react/*`
subpaths.
-
Routine text buttons are text-only; icon actions use Ant Design icons,
tooltips and accessible names.
-
Business page, module and field titles stay visually clean without adjacent
information-tip icons; reserve Tooltips for actual icon actions and controls.
-
Dropdown-like selection uses HeroUI Autocomplete/ListBox contracts.
-
Form checkboxes on light surfaces use HeroUI
`Checkbox`
with
`variant="secondary"`
so the unchecked control has a visible neutral fill
against white surfaces. Keep the official
`Checkbox.Content`
->
`Checkbox.Control`
->
`Checkbox.Indicator`
compound structure; selected
controls retain the brand accent, and field values/handlers remain unchanged.
-
Date and date-time fields use HeroUI DatePicker and preserve the existing
string protocol at the page boundary.
-
Files use HeroUI Pro DropZone while preserving the page-owned
`File`
object.
...
...
@@ -69,6 +81,9 @@ DataGrid. Cards are reserved for discrete business objects and analytical tools.
-
Routine success feedback is a fixed, auto-dismissing Toast.
-
Modal view mode uses one secondary Surface for the complete read-only field
group; edit mode removes that Surface and renders secondary fields directly.
-
Detail view modal footers expose only the contextual edit action; destructive
material actions stay in the DataGrid row menu or batch ActionBar to reduce
accidental deletion from a read-only view.
-
Empty results stay empty and request failures stay errors. UI code must never
populate dashboard or list surfaces with fabricated records or numbers.
...
...
src/components/DataTable.jsx
View file @
a4933ad8
import
{
Avatar
,
Button
,
Chip
,
Pagination
,
Spinner
,
Tooltip
}
from
"@heroui/react"
;
import
{
Button
,
Chip
,
Pagination
,
Spinner
,
Tooltip
}
from
"@heroui/react"
;
import
{
ActionBar
}
from
"@heroui-pro/react/action-bar"
;
import
{
DataGrid
}
from
"@heroui-pro/react/data-grid"
;
import
{
EmptyState
}
from
"@heroui-pro/react/empty-state"
;
...
...
@@ -240,18 +240,6 @@ export function DataTableTextPair({ primary, secondary, className = "" }) {
);
}
function
getPersonFallback
(
name
)
{
const
normalized
=
String
(
name
??
""
).
trim
();
if
(
!
normalized
||
normalized
===
"-"
)
{
return
"?"
;
}
const
compact
=
normalized
.
replace
(
/
\s
+/g
,
""
);
if
(
/^
[\u
3400-
\u
9fff
]
/
.
test
(
compact
))
{
return
Array
.
from
(
compact
)[
0
];
}
return
Array
.
from
(
compact
).
slice
(
0
,
2
).
join
(
""
).
toUpperCase
();
}
export
function
DataTablePersonIdentity
({
name
,
account
,
...
...
@@ -265,15 +253,11 @@ export function DataTablePersonIdentity({
return
(
<
Tooltip
delay=
{
0
}
>
<
div
className=
{
[
"flex min-w-0 items-center
gap-2
"
,
className
]
className=
{
[
"flex min-w-0 items-center"
,
className
]
.
filter
(
Boolean
)
.
join
(
" "
)
}
tabIndex=
{
0
}
>
<
Avatar
size=
"sm"
variant=
"soft"
className=
"shrink-0"
>
{
avatar
?
<
Avatar
.
Image
alt=
{
primaryText
}
src=
{
avatar
}
/>
:
null
}
<
Avatar
.
Fallback
>
{
getPersonFallback
(
primaryText
)
}
</
Avatar
.
Fallback
>
</
Avatar
>
<
span
className=
"grid min-w-0 gap-1"
>
<
span
className=
"min-w-0 truncate text-sm font-medium"
>
{
primaryText
}
...
...
@@ -347,7 +331,7 @@ export function DataTablePagination({
{
hasPageSizePicker
?
(
<
XinyuanAutocomplete
aria
-
label=
"每页条数"
className=
"w-
24
shrink-0"
className=
"w-
32
shrink-0"
isDisabled=
{
isDisabled
}
options=
{
pageSizeOptionsForPicker
}
placeholder=
"每页条数"
...
...
src/components/XinyuanAutocomplete.jsx
View file @
a4933ad8
...
...
@@ -41,11 +41,13 @@ export default function XinyuanAutocomplete({
fullWidth
=
true
,
inputValue
,
isDisabled
=
false
,
isClearable
=
false
,
isLoading
=
false
,
isOpen
,
isRequired
=
false
,
name
,
onInputChange
,
onClear
,
onOpenChange
,
onSelectionChange
,
options
=
[],
...
...
@@ -80,6 +82,7 @@ export default function XinyuanAutocomplete({
(
selectedOptions
.
length
>
0
?
selectedOptions
.
map
((
option
)
=>
option
.
label
).
join
(
"、"
)
:
placeholder
);
const
hasSelection
=
Array
.
from
(
selectedKeyStringSet
).
some
(
Boolean
);
const
rootSelectionProps
=
selectionMode
===
"multiple"
?
{
selectedKeys
}
:
{
selectedKey
};
const
listBoxSelectionProps
=
...
...
@@ -118,6 +121,7 @@ export default function XinyuanAutocomplete({
isOpen=
{
isOpen
}
isRequired=
{
isRequired
}
name=
{
name
}
onClear=
{
onClear
}
selectionMode=
{
selectionMode
}
variant=
{
variant
}
onOpenChange=
{
onOpenChange
}
...
...
@@ -129,6 +133,11 @@ export default function XinyuanAutocomplete({
{
resolvedValueText
}
</
span
>
</
Autocomplete
.
Value
>
{
isClearable
&&
hasSelection
?
(
<
Autocomplete
.
ClearButton
aria
-
label=
{
`清除${ariaLabel || placeholder}`
}
/>
)
:
null
}
<
Autocomplete
.
Indicator
>
<
ChevronDown
size=
{
16
}
style=
{
FIELD_SUFFIX_ICON_STYLE
}
/>
</
Autocomplete
.
Indicator
>
...
...
src/pages/project_manager_xy/resource-booking/index.jsx
View file @
a4933ad8
...
...
@@ -19,11 +19,11 @@ import {
}
from
"@/components/TableActionButton"
;
import
{
UserContext
}
from
"@/wrapper/Auth"
;
import
{
ArrowPathIcon
,
ArrowTopRightOnSquareIcon
,
ChevronDownIcon
,
ClipboardDocumentIcon
,
FolderIcon
,
InformationCircleIcon
,
MagnifyingGlassIcon
,
XMarkIcon
,
}
from
"@/components/AppIcons"
;
...
...
@@ -51,7 +51,6 @@ import { DropZone } from "@heroui-pro/react/drop-zone";
import
{
startTransition
,
useContext
,
useDeferredValue
,
useEffect
,
useMemo
,
useState
,
...
...
@@ -235,35 +234,12 @@ function buildDetailForm(material) {
};
}
function
TitleInfoTooltip
({
label
,
text
,
tone
=
"neutral"
})
{
return
(
<
Tooltip
delay=
{
0
}
>
<
Button
isIconOnly
aria
-
label=
{
`${label}说明`
}
className=
{
clsx
(
"rc-title-info-trigger"
,
tone
===
"warning"
&&
"text-warning"
,
)
}
type=
"button"
variant=
"ghost"
>
<
InformationCircleIcon
size=
{
14
}
/>
</
Button
>
<
Tooltip
.
Content
>
{
text
}
</
Tooltip
.
Content
>
</
Tooltip
>
);
}
function
FormSection
({
title
,
description
,
children
,
className
})
{
return
(
<
section
className=
{
clsx
(
"min-w-0 space-y-4"
,
className
)
}
>
<
div
className=
"min-w-0"
>
<
div
className=
"inline-flex items-center gap-1.5 text-base font-medium"
>
<
span
>
{
title
}
</
span
>
{
description
?
(
<
TitleInfoTooltip
label=
{
title
}
text=
{
description
}
/>
)
:
null
}
</
div
>
</
div
>
<
div
>
{
children
}
</
div
>
...
...
@@ -288,9 +264,6 @@ function FieldLabel({ children, required = false, infoText, infoTone }) {
{
required
?
(
<
span
className=
"text-danger"
>
*
</
span
>
)
:
null
}
{
infoText
?
(
<
TitleInfoTooltip
label=
{
label
}
text=
{
infoText
}
tone=
{
infoTone
}
/>
)
:
null
}
</
div
>
);
}
...
...
@@ -350,6 +323,7 @@ function ConfiguredSelect({
emptyText=
{
`暂无${fieldLabel}配置`
}
errorText=
{
error
}
isDisabled=
{
disabled
||
loading
||
Boolean
(
error
)
}
isClearable=
{
String
(
value
??
""
)
!==
""
}
isLoading=
{
loading
}
options=
{
[
{
id
:
""
,
label
:
emptyLabel
},
...
...
@@ -368,6 +342,9 @@ function ConfiguredSelect({
onSelectionChange=
{
(
key
)
=>
{
props
.
onChange
?.({
target
:
{
value
:
String
(
key
??
""
)
}
});
}
}
onClear=
{
()
=>
{
props
.
onChange
?.({
target
:
{
value
:
""
}
});
}
}
/>
{
showError
&&
error
?
(
<
div
className=
"mt-2 text-xs text-danger"
role=
"alert"
>
...
...
@@ -415,9 +392,10 @@ export default function ResourceBookingPage() {
const
[
materialSearchForm
,
setMaterialSearchForm
]
=
useState
(
buildEmptyMaterialSearchForm
,
);
const
[
submittedMaterialSearchForm
,
setSubmittedMaterialSearchForm
]
=
useState
(
buildEmptyMaterialSearchForm
);
const
[
isMaterialFilterExpanded
,
setIsMaterialFilterExpanded
]
=
useState
(
false
);
const
deferredMaterialSearchForm
=
useDeferredValue
(
materialSearchForm
);
const
[
activeCategory
,
setActiveCategory
]
=
useState
(
ALL_CATEGORY_KEY
);
const
[
pageSize
,
setPageSize
]
=
useState
(
DEFAULT_PAGE_SIZE
);
const
[
page
,
setPage
]
=
useState
(
0
);
...
...
@@ -509,7 +487,7 @@ export default function ResourceBookingPage() {
async
function
runFirstPage
()
{
setLoading
(
true
);
const
result
=
await
loadMaterialPage
({
...
deferr
edMaterialSearchForm
,
...
submitt
edMaterialSearchForm
,
page
,
pageSize
,
});
...
...
@@ -546,7 +524,7 @@ export default function ResourceBookingPage() {
return
()
=>
{
cancelled
=
true
;
};
},
[
activeMaterialId
,
deferr
edMaterialSearchForm
,
page
,
pageSize
]);
},
[
activeMaterialId
,
submitt
edMaterialSearchForm
,
page
,
pageSize
]);
const
sourceOptions
=
useMemo
(()
=>
{
const
dynamicOptions
=
(
dashboard
.
sourceSuggestions
||
[]).
filter
(
Boolean
);
...
...
@@ -763,12 +741,18 @@ export default function ResourceBookingPage() {
...
prev
,
[
fieldName
]:
value
,
}));
}
function
submitMaterialSearch
()
{
setSubmittedMaterialSearchForm
({
...
materialSearchForm
});
setActiveCategory
(
ALL_CATEGORY_KEY
);
setPage
(
0
);
}
function
resetMaterialSearch
()
{
setMaterialSearchForm
(
buildEmptyMaterialSearchForm
());
const
emptySearchForm
=
buildEmptyMaterialSearchForm
();
setMaterialSearchForm
(
emptySearchForm
);
setSubmittedMaterialSearchForm
(
emptySearchForm
);
setActiveCategory
(
ALL_CATEGORY_KEY
);
setPage
(
0
);
setIsMaterialFilterExpanded
(
false
);
...
...
@@ -776,16 +760,18 @@ export default function ResourceBookingPage() {
function
handleCategoryTabClick
(
categoryKey
)
{
setActiveCategory
(
categoryKey
);
setMaterialSearchForm
((
prev
)
=>
(
{
...
prev
,
const
nextSearchForm
=
{
...
submittedMaterialSearchForm
,
archiveCategory
:
categoryKey
===
ALL_CATEGORY_KEY
?
""
:
categoryKey
,
}));
};
setMaterialSearchForm
(
nextSearchForm
);
setSubmittedMaterialSearchForm
(
nextSearchForm
);
setPage
(
0
);
}
async
function
reloadMaterials
(
options
=
{})
{
const
result
=
await
loadMaterialPage
({
...
deferr
edMaterialSearchForm
,
...
submitt
edMaterialSearchForm
,
page
:
options
.
page
??
page
,
pageSize
,
});
...
...
@@ -1293,6 +1279,12 @@ export default function ResourceBookingPage() {
isExpanded=
{
shouldShowAdvancedMaterialFilters
}
onExpandedChange=
{
setIsMaterialFilterExpanded
}
>
<
form
onSubmit=
{
(
event
)
=>
{
event
.
preventDefault
();
submitMaterialSearch
();
}
}
>
<
div
className=
"grid min-w-0 gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end"
>
<
div
className=
"grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-3"
>
<
div
className=
"min-w-0"
>
...
...
@@ -1344,7 +1336,32 @@ export default function ResourceBookingPage() {
</
div
>
<
div
className=
"flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap"
>
<
Disclosure
.
Trigger
className=
"button button--sm button--ghost"
>
<
Tooltip
delay=
{
0
}
>
<
Button
aria
-
label=
"重置筛选"
isDisabled=
{
loading
}
isIconOnly
size=
"sm"
type=
"button"
variant=
"secondary"
onPress=
{
resetMaterialSearch
}
>
<
ArrowPathIcon
size=
{
16
}
/>
</
Button
>
<
Tooltip
.
Content
>
重置筛选
</
Tooltip
.
Content
>
</
Tooltip
>
<
Button
isDisabled=
{
loading
}
size=
"sm"
type=
"submit"
variant=
"primary"
>
查询
</
Button
>
<
Disclosure
.
Trigger
className=
"button button--sm button--ghost"
type=
"button"
>
<
span
>
{
shouldShowAdvancedMaterialFilters
?
"收起"
:
"展开"
}
</
span
>
<
Disclosure
.
Indicator
>
<
ChevronDownIcon
aria
-
hidden=
"true"
size=
{
14
}
/>
...
...
@@ -1463,6 +1480,7 @@ export default function ResourceBookingPage() {
</
div
>
</
Disclosure
.
Content
>
</
div
>
</
form
>
</
Disclosure
>
</
Surface
>
...
...
@@ -1660,13 +1678,7 @@ export default function ResourceBookingPage() {
</
Modal
.
CloseTrigger
>
<
Modal
.
Header
>
<
div
className=
"min-w-0"
>
<
div
className=
"inline-flex items-center gap-1.5"
>
<
Modal
.
Heading
>
材料详情
</
Modal
.
Heading
>
<
TitleInfoTooltip
label=
"材料详情"
text=
"默认以只读方式查看材料元数据;点击编辑后再修改,保存成功后生效。"
/>
</
div
>
</
div
>
</
Modal
.
Header
>
<
Modal
.
Body
>
...
...
@@ -1888,6 +1900,7 @@ export default function ResourceBookingPage() {
<
div
className=
"grid gap-3 sm:grid-cols-2"
>
<
HeroCheckbox
className=
"min-h-18"
variant=
"secondary"
isSelected=
{
detailForm
.
isConfidential
}
isDisabled=
{
saving
}
onChange=
{
(
checked
)
=>
...
...
@@ -1913,6 +1926,7 @@ export default function ResourceBookingPage() {
</
HeroCheckbox
>
<
HeroCheckbox
className=
"min-h-18"
variant=
"secondary"
isSelected=
{
detailForm
.
isAuditDeptMaterial
}
isDisabled=
{
saving
}
onChange=
{
(
checked
)
=>
...
...
@@ -1966,14 +1980,6 @@ export default function ResourceBookingPage() {
)
}
</
Modal
.
Body
>
<
Modal
.
Footer
>
<
Button
variant=
"danger-soft"
className=
"cursor-pointer"
onPress=
{
handleRemoveActiveMaterial
}
isDisabled=
{
saving
||
!
activeMaterial
}
>
{
saving
?
"处理中..."
:
"删除材料"
}
</
Button
>
{
isDetailEditing
?
(
<>
<
Button
...
...
@@ -2051,6 +2057,7 @@ export default function ResourceBookingPage() {
<
div
className=
"grid min-w-0 gap-4 sm:grid-cols-2"
>
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
archiveYear
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2097,6 +2104,7 @@ export default function ResourceBookingPage() {
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
sourceApp
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2144,6 +2152,7 @@ export default function ResourceBookingPage() {
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
sourceSystem
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2193,6 +2202,7 @@ export default function ResourceBookingPage() {
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
archiveCategory
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2242,6 +2252,7 @@ export default function ResourceBookingPage() {
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
provideDepartment
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2288,6 +2299,7 @@ export default function ResourceBookingPage() {
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
batchEditFields
.
provideTime
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2339,6 +2351,7 @@ export default function ResourceBookingPage() {
<
div
className=
"grid gap-4 sm:grid-cols-2"
>
<
div
className=
"space-y-3"
>
<
HeroCheckbox
variant=
"secondary"
isSelected=
{
batchEditFields
.
isConfidential
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2368,6 +2381,7 @@ export default function ResourceBookingPage() {
</
HeroCheckbox
.
Content
>
</
HeroCheckbox
>
<
HeroCheckbox
variant=
"secondary"
isSelected=
{
batchEditForm
.
isConfidential
}
onChange=
{
(
checked
)
=>
setBatchEditForm
((
prev
)
=>
({
...
...
@@ -2394,6 +2408,7 @@ export default function ResourceBookingPage() {
<
div
className=
"space-y-3"
>
<
HeroCheckbox
variant=
"secondary"
isSelected=
{
batchEditFields
.
isAuditDeptMaterial
}
onChange=
{
(
checked
)
=>
setBatchEditFields
((
prev
)
=>
({
...
...
@@ -2423,6 +2438,7 @@ export default function ResourceBookingPage() {
</
HeroCheckbox
.
Content
>
</
HeroCheckbox
>
<
HeroCheckbox
variant=
"secondary"
isSelected=
{
batchEditForm
.
isAuditDeptMaterial
}
onChange=
{
(
checked
)
=>
setBatchEditForm
((
prev
)
=>
({
...
...
@@ -2566,16 +2582,10 @@ export default function ResourceBookingPage() {
>
<
Modal
.
Header
>
<
Modal
.
Heading
>
上传材料
</
Modal
.
Heading
>
<
Description
>
创建页已按最新对象文档补齐字段,上传完成后会统一保存到材料档案。
</
Description
>
</
Modal
.
Header
>
<
Modal
.
Body
>
<
div
className=
"space-y-6"
>
<
FormSection
title=
"上传文件"
description=
"先完成文件选择,系统会自动尝试用文件名回填材料标题。"
>
<
div
>
<
DropZone
className=
"min-h-48"
>
<
DropZone
.
Area
onDrop=
{
async
(
event
)
=>
{
...
...
@@ -2619,7 +2629,7 @@ export default function ResourceBookingPage() {
</
div
>
)
:
null
}
</
DropZone
>
</
FormSection
>
</
div
>
{
selectedFileIsZip
?
(
<
FormSection
...
...
@@ -2844,6 +2854,7 @@ export default function ResourceBookingPage() {
<
div
className=
"grid gap-4 sm:grid-cols-2"
>
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
uploadForm
.
isConfidential
}
onChange=
{
(
checked
)
=>
setUploadForm
((
prev
)
=>
({
...
...
@@ -2874,6 +2885,7 @@ export default function ResourceBookingPage() {
</
HeroCheckbox
>
<
HeroCheckbox
className=
"min-h-20"
variant=
"secondary"
isSelected=
{
uploadForm
.
isAuditDeptMaterial
}
onChange=
{
(
checked
)
=>
setUploadForm
((
prev
)
=>
({
...
...
src/pages/project_manager_xy/resource-stat/index.jsx
View file @
a4933ad8
...
...
@@ -13,7 +13,6 @@ import {
CircleStackIcon
,
DocumentTextIcon
,
FolderOpenIcon
,
InformationCircleIcon
,
ShieldCheckIcon
,
ShieldExclamationIcon
,
}
from
"@/components/AppIcons"
;
...
...
@@ -29,7 +28,6 @@ import {
Chip
,
ProgressBar
as
HeroProgressBar
,
Spinner
,
Tooltip
,
}
from
"@heroui/react"
;
import
{
EmptyState
}
from
"@heroui-pro/react/empty-state"
;
import
{
KPI
}
from
"@heroui-pro/react/kpi"
;
...
...
@@ -48,9 +46,6 @@ function Panel({ title, titleInfo, className, children }) {
<
Card
.
Title
className=
"rc-module-title min-w-0 flex-1"
>
{
title
}
</
Card
.
Title
>
{
titleInfo
?
(
<
TitleInfoTooltip
label=
{
title
}
text=
{
titleInfo
}
/>
)
:
null
}
</
Card
.
Header
>
)
:
null
}
<
Card
.
Content
className=
{
className
}
>
{
children
}
</
Card
.
Content
>
...
...
@@ -65,9 +60,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
<
div
className=
"flex w-full min-w-0 items-center justify-between gap-4"
>
<
Card
.
Title
className=
"rc-module-title inline-flex min-w-0 items-center gap-1.5"
>
<
span
>
{
title
}
</
span
>
{
titleInfo
?
(
<
TitleInfoTooltip
label=
{
title
}
text=
{
titleInfo
}
/>
)
:
null
}
</
Card
.
Title
>
<
span
aria
-
hidden=
"true"
...
...
@@ -85,23 +77,6 @@ function GovernanceCard({ title, titleInfo, icon: Icon, tone, children }) {
);
}
function
TitleInfoTooltip
({
label
,
text
})
{
return
(
<
Tooltip
delay=
{
0
}
>
<
Button
isIconOnly
aria
-
label=
{
`${label}说明`
}
className=
"rc-title-info-trigger"
type=
"button"
variant=
"ghost"
>
<
InformationCircleIcon
size=
{
14
}
/>
</
Button
>
<
Tooltip
.
Content
>
{
text
}
</
Tooltip
.
Content
>
</
Tooltip
>
);
}
function
MetricCard
({
icon
:
Icon
,
label
,
value
,
hint
,
tone
})
{
const
numericValue
=
Number
(
value
||
0
);
...
...
@@ -111,7 +86,6 @@ function MetricCard({ icon: Icon, label, value, hint, tone }) {
<
KPI
.
Title
>
<
span
className=
"inline-flex items-center gap-1.5"
>
{
label
}
{
hint
?
<
TitleInfoTooltip
label=
{
label
}
text=
{
hint
}
/>
:
null
}
</
span
>
</
KPI
.
Title
>
</
KPI
.
Header
>
...
...
@@ -135,9 +109,6 @@ function SectionHeader({ title, description }) {
return
(
<
h2
className=
"rc-section-title inline-flex items-center gap-1.5"
>
<
span
>
{
title
}
</
span
>
{
description
?
(
<
TitleInfoTooltip
label=
{
title
}
text=
{
description
}
/>
)
:
null
}
</
h2
>
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment