Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
integrity_risk_prevention
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
integrity_risk_prevention
Commits
2482eb15
Commit
2482eb15
authored
Sep 08, 2026
by
Andy-bubu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add dedicated pie chart palette
parent
6db5196a
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
164 additions
and
1 deletion
+164
-1
ui-architecture.md
docs/ui-architecture.md
+1
-0
ui-migration-checklist.md
docs/ui-migration-checklist.md
+1
-1
xinyuan-design-system.md
docs/xinyuan-design-system.md
+2
-0
xinyuan-ui-audit.md
docs/xinyuan-ui-audit.md
+1
-0
theme.css
src/design-system/theme.css
+17
-0
tokens.css
src/design-system/tokens.css
+17
-0
chartColors.js
src/utils/chartColors.js
+125
-0
No files found.
docs/ui-architecture.md
View file @
2482eb15
...
...
@@ -30,6 +30,7 @@
-
弹窗头部说明由 HeroUI
`Description`
负责;字段标签由共享
`getFieldDisplayLabel`
从字段定义和中文别名表解析,
`metadata.*`
只存在于数据层。文本、选择、日期、人员/引用和附件的提示均由对应 HeroUI 控件在内部展示,禁止再在控件下方复制同一句自动提示。
-
4xl 动态表单由共享
`DynamicRecordDialog`
负责等宽双列编排,字段类型决定是否跨列;独立流程页没有 AppShell 时保留
`RecordWorkbench`
内容标题,主应用列表通过
`showContentTitle={false}`
只使用 Navbar 标题。
-
KPI 只能使用 HeroUI Pro
`KPI`
compound API;共享层不得以 Card class 重新绘制 KPI 的背景、描边、阴影或圆角。
-
图表颜色由共享 Token 和适配器负责:Bar/Line/Ranking 继续使用
`--rc-chart-*`
,Pie/Donut 仅使用
`--rc-pie-chart-*`
。
`applyXinyuanPieChartPalette`
只改 Pie 系列的颜色并按完整稳定分类 key 确定性映射,保留 radius、center、label、labelLine、legend、tooltip、事件及布局;没有真实饼图业务时不得新增伪造页面或数据。
-
所有可截断业务文本由共享
`DataTableText`
、
`DataTableTextPair`
或
`DataTableSecondaryText`
提供,并以 HeroUI
`Tooltip`
暴露完整值;页面不得再写独立截断、换行或原生
`title`
规则。
-
开发工具产物不属于页面依赖图:Umi webpack watcher 在共享配置中忽略
`.playwright-cli`
、
`docs`
、
`dist`
和
`artifacts`
,保证 HeroUI/Inter 样式不会因验收日志写入而反复 HMR。
...
...
docs/ui-migration-checklist.md
View file @
2482eb15
...
...
@@ -42,4 +42,4 @@
-
[
x
]
对齐问题整改
`6763c26`
:详情加载完整 View,Detail 字段权限整套优先、空配置整套回退 Grid、
`disableColumns`
不可旁路且切换 Detail 立即重算。
-
[
x
]
Playwright 权限矩阵覆盖其他应用管理、顶层管理、系统账号、本应用管理、管理员
`disableColumns`
、Detail 优先、Grid 正反向回退、不逐字段合并和多 Detail 切换;控制台为零。
剩余说明:业务没有活动图表路由,因此没有凭空添加 KPI/图表数据;
`recharts`
仅保留原依赖且无源码 import。应用切换遵循
`model_analysis`
专项基准和鑫元规范,使用动态分组、移动一列及
`sm`
起固定两列。当前真实 Detail View 没有 editable file 字段,未为验收修改后端 View 配置;文件事务由浏览器 fixture 验收。
剩余说明:业务没有活动图表路由,因此没有凭空添加 KPI/图表数据;
`recharts`
仅保留原依赖且无源码 import。
饼图/环形图已预置独立
`--xinyuan-pie-chart-*`
/
`--rc-pie-chart-*`
色板及
`applyXinyuanPieChartPalette`
稳定映射,前六色为
`#DA241A`
、
`#3F5F86`
、
`#8A668F`
、
`#C28A3D`
、
`#8A6253`
、
`#667080`
,未改变普通图表 Token。
应用切换遵循
`model_analysis`
专项基准和鑫元规范,使用动态分组、移动一列及
`sm`
起固定两列。当前真实 Detail View 没有 editable file 字段,未为验收修改后端 View 配置;文件事务由浏览器 fixture 验收。
docs/xinyuan-design-system.md
View file @
2482eb15
...
...
@@ -21,6 +21,8 @@
-
KPI 使用 HeroUI Pro KPI;图表使用 HeroUI Pro 图表与 ChartTooltip。
-
鑫元唯一 Primary Brand Color 为
`#DA241A`
;HeroUI
`--accent`
、
`--primary`
、focus、hover、pressed 和按钮 disabled 状态均从品牌红阶派生。
-
`--xinyuan-chart-1`
至
`--xinyuan-chart-16`
是独立于 UI 状态色的可视化 Token,采用品牌红、暖中性色、灰紫和灰蓝的低饱和顺序,不包含绿色或荧光色。单系列使用
`--rc-chart-primary`
,多系列通过
`src/utils/chartColors.js`
按稳定业务 key 分配颜色。
-
饼图 / 环形图是普通分类色板之外的专项例外,使用
`--xinyuan-pie-chart-1`
至
`--xinyuan-pie-chart-16`
(别名
`--rc-pie-chart-*`
)。前六个固定为
`#DA241A`
、
`#3F5F86`
、
`#8A668F`
、
`#C28A3D`
、
`#8A6253`
、
`#667080`
,后续只使用批准的低饱和灰蓝、灰紫、暖棕和暖灰。禁止绿色、青绿色、荧光色和彩虹式配色;普通柱状图、折线图、排名图和品牌主色继续使用原
`--xinyuan-chart-*`
。
-
未来 Pie/Donut 必须通过
`src/utils/chartColors.js`
的
`applyXinyuanPieChartPalette`
或等价共享适配器接入:完整分类 key 经稳定排序、哈希和冲突消解后分配颜色,筛选、排序、分页、Tab 和主题切换不得改变分类颜色;页面传入的旧
`itemStyle.color`
不得覆盖专项色板。
-
Modal/Dropdown/Popover/Tooltip 等 Portal 必须继承根级深色主题。
-
Modal 面板保持 HeroUI Overlay 的 24px 圆角;弹窗内结构性 Surface 只声明官方
`secondary/tertiary`
variant 和布局间距,不再通过业务 JSX 覆盖圆角、背景、描边或阴影。详情中的关联记录属于 L2 详情 Surface 内的静态 L3 内容,使用官方
`Surface variant="tertiary"`
承载,并在其内部使用共享
`DataTable`
的 HeroUI Pro DataGrid
`secondary`
variant;表头保持
`surface-secondary`
、表体保持透明并继承 L3 Surface。禁止页面自定义颜色、描边、阴影或 HeroUI 内部 BEM/slot 外观。
-
Modal 统一带有
`rc-dialog-panel rc-dialog-panel--{size}`
宽度阶梯;宽度只由共享布局类负责,背景、圆角、阴影、关闭和状态由 HeroUI Modal compound API 提供。
...
...
docs/xinyuan-ui-audit.md
View file @
2482eb15
...
...
@@ -37,6 +37,7 @@
-
Portal:Headless UI Dialog、Combobox、Listbox。
-
列表:自制 Table、选择列、分页、空状态和工具栏。
-
图表:未发现活动业务图表、ECharts 私有主题或自制 Tooltip;源码没有
`PieChart`
/
`BarChart`
/
`ComposedChart`
/
`ChartTooltip`
活动页面。
`recharts`
仍是原始依赖并保留在锁文件中,但没有活动 import,不制造测试数据。
-
饼图 / 环形图专项审计:当前仍无真实 Pie/Donut 调用点,未新增伪造图表数据或页面。已新增
`--xinyuan-pie-chart-*`
/
`--rc-pie-chart-*`
专用 Token、稳定分类 key 冲突消解映射和
`applyXinyuanPieChartPalette`
共享适配器;未来接入时不得由页面直接传色或改动普通图表色板。
-
原生控件:文件上传、日期/日期时间输入。
-
私有视觉:大量 zinc/blue/red class、边框、背景、圆角、阴影和状态样式。
...
...
src/design-system/theme.css
View file @
2482eb15
...
...
@@ -107,6 +107,23 @@
--xinyuan-chart-14
:
#8A6F63
;
--xinyuan-chart-15
:
#9A8174
;
--xinyuan-chart-16
:
#B0A5AD
;
/* Pie / donut only. This palette is independent from bar and line colors. */
--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
:
#B0A5AD
;
--xinyuan-pie-chart-14
:
#AFA5B1
;
--xinyuan-pie-chart-15
:
#897A73
;
--xinyuan-pie-chart-16
:
#556B88
;
--xinyuan-chart-sector-gap
:
var
(
--surface
);
--xinyuan-chart-sector-radius
:
10px
;
--xinyuan-chart-grid
:
#e8e8e8
;
...
...
src/design-system/tokens.css
View file @
2482eb15
...
...
@@ -169,6 +169,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 use a dedicated categorical palette. */
--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
);
/* Stable semantic meanings. The palette is intentionally free of green and neon hues. */
--rc-chart-primary
:
var
(
--rc-chart-1
);
--rc-chart-risk
:
var
(
--rc-chart-1
);
...
...
src/utils/chartColors.js
View file @
2482eb15
...
...
@@ -3,6 +3,16 @@ export const XINYUAN_CHART_TOKENS = Object.freeze(
Array
.
from
({
length
:
16
},
(
_
,
index
)
=>
`--rc-chart-
${
index
+
1
}
`
),
);
// Pie and donut sectors use a separate, restrained palette. Keep this list
// independent so adding a pie chart cannot alter bar/line series colors.
export
const
XINYUAN_PIE_CHART_TOKENS
=
Object
.
freeze
(
Array
.
from
({
length
:
16
},
(
_
,
index
)
=>
`--rc-pie-chart-
${
index
+
1
}
`
),
);
export
const
XINYUAN_PIE_CHART_PALETTE
=
Object
.
freeze
(
XINYUAN_PIE_CHART_TOKENS
.
map
((
token
)
=>
`var(
${
token
}
)`
),
);
export
function
getChartColorToken
(
index
=
0
)
{
const
normalizedIndex
=
Math
.
max
(
0
,
Number
(
index
)
||
0
);
if
(
normalizedIndex
>=
XINYUAN_CHART_TOKENS
.
length
)
{
...
...
@@ -11,6 +21,14 @@ export function getChartColorToken(index = 0) {
return
`var(
${
XINYUAN_CHART_TOKENS
[
normalizedIndex
]}
)`
;
}
export
function
getXinyuanPieChartColor
(
index
=
0
)
{
const
normalizedIndex
=
Math
.
max
(
0
,
Number
(
index
)
||
0
);
if
(
normalizedIndex
>=
XINYUAN_PIE_CHART_TOKENS
.
length
)
{
throw
new
RangeError
(
'Xinyuan pie charts support at most 16 distinct categories.'
);
}
return
XINYUAN_PIE_CHART_PALETTE
[
normalizedIndex
];
}
function
hashChartKey
(
key
)
{
let
hash
=
2166136261
;
for
(
let
index
=
0
;
index
<
key
.
length
;
index
+=
1
)
{
...
...
@@ -41,3 +59,110 @@ export function createStableChartColorMap(keys = []) {
}),
);
}
function
normalizePieChartKey
(
key
)
{
return
String
(
key
??
''
).
trim
();
}
function
createStablePieChartColorMap
(
keys
=
[])
{
const
uniqueKeys
=
[...
new
Set
(
keys
.
map
(
normalizePieChartKey
).
filter
(
Boolean
))]
.
sort
((
left
,
right
)
=>
left
.
localeCompare
(
right
,
'zh-Hans'
));
if
(
uniqueKeys
.
length
>
XINYUAN_PIE_CHART_TOKENS
.
length
)
{
throw
new
RangeError
(
'Xinyuan pie charts support at most 16 distinct categories.'
);
}
// Stable hashing plus linear probing keeps a category's color unchanged
// after filtering, sorting, pagination, or tab changes.
const
usedIndexes
=
new
Set
();
return
new
Map
(
uniqueKeys
.
map
((
key
)
=>
{
let
paletteIndex
=
hashChartKey
(
key
)
%
XINYUAN_PIE_CHART_TOKENS
.
length
;
while
(
usedIndexes
.
has
(
paletteIndex
))
{
paletteIndex
=
(
paletteIndex
+
1
)
%
XINYUAN_PIE_CHART_TOKENS
.
length
;
}
usedIndexes
.
add
(
paletteIndex
);
return
[
key
,
getXinyuanPieChartColor
(
paletteIndex
)];
}));
}
export
{
createStablePieChartColorMap
};
const
resolvedPieTokenCache
=
new
Map
();
function
resolvePieChartToken
(
token
)
{
if
(
typeof
document
===
'undefined'
||
typeof
window
===
'undefined'
)
return
token
;
const
match
=
String
(
token
).
match
(
/^var
\((
--
[^
)
]
+
)\)
$/
);
if
(
!
match
)
return
token
;
const
raw
=
window
.
getComputedStyle
(
document
.
documentElement
)
.
getPropertyValue
(
match
[
1
]).
trim
();
const
cached
=
resolvedPieTokenCache
.
get
(
match
[
1
]);
if
(
cached
?.
raw
===
raw
)
return
cached
.
value
;
if
(
!
raw
)
return
token
;
// A detached color probe resolves nested custom-property aliases and keeps
// ECharts options concrete, as required by the chart color contract.
const
probe
=
document
.
createElement
(
'span'
);
probe
.
style
.
color
=
raw
;
if
(
!
probe
.
style
.
color
)
return
token
;
probe
.
style
.
position
=
'absolute'
;
probe
.
style
.
pointerEvents
=
'none'
;
document
.
body
?.
appendChild
(
probe
);
const
value
=
window
.
getComputedStyle
(
probe
).
color
||
token
;
probe
.
remove
();
resolvedPieTokenCache
.
set
(
match
[
1
],
{
raw
,
value
});
return
value
;
}
export
function
createResolvedXinyuanPieChartColorMap
(
keys
=
[])
{
return
new
Map
(
Array
.
from
(
createStablePieChartColorMap
(
keys
)).
map
(([
key
,
token
])
=>
[
key
,
resolvePieChartToken
(
token
),
]),
);
}
/**
* Apply only the dedicated pie/donut colors to an ECharts option. All other
* series and all non-color pie settings are returned unchanged.
*/
export
function
applyXinyuanPieChartPalette
(
option
)
{
if
(
!
option
||
(
!
Array
.
isArray
(
option
.
series
)
&&
!
option
.
series
))
return
option
;
const
seriesList
=
Array
.
isArray
(
option
.
series
)
?
option
.
series
:
[
option
.
series
];
const
pieSeries
=
seriesList
.
filter
((
series
)
=>
series
?.
type
===
'pie'
);
if
(
pieSeries
.
length
===
0
)
return
option
;
const
categoryKeys
=
pieSeries
.
flatMap
((
series
)
=>
(
Array
.
isArray
(
series
.
data
)
?
series
.
data
.
map
((
item
)
=>
(
item
&&
typeof
item
===
'object'
?
item
.
name
:
item
))
:
[]
));
const
colorMap
=
createResolvedXinyuanPieChartColorMap
(
categoryKeys
);
const
piePalette
=
XINYUAN_PIE_CHART_PALETTE
.
map
(
resolvePieChartToken
);
const
themedSeries
=
seriesList
.
map
((
series
)
=>
{
if
(
series
?.
type
!==
'pie'
)
return
series
;
const
seriesItemStyle
=
{
...(
series
.
itemStyle
||
{})
};
delete
seriesItemStyle
.
color
;
return
{
...
series
,
color
:
piePalette
,
data
:
Array
.
isArray
(
series
.
data
)
?
series
.
data
.
map
((
item
,
index
)
=>
{
if
(
!
item
||
typeof
item
!==
'object'
||
Array
.
isArray
(
item
))
return
item
;
const
key
=
normalizePieChartKey
(
item
.
name
);
return
{
...
item
,
itemStyle
:
{
...
item
.
itemStyle
,
color
:
colorMap
.
get
(
key
)
||
piePalette
[
index
%
piePalette
.
length
],
},
};
})
:
series
.
data
,
itemStyle
:
seriesItemStyle
,
};
});
return
{
...
option
,
series
:
Array
.
isArray
(
option
.
series
)
?
themedSeries
:
themedSeries
[
0
],
};
}
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