Commit 90949774 authored by Andy-bubu's avatar Andy-bubu

feat: unify Xinyuan brand and filter visuals

parent 174de5ea
...@@ -13,6 +13,12 @@ Inter 禁止通过 `src/tailwind.css` 导入。Tailwind v4 内容扫描必须排 ...@@ -13,6 +13,12 @@ Inter 禁止通过 `src/tailwind.css` 导入。Tailwind v4 内容扫描必须排
页面 `className` 仅承担 flex、grid、宽度、间距、对齐、滚动和响应式。背景、颜色、描边、圆角、阴影及交互状态由官方 variant、compound API 和上述共享样式负责。 页面 `className` 仅承担 flex、grid、宽度、间距、对齐、滚动和响应式。背景、颜色、描边、圆角、阴影及交互状态由官方 variant、compound API 和上述共享样式负责。
## 品牌与图表色板
- 鑫元品牌主色固定为 `#DA241A`。HeroUI v3 的 `--accent`、`--accent-hover`、`--focus` 以及共享 `--rc-brand-primary` 均不得被页面或深色主题改写为其他色相;primary 色阶仅作为兼容别名维护。
- 图表统一使用 `--rc-chart-1` 至 `--rc-chart-16`,由 `src/design-system/chartTokens.js` 提供 ECharts 的解析值。顺序为品牌红、低饱和暖色、灰紫中性色和蓝灰色,禁止绿色、荧光色和彩虹式相邻对撞;单系列始终使用 `chart-1`。
- KPI 图标色调复用同一图表色板的暖色/灰紫/蓝灰衍生值,卡片表面保持中性,不以高饱和渐变区分指标。
## 组件合同 ## 组件合同
- Shell:`@heroui-pro/react/sidebar`、`navbar`;桌面 offcanvas 真折叠,移动端 `Sidebar.Mobile`;导航分组首次进入时默认展开,用户仍可手动收起。 - Shell:`@heroui-pro/react/sidebar`、`navbar`;桌面 offcanvas 真折叠,移动端 `Sidebar.Mobile`;导航分组首次进入时默认展开,用户仍可手动收起。
......
...@@ -604,7 +604,7 @@ export function DataTablePagination({ ...@@ -604,7 +604,7 @@ export function DataTablePagination({
{hasPageSizePicker ? ( {hasPageSizePicker ? (
<XinyuanAutocomplete <XinyuanAutocomplete
aria-label="每页条数" aria-label="每页条数"
className="w-24 shrink-0" className="w-32 shrink-0"
isDisabled={isDisabled} isDisabled={isDisabled}
options={pageSizeOptionsForPicker} options={pageSizeOptionsForPicker}
placeholder="每页条数" placeholder="每页条数"
......
import { Close, Eye, More, Reload, Search } from '@/components/AppIcons'; import { ChevronDown, Close, Eye, More, Reload, Search } from '@/components/AppIcons';
import { TableActionButton, TableActionGroup } from '@/components/TableActionButton'; import { TableActionButton, TableActionGroup } from '@/components/TableActionButton';
import FilterSelect from '@/components/FilterSelect'; import FilterSelect from '@/components/FilterSelect';
import FormDatePicker from '@/components/FormDatePicker'; import FormDatePicker from '@/components/FormDatePicker';
...@@ -174,6 +174,7 @@ export default function RecordWorkbench({ ...@@ -174,6 +174,7 @@ export default function RecordWorkbench({
filterSchema = EMPTY_ARRAY, filterSchema = EMPTY_ARRAY,
onFiltersChange, onFiltersChange,
onClearFilters, onClearFilters,
onQuery,
title, title,
listTitle, listTitle,
columns = EMPTY_ARRAY, columns = EMPTY_ARRAY,
...@@ -373,20 +374,45 @@ export default function RecordWorkbench({ ...@@ -373,20 +374,45 @@ export default function RecordWorkbench({
{structuredFilterFields.length > 0 ? ( {structuredFilterFields.length > 0 ? (
<Surface className="rc-filter-surface p-6" variant="default"> <Surface className="rc-filter-surface p-6" variant="default">
<Disclosure isExpanded={filtersExpanded} onExpandedChange={setFiltersExpanded}> <Disclosure isExpanded={filtersExpanded} onExpandedChange={setFiltersExpanded}>
<form
onSubmit={(event) => {
event.preventDefault();
onQuery?.();
}}
>
<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 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
<div className="grid min-w-0 gap-3 sm:grid-cols-2 xl:grid-cols-3"> <div className="grid min-w-0 gap-3 sm:grid-cols-2 xl:grid-cols-3">
{primaryFilterFields.map(renderFilterField)} {primaryFilterFields.map(renderFilterField)}
</div> </div>
<div className="flex min-w-0 items-end justify-end gap-2"> <div className="flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap">
{onClearFilters ? ( {onClearFilters ? (
<Tooltip delay={0}> <Tooltip delay={0}>
<Button isIconOnly aria-label="重置筛选" size="sm" variant="ghost" onPress={onClearFilters}><Reload size={16} /></Button> <Button
aria-label="重置筛选"
className="rc-filter-reset-button"
isDisabled={Boolean(states.loading)}
isIconOnly
size="sm"
variant="secondary"
type="button"
onPress={onClearFilters}
>
<Reload size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content> <Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip> </Tooltip>
) : null} ) : null}
{onQuery ? (
<Button isDisabled={Boolean(states.loading)} size="sm" variant="primary" type="submit">
查询
</Button>
) : null}
{expandedFilterFields.length > 0 ? ( {expandedFilterFields.length > 0 ? (
<Disclosure.Trigger className={buttonVariants({ size: 'sm', variant: 'ghost' })}> <Disclosure.Trigger className={buttonVariants({ size: 'sm', variant: 'ghost' })}>
<span>{filtersExpanded ? '收起' : '展开'}</span> <span>{filtersExpanded ? '收起' : '展开'}</span>
<Disclosure.Indicator>
<ChevronDown aria-hidden="true" size={14} />
</Disclosure.Indicator>
</Disclosure.Trigger> </Disclosure.Trigger>
) : null} ) : null}
</div> </div>
...@@ -398,6 +424,7 @@ export default function RecordWorkbench({ ...@@ -398,6 +424,7 @@ export default function RecordWorkbench({
</Disclosure.Content> </Disclosure.Content>
) : null} ) : null}
</div> </div>
</form>
</Disclosure> </Disclosure>
</Surface> </Surface>
) : null} ) : null}
......
/**
* Xinyuan chart palette in visual order: brand red, warm neutrals, mauve
* neutrals, then restrained blue-greys. Green and fluorescent hues are not
* part of the visualization system.
*/
export const XINYUAN_CHART_COLORS = [
'#DA241A',
'#B83A32',
'#C95A52',
'#D47A70',
'#A65A45',
'#B87958',
'#C99772',
'#6F6674',
'#807783',
'#938B96',
'#4F6380',
'#61758F',
'#74879A',
'#8A6F63',
'#9A8174',
'#B5A69D',
];
export function getXinyuanChartColors() {
if (typeof window === 'undefined') {
return XINYUAN_CHART_COLORS;
}
const styles = getComputedStyle(document.documentElement);
return XINYUAN_CHART_COLORS.map((fallback, index) => (
styles.getPropertyValue(`--rc-chart-${index + 1}`).trim() || fallback
));
}
...@@ -552,6 +552,27 @@ ...@@ -552,6 +552,27 @@
border-radius: var(--rc-radius-overlay); border-radius: var(--rc-radius-overlay);
} }
.rc-filter-reset-button.button {
width: 2.25rem;
min-width: 2.25rem;
max-width: 2.25rem;
height: 2.25rem;
flex: 0 0 2.25rem;
padding: 0;
border-radius: 999px;
color: var(--rc-brand-primary);
}
.rc-filter-reset-button.button > span,
.rc-filter-reset-button.button .anticon,
.rc-filter-reset-button.button svg {
display: inline-grid;
width: 1rem;
height: 1rem;
place-items: center;
margin: 0;
}
/* Filter fields paint their HeroUI focus/invalid ring outside the disclosure /* Filter fields paint their HeroUI focus/invalid ring outside the disclosure
panel. The workbench unmounts this content while the filter is collapsed. */ panel. The workbench unmounts this content while the filter is collapsed. */
.rc-filter-disclosure-content { .rc-filter-disclosure-content {
...@@ -729,7 +750,10 @@ ...@@ -729,7 +750,10 @@
max-height: min(60dvh, 42rem); max-height: min(60dvh, 42rem);
overflow-x: auto; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain; /* Keep horizontal overflow inside the grid, but let vertical wheel/touch
scrolling continue into the page once the last row is reached. */
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
} }
/* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the /* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the
......
...@@ -11,8 +11,11 @@ ...@@ -11,8 +11,11 @@
[data-theme="light"], [data-theme="light"],
[data-theme="default"] { [data-theme="default"] {
/* Theme Colors (Light Mode) */ /* Theme Colors (Light Mode) */
--accent: oklch(57.14% 0.2156 29.20); /* Xinyuan brand red. HeroUI's accent token is the v3 primary color. */
--accent: #DA241A;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--accent-hover: #C91F17;
--accent-pressed: #B51B14;
--background: oklch(97.02% 0.0000 29.20); --background: oklch(97.02% 0.0000 29.20);
--border: oklch(90.00% 0.0000 29.20); --border: oklch(90.00% 0.0000 29.20);
--danger: oklch(45.32% 0.1661 27.03); --danger: oklch(45.32% 0.1661 27.03);
...@@ -23,7 +26,7 @@ ...@@ -23,7 +26,7 @@
--field-border: transparent; --field-border: transparent;
--field-foreground: oklch(21.03% 0.0000 29.20); --field-foreground: oklch(21.03% 0.0000 29.20);
--field-placeholder: oklch(55.17% 0.0000 29.20); --field-placeholder: oklch(55.17% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20); --focus: #DA241A;
--foreground: oklch(21.03% 0.0000 29.20); --foreground: oklch(21.03% 0.0000 29.20);
--muted: oklch(55.17% 0.0000 29.20); --muted: oklch(55.17% 0.0000 29.20);
--overlay: oklch(100.00% 0.0000 29.20); --overlay: oklch(100.00% 0.0000 29.20);
...@@ -32,8 +35,8 @@ ...@@ -32,8 +35,8 @@
--segment: oklch(100.00% 0.0000 29.20); --segment: oklch(100.00% 0.0000 29.20);
--segment-foreground: oklch(21.03% 0.0000 29.20); --segment-foreground: oklch(21.03% 0.0000 29.20);
--separator: oklch(92.00% 0.0000 29.20); --separator: oklch(92.00% 0.0000 29.20);
--success: oklch(73.29% 0.1935 123.85); --success: #61758F;
--success-foreground: oklch(21.03% 0.0059 123.85); --success-foreground: #ffffff;
--surface: oklch(100.00% 0.0000 29.20); --surface: oklch(100.00% 0.0000 29.20);
--surface-foreground: oklch(21.03% 0.0000 29.20); --surface-foreground: oklch(21.03% 0.0000 29.20);
--surface-secondary: oklch(95.24% 0.0000 29.20); --surface-secondary: oklch(95.24% 0.0000 29.20);
...@@ -70,24 +73,24 @@ ...@@ -70,24 +73,24 @@
* Ant Design remains the icon source. Resolve these CSS variables to * Ant Design remains the icon source. Resolve these CSS variables to
* concrete colors before assigning them to an ECharts option object. * concrete colors before assigning them to an ECharts option object.
*/ */
/* First pass: red -> pink -> orange -> yellow -> blue -> purple -> cyan -> neutral. */ /* Coordinated low-conflict chart palette: brand, warm neutrals, mauve
--xinyuan-chart-1: #991b1b; neutrals and restrained blue-greys. */
--xinyuan-chart-2: #db2777; --xinyuan-chart-1: #DA241A;
--xinyuan-chart-3: #ea580c; --xinyuan-chart-2: #B83A32;
--xinyuan-chart-4: #eab308; --xinyuan-chart-3: #C95A52;
--xinyuan-chart-5: #1d4ed8; --xinyuan-chart-4: #D47A70;
--xinyuan-chart-6: #7e22ce; --xinyuan-chart-5: #A65A45;
--xinyuan-chart-7: #0891b2; --xinyuan-chart-6: #B87958;
--xinyuan-chart-8: #475569; --xinyuan-chart-7: #C99772;
/* Second pass: stronger/lighter companions for charts with more series. */ --xinyuan-chart-8: #6F6674;
--xinyuan-chart-9: #dc2626; --xinyuan-chart-9: #807783;
--xinyuan-chart-10: #ec4899; --xinyuan-chart-10: #938B96;
--xinyuan-chart-11: #f97316; --xinyuan-chart-11: #4F6380;
--xinyuan-chart-12: #facc15; --xinyuan-chart-12: #61758F;
--xinyuan-chart-13: #60a5fa; --xinyuan-chart-13: #74879A;
--xinyuan-chart-14: #a855f7; --xinyuan-chart-14: #8A6F63;
--xinyuan-chart-15: #06b6d4; --xinyuan-chart-15: #9A8174;
--xinyuan-chart-16: #1e293b; --xinyuan-chart-16: #B5A69D;
--xinyuan-chart-sector-gap: var(--surface); --xinyuan-chart-sector-gap: var(--surface);
--xinyuan-chart-sector-radius: 10px; --xinyuan-chart-sector-radius: 10px;
--xinyuan-chart-grid: #e8e8e8; --xinyuan-chart-grid: #e8e8e8;
...@@ -105,8 +108,11 @@ ...@@ -105,8 +108,11 @@
[data-theme="dark"] { [data-theme="dark"] {
color-scheme: dark; color-scheme: dark;
/* Theme Colors (Dark Mode) */ /* Theme Colors (Dark Mode) */
--accent: oklch(57.14% 0.2156 29.20); /* Keep the primary brand stable across themes. */
--accent: #DA241A;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--accent-hover: #C91F17;
--accent-pressed: #B51B14;
--background: oklch(12.00% 0.0000 29.20); --background: oklch(12.00% 0.0000 29.20);
--border: oklch(28.00% 0.0000 29.20); --border: oklch(28.00% 0.0000 29.20);
--danger: oklch(62% 0.16 27.03); --danger: oklch(62% 0.16 27.03);
...@@ -117,7 +123,7 @@ ...@@ -117,7 +123,7 @@
--field-border: transparent; --field-border: transparent;
--field-foreground: oklch(99.11% 0.0000 29.20); --field-foreground: oklch(99.11% 0.0000 29.20);
--field-placeholder: oklch(70.50% 0.0000 29.20); --field-placeholder: oklch(70.50% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20); --focus: #DA241A;
--foreground: oklch(99.11% 0.0000 29.20); --foreground: oklch(99.11% 0.0000 29.20);
--muted: oklch(70.50% 0.0000 29.20); --muted: oklch(70.50% 0.0000 29.20);
--overlay: oklch(21.03% 0.0000 29.20); --overlay: oklch(21.03% 0.0000 29.20);
...@@ -126,8 +132,8 @@ ...@@ -126,8 +132,8 @@
--segment: oklch(39.64% 0.0000 29.20); --segment: oklch(39.64% 0.0000 29.20);
--segment-foreground: oklch(99.11% 0.0000 29.20); --segment-foreground: oklch(99.11% 0.0000 29.20);
--separator: oklch(25.00% 0.0000 29.20); --separator: oklch(25.00% 0.0000 29.20);
--success: oklch(73.29% 0.1935 123.85); --success: #74879A;
--success-foreground: oklch(21.03% 0.0059 123.85); --success-foreground: #ffffff;
--surface: oklch(21.03% 0.0000 29.20); --surface: oklch(21.03% 0.0000 29.20);
--surface-foreground: oklch(99.11% 0.0000 29.20); --surface-foreground: oklch(99.11% 0.0000 29.20);
--surface-secondary: oklch(25.70% 0.0000 29.20); --surface-secondary: oklch(25.70% 0.0000 29.20);
......
...@@ -4,10 +4,26 @@ ...@@ -4,10 +4,26 @@
[data-theme='light'], [data-theme='light'],
[data-theme='default'] { [data-theme='default'] {
/* Xinyuan aliases and extension tokens. HeroUI core tokens are owned by theme.css. */ /* Xinyuan aliases and extension tokens. HeroUI core tokens are owned by theme.css. */
--rc-brand-primary: var(--accent); --rc-brand-primary: #DA241A;
--rc-brand-light: color-mix(in oklab, var(--accent) 84%, white); --rc-brand-light: color-mix(in oklab, var(--accent) 84%, white);
--rc-brand-dark: color-mix(in oklab, var(--accent) 82%, black); --rc-brand-dark: color-mix(in oklab, var(--accent) 82%, black);
/* Compatibility aliases for integrations that still read numbered primary
tokens. HeroUI v3 components consume --accent and --accent-hover. */
--primary: #DA241A;
--primary-foreground: #ffffff;
--primary-50: #FFF1F0;
--primary-100: #FFE0DD;
--primary-200: #FFC2BD;
--primary-300: #FFA39C;
--primary-400: #F26D63;
--primary-500: #DA241A;
--primary-600: #C91F17;
--primary-700: #B51B14;
--primary-800: #8F1711;
--primary-900: #6F120E;
--primary-950: #4B0C09;
--rc-bg-canvas: var(--background); --rc-bg-canvas: var(--background);
--rc-bg-surface: var(--surface); --rc-bg-surface: var(--surface);
--rc-border-default: var(--border); --rc-border-default: var(--border);
...@@ -92,14 +108,14 @@ ...@@ -92,14 +108,14 @@
0 4px 12px rgb(15 23 42 / 2%); 0 4px 12px rgb(15 23 42 / 2%);
/* KPI icon surfaces. Card bodies remain neutral; only the icon well carries tone. */ /* KPI icon surfaces. Card bodies remain neutral; only the icon well carries tone. */
--rc-metric-tone-info: oklch(55% 0.14 255); --rc-metric-tone-info: var(--rc-chart-11);
--rc-metric-tone-success: oklch(54% 0.13 145); --rc-metric-tone-success: var(--rc-chart-12);
--rc-metric-tone-teal: oklch(55% 0.1 205); --rc-metric-tone-teal: var(--rc-chart-14);
--rc-metric-tone-danger: oklch(52% 0.17 25); --rc-metric-tone-danger: var(--rc-chart-1);
--rc-metric-tone-warning: oklch(62% 0.12 75); --rc-metric-tone-warning: var(--rc-chart-6);
--rc-metric-tone-violet: oklch(56% 0.12 300); --rc-metric-tone-violet: var(--rc-chart-9);
--rc-metric-tone-indigo: oklch(53% 0.13 275); --rc-metric-tone-indigo: var(--rc-chart-13);
--rc-metric-tone-coral: oklch(60% 0.12 48); --rc-metric-tone-coral: var(--rc-chart-3);
--rc-metric-tone-neutral: var(--muted); --rc-metric-tone-neutral: var(--muted);
--rc-metric-tone-info-soft: color-mix( --rc-metric-tone-info-soft: color-mix(
in oklab, in oklab,
...@@ -167,8 +183,8 @@ ...@@ -167,8 +183,8 @@
--rc-chart-primary: var(--rc-chart-1); --rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: var(--rc-chart-1); --rc-chart-risk: var(--rc-chart-1);
--rc-chart-warning: var(--rc-chart-3); --rc-chart-warning: var(--rc-chart-3);
--rc-chart-positive: var(--rc-chart-7); --rc-chart-positive: var(--rc-chart-12);
--rc-chart-comparison: var(--rc-chart-5); --rc-chart-comparison: var(--rc-chart-11);
--rc-chart-neutral: var(--rc-chart-8); --rc-chart-neutral: var(--rc-chart-8);
--rc-chart-legend-font-size: var(--xinyuan-chart-legend-font-size); --rc-chart-legend-font-size: var(--xinyuan-chart-legend-font-size);
--rc-chart-legend-line-height: var(--xinyuan-chart-legend-line-height); --rc-chart-legend-line-height: var(--xinyuan-chart-legend-line-height);
......
import { useMemo } from 'react'; import { useMemo } from 'react';
import { getXinyuanChartColors } from './chartTokens';
function readToken(name, fallback) { function readToken(name, fallback) {
if (typeof window === 'undefined') return fallback; if (typeof window === 'undefined') return fallback;
...@@ -12,7 +13,7 @@ export default function useXinyuanChartTheme(theme = 'light') { ...@@ -12,7 +13,7 @@ export default function useXinyuanChartTheme(theme = 'light') {
const muted = readToken('--rc-text-muted', theme === 'dark' ? '#a1a1aa' : '#718096'); const muted = readToken('--rc-text-muted', theme === 'dark' ? '#a1a1aa' : '#718096');
const border = readToken('--rc-border-default', theme === 'dark' ? '#303036' : '#e3e8ef'); const border = readToken('--rc-border-default', theme === 'dark' ? '#303036' : '#e3e8ef');
return { return {
colors: ['#9e1b1b', '#16845b', '#b46a00', '#0f766e', '#718096'], colors: getXinyuanChartColors(),
axisLine: { lineStyle: { color: border } }, axisLine: { lineStyle: { color: border } },
axisLabel: { color: muted, fontSize: 12 }, axisLabel: { color: muted, fontSize: 12 },
splitLine: { lineStyle: { color: border, opacity: 0.7 } }, splitLine: { lineStyle: { color: border, opacity: 0.7 } },
......
...@@ -325,6 +325,9 @@ export default function SubjectWorkbench() { ...@@ -325,6 +325,9 @@ export default function SubjectWorkbench() {
setFilters({}); setFilters({});
setPage(0); setPage(0);
}} }}
onQuery={() => {
setRefreshKey((value) => value + 1);
}}
title={subjectConfig.title || subject?.title || subjectName} title={subjectConfig.title || subject?.title || subjectName}
listTitle={subjectConfig.listTitle} listTitle={subjectConfig.listTitle}
description={ description={
......
...@@ -37,7 +37,7 @@ import { saveAs } from 'file-saver'; ...@@ -37,7 +37,7 @@ import { saveAs } from 'file-saver';
import JSZip from 'jszip'; import JSZip from 'jszip';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
export const DEFAULT_PAGE_SIZE = 10; export const DEFAULT_PAGE_SIZE = 20;
function normalizeFilterFieldPath(field) { function normalizeFilterFieldPath(field) {
const value = typeof field === 'string' ? field.trim() : ''; const value = typeof field === 'string' ? field.trim() : '';
......
...@@ -20,8 +20,9 @@ import clsx from 'clsx'; ...@@ -20,8 +20,9 @@ import clsx from 'clsx';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { loadProblemRectificationDashboardData } from '../problemRectificationDashboardService'; import { loadProblemRectificationDashboardData } from '../problemRectificationDashboardService';
import { XINYUAN_CHART_COLORS } from '@/design-system/chartTokens';
const CHART_COLOR_SET = ['#e11d48', '#fb7185', '#f97316', '#f59e0b', '#22c55e', '#0ea5e9', '#8b5cf6']; const CHART_COLOR_SET = XINYUAN_CHART_COLORS;
const CATEGORY_LEVEL_TABS = [ const CATEGORY_LEVEL_TABS = [
{ key: 'level1', label: '一级分类' }, { key: 'level1', label: '一级分类' },
{ key: 'level2', label: '二级分类' }, { key: 'level2', label: '二级分类' },
...@@ -34,24 +35,24 @@ const FOCUS_ISSUE_TYPES = [ ...@@ -34,24 +35,24 @@ const FOCUS_ISSUE_TYPES = [
label: '经济损失问题', label: '经济损失问题',
shortLabel: '经济损失', shortLabel: '经济损失',
description: '造成经济损失且严重程度较高的问题。', description: '造成经济损失且严重程度较高的问题。',
color: '#e11d48', color: CHART_COLOR_SET[0],
badgeClass: 'bg-rose-50 text-rose-700', badgeClass: 'bg-[color-mix(in_oklab,var(--rc-chart-1)_12%,var(--surface))] text-[var(--rc-chart-1)]',
}, },
{ {
key: 'externalImpact', key: 'externalImpact',
label: '外部影响问题', label: '外部影响问题',
shortLabel: '外部影响', shortLabel: '外部影响',
description: '涉及处罚机构或外部影响的问题。', description: '涉及处罚机构或外部影响的问题。',
color: '#f59e0b', color: CHART_COLOR_SET[5],
badgeClass: 'bg-amber-50 text-amber-700', badgeClass: 'bg-[color-mix(in_oklab,var(--rc-chart-6)_14%,var(--surface))] text-[var(--rc-chart-6)]',
}, },
{ {
key: 'accountability', key: 'accountability',
label: '员工问责问题', label: '员工问责问题',
shortLabel: '员工问责', shortLabel: '员工问责',
description: '已触发员工问责后果的问题。', description: '已触发员工问责后果的问题。',
color: '#0ea5e9', color: CHART_COLOR_SET[10],
badgeClass: 'bg-sky-50 text-sky-700', badgeClass: 'bg-[color-mix(in_oklab,var(--rc-chart-11)_12%,var(--surface))] text-[var(--rc-chart-11)]',
}, },
]; ];
...@@ -72,25 +73,13 @@ function formatCount(value) { ...@@ -72,25 +73,13 @@ function formatCount(value) {
} }
function toneCardClass(tone) { function toneCardClass(tone) {
if (tone === 'rose') { return 'rc-card-surface rc-dashboard-card';
return 'border-rose-200 bg-[linear-gradient(135deg,rgba(255,241,242,0.95),rgba(255,255,255,0.9))]';
}
if (tone === 'amber') {
return 'border-amber-200 bg-[linear-gradient(135deg,rgba(255,251,235,0.95),rgba(255,255,255,0.92))]';
}
if (tone === 'emerald') {
return 'border-emerald-200 bg-[linear-gradient(135deg,rgba(236,253,245,0.95),rgba(255,255,255,0.92))]';
}
if (tone === 'sky') {
return 'border-sky-200 bg-[linear-gradient(135deg,rgba(240,249,255,0.95),rgba(255,255,255,0.92))]';
}
return 'border-zinc-200 bg-[linear-gradient(135deg,rgba(250,250,250,0.95),rgba(255,255,255,0.92))]';
} }
function SectionHeading({ eyebrow, title, description }) { function SectionHeading({ eyebrow, title, description }) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<div className="text-xs font-semibold uppercase tracking-[0.24em] text-rose-500"> <div className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--rc-brand-primary)]">
{eyebrow} {eyebrow}
</div> </div>
<div className="text-2xl font-semibold tracking-tight text-slate-950">{title}</div> <div className="text-2xl font-semibold tracking-tight text-slate-950">{title}</div>
...@@ -105,7 +94,7 @@ function StatCard({ label, value, hint, tone = 'default' }) { ...@@ -105,7 +94,7 @@ function StatCard({ label, value, hint, tone = 'default' }) {
return ( return (
<div <div
className={clsx( className={clsx(
'rounded-[28px] border p-5 shadow-[0_16px_40px_rgba(15,23,42,0.06)]', 'p-5',
toneCardClass(tone), toneCardClass(tone),
)} )}
> >
...@@ -233,7 +222,7 @@ function buildDonutOption(data, colors) { ...@@ -233,7 +222,7 @@ function buildDonutOption(data, colors) {
function buildCategoryBarOption(data = []) { function buildCategoryBarOption(data = []) {
return { return {
color: ['#e11d48'], color: [CHART_COLOR_SET[0]],
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { type: 'shadow' }, axisPointer: { type: 'shadow' },
...@@ -478,10 +467,10 @@ function buildBubbleMatrixOption(data = []) { ...@@ -478,10 +467,10 @@ function buildBubbleMatrixOption(data = []) {
itemStyle: { itemStyle: {
color: color:
item.completionRate >= 75 item.completionRate >= 75
? '#22c55e' ? CHART_COLOR_SET[10]
: item.completionRate >= 45 : item.completionRate >= 45
? '#f59e0b' ? CHART_COLOR_SET[5]
: '#e11d48', : CHART_COLOR_SET[0],
opacity: 0.85, opacity: 0.85,
}, },
label: { label: {
...@@ -582,7 +571,7 @@ function buildBubbleMatrixOption(data = []) { ...@@ -582,7 +571,7 @@ function buildBubbleMatrixOption(data = []) {
function buildQualityRatingOption(data) { function buildQualityRatingOption(data) {
return { return {
color: ['#22c55e', '#f59e0b', '#e11d48'], color: [CHART_COLOR_SET[10], CHART_COLOR_SET[5], CHART_COLOR_SET[0]],
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
backgroundColor: 'rgba(15,23,42,0.9)', backgroundColor: 'rgba(15,23,42,0.9)',
...@@ -707,7 +696,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' } ...@@ -707,7 +696,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
[summary.sourceComparison], [summary.sourceComparison],
); );
const overdueRankingOption = useMemo( const overdueRankingOption = useMemo(
() => buildHorizontalBarOption(summary.overdueRanking, '#e11d48'), () => buildHorizontalBarOption(summary.overdueRanking, CHART_COLOR_SET[0]),
[summary.overdueRanking], [summary.overdueRanking],
); );
const departmentMatrixOption = useMemo( const departmentMatrixOption = useMemo(
...@@ -756,7 +745,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' } ...@@ -756,7 +745,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<section className="relative overflow-hidden rounded-[34px] border border-white/80 bg-[linear-gradient(135deg,rgba(124,24,24,0.96),rgba(190,24,93,0.92)_54%,rgba(244,63,94,0.9))] px-6 py-7 text-white shadow-[0_28px_80px_rgba(159,18,57,0.24)]"> <section className="relative overflow-hidden rounded-[34px] border border-white/20 bg-[var(--rc-brand-primary)] px-6 py-7 text-white shadow-[0_28px_80px_rgba(218,36,26,0.24)]">
<div className="absolute inset-y-0 right-0 w-[36%] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_45%)]" /> <div className="absolute inset-y-0 right-0 w-[36%] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_45%)]" />
<div className="absolute -bottom-16 left-1/3 h-40 w-40 rounded-full bg-white/10 blur-3xl" /> <div className="absolute -bottom-16 left-1/3 h-40 w-40 rounded-full bg-white/10 blur-3xl" />
<div className="relative flex flex-wrap items-end justify-between gap-6"> <div className="relative flex flex-wrap items-end justify-between gap-6">
...@@ -791,8 +780,8 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' } ...@@ -791,8 +780,8 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
className={clsx( className={clsx(
'rounded-[20px] px-4 py-3 text-sm font-medium transition-all duration-200', 'rounded-[20px] px-4 py-3 text-sm font-medium transition-all duration-200',
activeGroupKey === group.key activeGroupKey === group.key
? 'bg-[linear-gradient(135deg,#7f1d1d,#be123c)] text-white shadow-lg' ? 'bg-[var(--rc-brand-primary)] text-white shadow-lg'
: 'text-slate-600 hover:bg-rose-50 hover:text-rose-700', : 'text-slate-600 hover:bg-[color-mix(in_oklab,var(--rc-brand-primary)_8%,var(--surface))] hover:text-[var(--rc-brand-primary)]',
)} )}
> >
{group.label} {group.label}
...@@ -864,7 +853,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' } ...@@ -864,7 +853,7 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
label="已完成整改数" label="已完成整改数"
value={formatCount(summary.fixedCount)} value={formatCount(summary.fixedCount)}
hint="整改进度为已整改或已优化" hint="整改进度为已整改或已优化"
tone="emerald" tone="neutral"
/> />
<StatCard <StatCard
label="整改完成率" label="整改完成率"
...@@ -932,8 +921,8 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' } ...@@ -932,8 +921,8 @@ export function ProblemRectificationDashboardPage({ dashboardMode = 'overview' }
className={clsx( className={clsx(
'min-h-11 rounded-full border px-4 text-sm font-medium transition', 'min-h-11 rounded-full border px-4 text-sm font-medium transition',
isActive isActive
? 'border-rose-500 bg-rose-500 text-white shadow-sm' ? 'border-[var(--rc-brand-primary)] bg-[var(--rc-brand-primary)] text-white shadow-sm'
: 'border-zinc-200 bg-white text-slate-600 hover:border-rose-200 hover:bg-rose-50 hover:text-rose-700', : 'border-zinc-200 bg-white text-slate-600 hover:border-[var(--rc-brand-primary)] hover:bg-[color-mix(in_oklab,var(--rc-brand-primary)_8%,var(--surface))] hover:text-[var(--rc-brand-primary)]',
)} )}
onClick={() => setActiveCategoryLevel(tab.key)} onClick={() => setActiveCategoryLevel(tab.key)}
> >
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment