Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
incorrupt_party_build
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
incorrupt_party_build
Commits
c34e54a1
Commit
c34e54a1
authored
Sep 08, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: sync dedicated pie chart palette
parent
4a3e0202
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
131 additions
and
10 deletions
+131
-10
ui-architecture.md
docs/ui-architecture.md
+1
-0
ui-migration-checklist.md
docs/ui-migration-checklist.md
+1
-0
xinyuan-design-system.md
docs/xinyuan-design-system.md
+2
-1
xinyuan-ui-audit.md
docs/xinyuan-ui-audit.md
+1
-0
XinyuanEChart.jsx
src/components/XinyuanEChart.jsx
+51
-3
chartPalette.js
src/design-system/chartPalette.js
+40
-0
theme.css
src/design-system/theme.css
+17
-0
tokens.css
src/design-system/tokens.css
+17
-0
index.jsx
src/pages/project_manager_xy/resource-stat/index.jsx
+1
-6
No files found.
docs/ui-architecture.md
View file @
c34e54a1
...
...
@@ -50,6 +50,7 @@ Native HeroUI 静态架构门禁已通过:`src/xinyuan-portable.css` 已移除
# 共享 UI 架构补充
`XinyuanEChart`
是图表唯一渲染适配器,页面仅提供业务数据和 ECharts option;适配器统一主题、Tooltip、坐标轴和 Pie 扇区样式。相同分类维度的多个数据项使用
`createResolvedXinyuanChartColorMap`
和
`XINYUAN_CHART_CATEGORY_PALETTE`
按稳定顺序分配颜色,顺序为品牌红深浅 → 暖土色 → 中性灰紫 → 灰蓝,禁止绿色、荧光色和彩虹式跳色;单一指标或多指标系列使用
`XINYUAN_CHART_SEMANTIC`
语义色,单系列固定使用
`#DA241A`
,页面不得写入硬编码颜色。业务源码禁止直接导入 Recharts 或 HeroUI Pro 图表组件;
`recharts`
依赖仅因当前 HeroUI Pro
`KPI`
包的静态运行时解析而保留,不作为应用图表渲染器。
`XinyuanModal`
统一使用
`bg-background`
Dialog Surface。党风廉政台账筛选由 HeroUI
`form`
手动提交,字段值先保留在页面草稿,右侧操作固定为重置图标、
`查询`
主按钮和
`展开/收起`
Disclosure;查询时复用原有
`reload`
请求参数,不改变接口协议。列表分页由
`DataTablePagination`
统一承载容量选择器与页码状态:容量使用 HeroUI Autocomplete 受控单选,标准选项为
`20 / 50 / 100 条/页`
、默认值必须属于选项集合,固定少量选项时关闭
`showSearch`
与
`isClearable`
,切换容量回到第 1 页。
-
Pie/Donut 在共享适配器中独立使用
`--rc-pie-chart-*`
;适配器按完整稳定分类 key 生成确定性颜色映射并覆盖旧页面
`itemStyle.color`
,只改变扇区颜色,不改变半径、间隙、圆角、标签、图例、Tooltip、尺寸或回调。普通 Bar/Line/Ranking 继续使用
`--rc-chart-*`
,不得复用 Pie 专用色板。
长列表 DataGrid 的
`.data-table-scroll`
只隔离横向滚动;纵向在首行/末行继续滚动时必须允许滚动链传递到
`.app-shell-content-scroll`
,保证筛选、列表和 Pagination 可以连续浏览,不增加页面级滚轮监听或改变数据请求时机。
驾驶舱模块标题沿用“审计项目管理”基准的 HeroUI
`Card.Header`
左对齐横向结构;
`Card.Title`
使用共享
`.rc-module-title`
(16px/600、24px 行高、主题前景色)与
`min-w-0 flex-1 text-left`
,操作或描述信息放在标题右侧,不通过页面私有 CSS 改写字号、颜色或字重。
...
...
docs/ui-migration-checklist.md
View file @
c34e54a1
...
...
@@ -87,5 +87,6 @@
-
[
x
]
Pagination 页容量 20/50/100;默认值属于选项集合,选择器关闭清除图标和关键词搜索
-
[
x
]
Modal 背景与页面
`bg-background`
一致
-
[
x
]
资源统计统一 ECharts,饼图扇区 gap/radius
-
[
x
]
饼图/环形图独立使用
`--xinyuan-pie-chart-*`
/
`--rc-pie-chart-*`
:前六色固定、稳定分类 key 映射、冲突消解,无绿色/青绿色/荧光/彩虹色;普通 Bar/Line/Ranking 色板保持不变
-
[
x
]
驾驶舱模块标题统一为 HeroUI
`Card.Title`
+
`.rc-module-title`
:16px/600、24px 行高、主题前景色,标题左对齐,右侧描述与操作保持同一行
-
[
x
]
业务 API、权限、保存回调未改动
docs/xinyuan-design-system.md
View file @
c34e54a1
...
...
@@ -68,7 +68,8 @@
-
Shell 采用固定深色 Sidebar + 浅色内容区,移除主题切换入口。
-
分页支持 20/50/100 条/页选择,切换容量回到第一页。
-
图表统一通过
`XinyuanEChart`
(ECharts)渲染,饼图使用扇区间隙与圆角;同一分类维度的多个数据项(例如饼图扇区、模块柱图)必须通过
`createResolvedXinyuanChartColorMap`
按稳定顺序使用
`XINYUAN_CHART_CATEGORY_PALETTE`
映射,顺序为品牌红深浅 → 暖土色 → 中性灰紫 → 灰蓝,禁止绿色、荧光色和彩虹式跳色。单一指标或不同指标系列使用与基准一致的
`XINYUAN_CHART_SEMANTIC`
语义色,不得把单一指标的每根柱子人为染成不同颜色;单系列统一使用品牌红
`#DA241A`
。年度分布柱图保留单一主色,并显示顶部真实数值、紧凑柱宽和规范圆角,单年份数据也要保持视觉平衡。
-
图表统一通过
`XinyuanEChart`
(ECharts)渲染,饼图使用扇区间隙与圆角;普通分类图表(例如模块柱图、排名图)必须通过
`createResolvedXinyuanChartColorMap`
按稳定顺序使用
`XINYUAN_CHART_CATEGORY_PALETTE`
映射,顺序为品牌红深浅 → 暖土色 → 中性灰紫 → 灰蓝,禁止绿色、荧光色和彩虹式跳色。单一指标或不同指标系列使用与基准一致的
`XINYUAN_CHART_SEMANTIC`
语义色,不得把单一指标的每根柱子人为染成不同颜色;单系列统一使用品牌红
`#DA241A`
。年度分布柱图保留单一主色,并显示顶部真实数值、紧凑柱宽和规范圆角,单年份数据也要保持视觉平衡。
-
饼图 / 环形图是普通图表色板之外的独立例外,仅由共享
`XinyuanEChart`
读取
`--xinyuan-pie-chart-1`
至
`--xinyuan-pie-chart-16`
(别名
`--rc-pie-chart-*`
)分配扇区颜色。前六个颜色固定为
`#DA241A`
、
`#3F5F86`
、
`#8A668F`
、
`#C28A3D`
、
`#8A6253`
、
`#667080`
;其余为低饱和灰蓝、灰紫、暖棕和暖灰扩展色。分类 key 先稳定排序并做冲突消解,同一分类在筛选、排序、Tab 切换和浅深主题下保持同色;禁止绿色、青绿色、荧光色、彩虹色和页面级
`itemStyle.color`
覆盖。该例外不改变普通 Bar/Line/Ranking、KPI、品牌主色、布局或业务逻辑。
-
品牌主色 Token 固定为
`#DA241A`
:HeroUI
`accent`
、
`focus`
、
`primary`
及主按钮默认态均使用该值,Hover 使用
`#C91F17`
,Pressed 使用
`#B51B14`
。
`primary-50`
至
`primary-950`
只能由该品牌红与黑/白按比例派生,浅色和深色主题不得切换到蓝、紫或其他色相。
-
Modal/Dialog 使用
`bg-background`
,详情查看态不再包裹大面积 secondary Surface。
...
...
docs/xinyuan-ui-audit.md
View file @
c34e54a1
...
...
@@ -91,5 +91,6 @@
-
共享壳层、Sidebar 选中态、Navbar 标题层级已对齐基准。
-
台账 Pagination 已接入 20/50/100 页容量选择器,默认 20;选择器关闭清除图标和关键词搜索,并在切换后回到第 1 页。
-
资源统计图表已迁移到共享 ECharts 适配器;Pie gap/radius 与 Tooltip 由适配器统一管理。
-
2026-09-08 饼图专项审计:
`/resource/stat`
的“台账分类分布图”为唯一 Pie 调用点,已从普通
`--rc-chart-*`
分类色板切换至共享
`--rc-pie-chart-*`
专用色板;页面不再提供
`color`
/
`itemStyle.color`
覆盖。年度分布与年度开展次数为 Bar 图,继续使用普通图表 Token,未受影响。
-
资源统计各模块标题已与“审计项目管理”基准统一:HeroUI
`Card.Header`
左对齐横向结构,
`Card.Title`
使用共享
`.rc-module-title`
(16px/600、24px 行高、主题前景色),右侧描述/操作与标题垂直居中。
-
详情与导入预览 Modal 使用页面背景层级,去除查看态大块灰色 Surface。
src/components/XinyuanEChart.jsx
View file @
c34e54a1
import
{
getXinyuanEChartsTheme
}
from
'@/design-system/chartPalette'
;
import
{
createResolvedXinyuanPieChartColorMap
,
getXinyuanEChartsTheme
,
}
from
'@/design-system/chartPalette'
;
import
ReactECharts
from
'echarts-for-react'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
}
from
'react'
;
...
...
@@ -20,8 +23,53 @@ export default function XinyuanEChart({ ariaLabel, className = '', height = 320,
useEffect
(()
=>
{
window
.
addEventListener
(
'resize'
,
hideTooltip
);
return
()
=>
window
.
removeEventListener
(
'resize'
,
hideTooltip
);
},
[
hideTooltip
]);
const
themedOption
=
useMemo
(()
=>
{
const
theme
=
getXinyuanEChartsTheme
();
const
applySeries
=
(
series
)
=>
series
?.
type
===
'pie'
?
({
...
series
,
itemStyle
:
{
borderColor
:
theme
.
sectorGap
,
borderRadius
:
theme
.
sectorRadius
,
borderWidth
:
2
,
...
series
.
itemStyle
},
padAngle
:
series
.
padAngle
??
1
})
:
series
;
const
series
=
Array
.
isArray
(
option
?.
series
)
?
option
.
series
.
map
(
applySeries
)
:
applySeries
(
option
?.
series
);
const
sourceSeries
=
Array
.
isArray
(
option
?.
series
)
?
option
.
series
:
[
option
?.
series
];
const
pieKeys
=
sourceSeries
.
flatMap
((
series
)
=>
(
series
?.
type
===
'pie'
&&
Array
.
isArray
(
series
.
data
)
?
series
.
data
.
map
((
item
)
=>
item
?.
name
)
:
[]
));
const
pieColorMap
=
createResolvedXinyuanPieChartColorMap
(
pieKeys
);
const
applySeries
=
(
series
)
=>
{
if
(
series
?.
type
!==
'pie'
)
return
series
;
const
data
=
Array
.
isArray
(
series
.
data
)
?
series
.
data
.
map
((
item
,
index
)
=>
{
if
(
!
item
||
typeof
item
!==
'object'
||
Array
.
isArray
(
item
))
return
item
;
const
key
=
String
(
item
.
name
??
''
).
trim
();
return
{
...
item
,
itemStyle
:
{
...
item
.
itemStyle
,
// Pie/donut colors always come from the dedicated palette. This
// deliberately overrides legacy page-level itemStyle colors.
color
:
pieColorMap
.
get
(
key
)
||
theme
.
piePalette
[
index
%
theme
.
piePalette
.
length
],
borderColor
:
theme
.
sectorGap
,
borderRadius
:
theme
.
sectorRadius
,
borderWidth
:
2
,
},
};
})
:
series
.
data
;
return
{
...
series
,
color
:
theme
.
piePalette
,
itemStyle
:
{
...
series
.
itemStyle
,
borderColor
:
theme
.
sectorGap
,
borderRadius
:
theme
.
sectorRadius
,
borderWidth
:
2
,
},
data
,
padAngle
:
series
.
padAngle
??
1
,
};
};
const
series
=
Array
.
isArray
(
option
?.
series
)
?
option
.
series
.
map
(
applySeries
)
:
applySeries
(
option
?.
series
);
return
{
animationDuration
:
320
,
animationDurationUpdate
:
240
,
...
...
src/design-system/chartPalette.js
View file @
c34e54a1
...
...
@@ -45,6 +45,34 @@ export const XINYUAN_CHART_CATEGORY_PALETTE = Object.freeze([
'var(--rc-chart-16)'
,
]);
// Pie and donut slices use a separate palette so adjacent sectors stay
// distinguishable without changing the approved bar/line/ranking colors.
export
const
XINYUAN_PIE_CHART_PALETTE
=
Object
.
freeze
([
'var(--rc-pie-chart-1)'
,
'var(--rc-pie-chart-2)'
,
'var(--rc-pie-chart-3)'
,
'var(--rc-pie-chart-4)'
,
'var(--rc-pie-chart-5)'
,
'var(--rc-pie-chart-6)'
,
'var(--rc-pie-chart-7)'
,
'var(--rc-pie-chart-8)'
,
'var(--rc-pie-chart-9)'
,
'var(--rc-pie-chart-10)'
,
'var(--rc-pie-chart-11)'
,
'var(--rc-pie-chart-12)'
,
'var(--rc-pie-chart-13)'
,
'var(--rc-pie-chart-14)'
,
'var(--rc-pie-chart-15)'
,
'var(--rc-pie-chart-16)'
,
]);
const
XINYUAN_PIE_CHART_FALLBACKS
=
Object
.
freeze
([
'#DA241A'
,
'#3F5F86'
,
'#8A668F'
,
'#C28A3D'
,
'#8A6253'
,
'#667080'
,
'#74879A'
,
'#9A8174'
,
'#6F6674'
,
'#B87958'
,
'#61758F'
,
'#807783'
,
'#B0A4A0'
,
'#AFA5B1'
,
'#897A73'
,
'#556B88'
,
]);
export
const
XINYUAN_CHART_SEMANTIC
=
Object
.
freeze
({
primary
:
'var(--rc-chart-primary)'
,
risk
:
'var(--rc-chart-risk)'
,
...
...
@@ -151,6 +179,12 @@ export function createResolvedXinyuanChartColorMap(keys = [], options = {}) {
);
}
export
function
createResolvedXinyuanPieChartColorMap
(
keys
=
[])
{
return
createResolvedXinyuanChartColorMap
(
keys
,
{
palette
:
XINYUAN_PIE_CHART_PALETTE
,
});
}
export
function
getXinyuanEChartsTheme
()
{
const
documentFont
=
typeof
document
===
'undefined'
?
'Inter, sans-serif'
...
...
@@ -165,6 +199,12 @@ export function getXinyuanEChartsTheme() {
palette
:
Array
.
from
({
length
:
16
},
(
_
,
index
)
=>
(
readRootToken
(
`--xinyuan-chart-
${
index
+
1
}
`
,
XINYUAN_CHART_FALLBACKS
[
index
])
)),
piePalette
:
Array
.
from
({
length
:
16
},
(
_
,
index
)
=>
(
resolveXinyuanChartColor
(
XINYUAN_PIE_CHART_PALETTE
[
index
],
XINYUAN_PIE_CHART_FALLBACKS
[
index
],
)
)),
tooltipBackground
:
readRootToken
(
'--xinyuan-chart-tooltip-background'
,
'#ffffff'
),
tooltipBorder
:
readRootToken
(
'--xinyuan-chart-tooltip-border'
,
'#dedede'
),
sectorGap
:
readRootToken
(
'--rc-chart-sector-gap'
,
'#ffffff'
),
...
...
src/design-system/theme.css
View file @
c34e54a1
...
...
@@ -88,6 +88,23 @@
--xinyuan-chart-14
:
#61758F
;
--xinyuan-chart-15
:
#74879A
;
--xinyuan-chart-16
:
#B39A93
;
/* Pie / donut only: independent muted palette with stable sector contrast. */
--xinyuan-pie-chart-1
:
#DA241A
;
--xinyuan-pie-chart-2
:
#3F5F86
;
--xinyuan-pie-chart-3
:
#8A668F
;
--xinyuan-pie-chart-4
:
#C28A3D
;
--xinyuan-pie-chart-5
:
#8A6253
;
--xinyuan-pie-chart-6
:
#667080
;
--xinyuan-pie-chart-7
:
#74879A
;
--xinyuan-pie-chart-8
:
#9A8174
;
--xinyuan-pie-chart-9
:
#6F6674
;
--xinyuan-pie-chart-10
:
#B87958
;
--xinyuan-pie-chart-11
:
#61758F
;
--xinyuan-pie-chart-12
:
#807783
;
--xinyuan-pie-chart-13
:
#B0A4A0
;
--xinyuan-pie-chart-14
:
#AFA5B1
;
--xinyuan-pie-chart-15
:
#897A73
;
--xinyuan-pie-chart-16
:
#556B88
;
--xinyuan-chart-grid
:
#e8e8e8
;
--xinyuan-chart-axis
:
#737373
;
--xinyuan-chart-label
:
#737373
;
...
...
src/design-system/tokens.css
View file @
c34e54a1
...
...
@@ -182,6 +182,23 @@
--rc-chart-14
:
var
(
--xinyuan-chart-14
);
--rc-chart-15
:
var
(
--xinyuan-chart-15
);
--rc-chart-16
:
var
(
--xinyuan-chart-16
);
/* Pie / donut series are intentionally separate from bar/line colors. */
--rc-pie-chart-1
:
var
(
--xinyuan-pie-chart-1
);
--rc-pie-chart-2
:
var
(
--xinyuan-pie-chart-2
);
--rc-pie-chart-3
:
var
(
--xinyuan-pie-chart-3
);
--rc-pie-chart-4
:
var
(
--xinyuan-pie-chart-4
);
--rc-pie-chart-5
:
var
(
--xinyuan-pie-chart-5
);
--rc-pie-chart-6
:
var
(
--xinyuan-pie-chart-6
);
--rc-pie-chart-7
:
var
(
--xinyuan-pie-chart-7
);
--rc-pie-chart-8
:
var
(
--xinyuan-pie-chart-8
);
--rc-pie-chart-9
:
var
(
--xinyuan-pie-chart-9
);
--rc-pie-chart-10
:
var
(
--xinyuan-pie-chart-10
);
--rc-pie-chart-11
:
var
(
--xinyuan-pie-chart-11
);
--rc-pie-chart-12
:
var
(
--xinyuan-pie-chart-12
);
--rc-pie-chart-13
:
var
(
--xinyuan-pie-chart-13
);
--rc-pie-chart-14
:
var
(
--xinyuan-pie-chart-14
);
--rc-pie-chart-15
:
var
(
--xinyuan-pie-chart-15
);
--rc-pie-chart-16
:
var
(
--xinyuan-pie-chart-16
);
--rc-chart-primary
:
var
(
--rc-chart-1
);
--rc-chart-risk
:
var
(
--rc-chart-2
);
--rc-chart-warning
:
var
(
--rc-chart-6
);
...
...
src/pages/project_manager_xy/resource-stat/index.jsx
View file @
c34e54a1
...
...
@@ -71,18 +71,13 @@ function LedgerDistributionChart({ groups }) {
name
:
item
.
label
,
value
:
Number
(
item
.
count
||
0
),
}));
const
colorMap
=
createResolvedXinyuanChartColorMap
(
data
.
map
((
item
)
=>
item
.
name
),
{
order
:
data
.
map
((
item
)
=>
item
.
name
),
palette
:
XINYUAN_CHART_CATEGORY_PALETTE
,
});
if
(
data
.
length
===
0
)
return
<
EmptyBlock
title=
"暂无台账分类数据"
/>;
return
<
XinyuanEChart
ariaLabel=
"台账分类分布图"
option=
{
{
color
:
data
.
map
((
item
)
=>
colorMap
.
get
(
item
.
name
)),
legend
:
{
bottom
:
0
,
data
:
data
.
map
((
item
)
=>
item
.
name
)
},
tooltip
:
{
trigger
:
'item'
,
formatter
:
({
name
,
value
,
percent
})
=>
`${name}<br/>数量:${value} 条(${percent}%)`
},
series
:
[{
type
:
'pie'
,
radius
:
[
'52%'
,
'72%'
],
center
:
[
'50%'
,
'44%'
],
avoidLabelOverlap
:
true
,
data
:
data
.
map
((
item
)
=>
({
name
:
item
.
name
,
value
:
item
.
value
,
itemStyle
:
{
color
:
colorMap
.
get
(
item
.
name
)
}
})),
label
:
{
alignTo
:
'labelLine'
,
distanceToLabelLine
:
6
,
formatter
:
({
name
,
value
})
=>
`{name|${name}}\n{value|${value} 条}`
,
lineHeight
:
18
,
rich
:
{
name
:
{
fontSize
:
13
,
fontWeight
:
500
,
lineHeight
:
18
},
value
:
{
fontSize
:
14
,
fontWeight
:
600
,
lineHeight
:
20
}
}
},
labelLine
:
{
length
:
20
,
length2
:
22
,
lineStyle
:
{
width
:
1.5
}
},
labelLayout
:
(
params
)
=>
{
const
points
=
params
?.
labelLinePoints
;
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
return
null
;
const
endpoint
=
points
[
2
];
const
isRight
=
endpoint
[
0
]
>=
points
[
0
][
0
];
return
{
align
:
isRight
?
'left'
:
'right'
,
hideOverlap
:
true
,
moveOverlap
:
'none'
,
verticalAlign
:
'middle'
,
x
:
endpoint
[
0
]
+
(
isRight
?
8
:
-
8
),
y
:
endpoint
[
1
]
};
}
}]
}
}
height=
{
320
}
/>;
series
:
[{
type
:
'pie'
,
radius
:
[
'52%'
,
'72%'
],
center
:
[
'50%'
,
'44%'
],
avoidLabelOverlap
:
true
,
data
:
data
.
map
((
item
)
=>
({
name
:
item
.
name
,
value
:
item
.
value
})),
label
:
{
alignTo
:
'labelLine'
,
distanceToLabelLine
:
6
,
formatter
:
({
name
,
value
})
=>
`{name|${name}}\n{value|${value} 条}`
,
lineHeight
:
18
,
rich
:
{
name
:
{
fontSize
:
13
,
fontWeight
:
500
,
lineHeight
:
18
},
value
:
{
fontSize
:
14
,
fontWeight
:
600
,
lineHeight
:
20
}
}
},
labelLine
:
{
length
:
20
,
length2
:
22
,
lineStyle
:
{
width
:
1.5
}
},
labelLayout
:
(
params
)
=>
{
const
points
=
params
?.
labelLinePoints
;
if
(
!
Array
.
isArray
(
points
)
||
points
.
length
<
3
)
return
null
;
const
endpoint
=
points
[
2
];
const
isRight
=
endpoint
[
0
]
>=
points
[
0
][
0
];
return
{
align
:
isRight
?
'left'
:
'right'
,
hideOverlap
:
true
,
moveOverlap
:
'none'
,
verticalAlign
:
'middle'
,
x
:
endpoint
[
0
]
+
(
isRight
?
8
:
-
8
),
y
:
endpoint
[
1
]
};
}
}]
}
}
height=
{
320
}
/>;
}
function
YearDistributionChart
({
groups
})
{
...
...
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