Commit 3334a511 authored by Andy-bubu's avatar Andy-bubu

feat: align xinyuan shell and brand chart theme

parent d3165549
...@@ -7,7 +7,7 @@ Umi route /ai_chat/ ...@@ -7,7 +7,7 @@ Umi route /ai_chat/
-> root theme and CSS imports -> root theme and CSS imports
-> HeroUI Pro AppShell -> HeroUI Pro AppShell
-> Sidebar: identity, new session, conversations, app switcher, user menu -> Sidebar: identity, new session, conversations, app switcher, user menu
-> Navbar: Sidebar.Trigger, route title, page actions, theme action -> Navbar: Sidebar.Trigger, route title, page actions, notification action
-> page canvas: AI workspace -> page canvas: AI workspace
-> conversation/content surface -> conversation/content surface
-> fixed PromptInput composer -> fixed PromptInput composer
...@@ -24,7 +24,7 @@ Umi route /ai_chat/ ...@@ -24,7 +24,7 @@ Umi route /ai_chat/
- 页面/controller 保留业务 state、API 参数、回调、格式转换、请求时机和权限判断。 - 页面/controller 保留业务 state、API 参数、回调、格式转换、请求时机和权限判断。
- HeroUI/HeroUI Pro 组件拥有可见控件、ARIA、焦点、键盘交互、Portal 和视觉状态。 - HeroUI/HeroUI Pro 组件拥有可见控件、ARIA、焦点、键盘交互、Portal 和视觉状态。
- 标准弹窗通过共享 `rc-dialog-panel--{size}` 尺寸梯度约束宽度;内容型配置/导入弹窗使用 `4xl = 1088px`,桌面保留 40px 安全边距,手机保留 16px 安全边距。 - 标准弹窗通过共享 `rc-dialog-panel--{size}` 尺寸梯度约束宽度;内容型配置/导入弹窗使用 `4xl = 1088px`,桌面保留 40px 安全边距,手机保留 16px 安全边距。
- 页面级短命令通过 AppShell 的 Navbar actions 插槽渲染在主题按钮左侧;窄屏使用 HeroUI Dropdown,页面正文不重复同一状态摘要。 - 页面级短命令通过 AppShell 的 Navbar actions 插槽渲染在通知按钮左侧;窄屏使用 HeroUI Dropdown,页面正文不重复同一状态摘要。
- AI 工作区由固定视口高度承载;`ChatConversation` 是唯一纵向滚动区域,PromptInput 作为 flex 尾部固定在底部。PromptInput 使用 HeroUI Pro 官方 `stacked`/`lg` 组合,保持附件、停止生成、发送和自动扩展行为。 - AI 工作区由固定视口高度承载;`ChatConversation` 是唯一纵向滚动区域,PromptInput 作为 flex 尾部固定在底部。PromptInput 使用 HeroUI Pro 官方 `stacked`/`lg` 组合,保持附件、停止生成、发送和自动扩展行为。
- 共享组件只能是业务无关的 HeroUI-native composition,不得复刻 Ant/旧组件 props、事件或 DOM。 - 共享组件只能是业务无关的 HeroUI-native composition,不得复刻 Ant/旧组件 props、事件或 DOM。
- Ant Design Icons 可作为纯图标目录保留;不得通过它包装 UI primitive。 - Ant Design Icons 可作为纯图标目录保留;不得通过它包装 UI primitive。
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
- [x] 引入权威 `theme.css` 原始副本 - [x] 引入权威 `theme.css` 原始副本
- [x] 引入批准的 `tokens.css` 与 Shell 布局 Tokens - [x] 引入批准的 `tokens.css` 与 Shell 布局 Tokens
- [x] 建立 Tailwind -> HeroUI -> Pro -> theme -> tokens 顺序 - [x] 建立 Tailwind -> HeroUI -> Pro -> theme -> tokens 顺序
- [x] 根级浅/深色与 Portal 主题继承 - [x] 固定浅色内容区、深色 Sidebar 与 Portal 主题继承
- [x] 活动页面 Ant Design/Ant Design X/antd-style 视觉引用归零 - [x] 活动页面 Ant Design/Ant Design X/antd-style 视觉引用归零
- [x] 生产解析固定为 React 19;浏览器无重复 Context/slot/provider 警告 - [x] 生产解析固定为 React 19;浏览器无重复 Context/slot/provider 警告
- [x] Inter 从 Umi 热更新 CSS 移出,改由稳定 HTML link 加载本地 400/500/600 WOFF2 - [x] Inter 从 Umi 热更新 CSS 移出,改由稳定 HTML link 加载本地 400/500/600 WOFF2
...@@ -28,10 +28,10 @@ ...@@ -28,10 +28,10 @@
- [x] 账号、密码、验证码 40px 等高与 filled/autofill 状态 - [x] 账号、密码、验证码 40px 等高与 filled/autofill 状态
- [x] HeroUI Pro Sidebar 240px 与真 offcanvas - [x] HeroUI Pro Sidebar 240px 与真 offcanvas
- [x] HeroUI Pro Navbar、Sidebar.Trigger 与 48/32/16px 对称边距 - [x] HeroUI Pro Navbar、Sidebar.Trigger 与 48/32/16px 对称边距
- [x] 主题切换及持久化 - [x] 删除主题切换入口,固定浅色内容区
- [x] Sidebar 用户 Dropdown 与真实退出登录 - [x] Sidebar 用户 Dropdown 与真实退出登录
- [x] 应用切换 HeroUI Modal、两列应用 Card、36px identity well,以及 `site.group` 动态分组、真实数量和“全部应用/其他应用”回退 - [x] 应用切换 HeroUI Modal、两列应用 Card、36px identity well,以及 `site.group` 动态分组、真实数量和“全部应用/其他应用”回退
- [x] 移动端 Sidebar.Mobile - [x] 移动端 Pro Sheet Sidebar
## AI 与业务模块 ## AI 与业务模块
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
- [x] 主对话列表、PromptInput、附件和发送/停止状态 - [x] 主对话列表、PromptInput、附件和发送/停止状态
- [x] 消息区独立滚动、PromptInput 底部固定,并提升为 HeroUI Pro stacked/lg 高度布局 - [x] 消息区独立滚动、PromptInput 底部固定,并提升为 HeroUI Pro stacked/lg 高度布局
- [x] 空会话使用 HeroUI Pro PromptSuggestion 原生分组与 Card 网格;单项满行、双项两列、移动单列,PromptInput 使用 compact 布局 - [x] 空会话使用 HeroUI Pro PromptSuggestion 原生分组与 Card 网格;单项满行、双项两列、移动单列,PromptInput 使用 compact 布局
- [x] 审计知识库操作迁入 Navbar 主题按钮左侧,移动端使用 HeroUI Dropdown;删除 PromptInput 上方重复的文件与数量摘要 - [x] 审计知识库操作迁入 Navbar 通知按钮左侧,移动端使用 HeroUI Dropdown;删除 PromptInput 上方重复的文件与数量摘要
- [~] 审计知识库状态、配置、导入、结果与导出:DropZone/Modal 已验;无授权 Excel,DataGrid 真实行待复验 - [~] 审计知识库状态、配置、导入、结果与导出:DropZone/Modal 已验;无授权 Excel,DataGrid 真实行待复验
- [x] 发票助手上传、步骤、确认和下载的组件边界迁移 - [x] 发票助手上传、步骤、确认和下载的组件边界迁移
- [x] 所有 Modal/AlertDialog/Dropdown/Tooltip/DropZone 使用 HeroUI 根 - [x] 所有 Modal/AlertDialog/Dropdown/Tooltip/DropZone 使用 HeroUI 根
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
- [x] 确认应用为“仅消息通知”范围;功能 1、2、3、5 不适用且未实施 - [x] 确认应用为“仅消息通知”范围;功能 1、2、3、5 不适用且未实施
- [x] 审计 `current_user`、`applications[]`、basePath、Shell 数据流、通知接口和现有回调 - [x] 审计 `current_user`、`applications[]`、basePath、Shell 数据流、通知接口和现有回调
- [x] 新增独立 `/notifications` 路由,保留 `/ai_chat/` base - [x] 新增独立 `/notifications` 路由,保留 `/ai_chat/` base
- [x] Navbar 使用 HeroUI Badge.Anchor、Badge、Button、Tooltip;36px Bell 位于主题按钮左侧 - [x] Navbar 使用 HeroUI Badge.Anchor、Badge、Button、Tooltip;36px Bell 位于右侧
- [x] Sidebar 无通知入口,未读 0/1/99/100 显示规则通过 - [x] Sidebar 无通知入口,未读 0/1/99/100 显示规则通过
- [x] 消息/待办、全部/未读、搜索、防抖、分页、单条/全部已读通过 - [x] 消息/待办、全部/未读、搜索、防抖、分页、单条/全部已读通过
- [x] 动态 action 路径及 `{notice_id}` 请求体通过 - [x] 动态 action 路径及 `{notice_id}` 请求体通过
...@@ -93,3 +93,11 @@ ...@@ -93,3 +93,11 @@
- [x] 与“问题整改”同视口对照通过;目标通知页控制台 0 error / 0 warning - [x] 与“问题整改”同视口对照通过;目标通知页控制台 0 error / 0 warning
- [x] 浏览器内存夹具未进入源码、文档或业务数据 - [x] 浏览器内存夹具未进入源码、文档或业务数据
- [x] Andy 真实验证码登录、空通知接口、Sidebar 真实退出通过 - [x] Andy 真实验证码登录、空通知接口、Sidebar 真实退出通过
## 图表配色专项
- [x] 品牌主色固定为 `#DA241A`,primary/accent/focus/hover/pressed 色阶统一
- [x] 建立 `xinyuan-chart-1..16` 与 `rc-chart-1..16` 统一色彩 Token
- [x] 图表色板移除绿色、荧光和彩虹式高冲突组合
- [x] KPI 图标色引用同一 Chart Token,单系列默认品牌红
- [-] 当前 AI 应用没有真实业务图表调用点,未制造测试图表数据
...@@ -38,7 +38,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳 ...@@ -38,7 +38,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳
- Navbar 高 64px,使用 HeroUI Pro `Sidebar.Trigger` 默认图形。 - Navbar 高 64px,使用 HeroUI Pro `Sidebar.Trigger` 默认图形。
- Navbar 与页面内容共享对称 gutter:桌面 48px、平板 32px、手机 16px。 - Navbar 与页面内容共享对称 gutter:桌面 48px、平板 32px、手机 16px。
- Sidebar 用户整行通过 `Dropdown.Trigger` 打开菜单,`退出登录` 调用现有清理流程。 - Sidebar 用户整行通过 `Dropdown.Trigger` 打开菜单,`退出登录` 调用现有清理流程。
- 应用切换为 HeroUI Modal,46rem/42rem 上限、28px 内容内边距,一列手机/两列 `sm` 以上,应用 identity well 为 36px `Surface tertiary + Avatar soft`。分组必须来自 `/api/info` 的 `site.group`,保持配置顺序并显示真实 `{count} 个应用`;无配置使用“全部应用”,未被配置引用的授权应用使用“其他应用”。 - 应用切换为 HeroUI Modal,46rem/42rem 上限、28px 内容内边距,一列手机/两列 `sm` 以上,应用 identity well 为 36px `Surface default + Avatar soft`。分组必须来自 `/api/info` 的 `site.group`,保持配置顺序并显示真实 `{count} 个应用`;无配置使用“全部应用”,未被配置引用的授权应用使用“其他应用”。
## Surface 与主题 ## Surface 与主题
...@@ -47,7 +47,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳 ...@@ -47,7 +47,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳
- 嵌套层 L2:`surface-secondary`,Surface/Card/Modal/Overlay 内控件使用 `secondary`。 - 嵌套层 L2:`surface-secondary`,Surface/Card/Modal/Overlay 内控件使用 `secondary`。
- 仅真实第三层静态内容使用 L3 `surface-tertiary`。 - 仅真实第三层静态内容使用 L3 `surface-tertiary`。
- Modal、Dropdown、Popover、Tooltip 等浮层使用独立 `overlay`,不当作第四层 Surface。 - Modal、Dropdown、Popover、Tooltip 等浮层使用独立 `overlay`,不当作第四层 Surface。
- 根节点拥有 `.light/.dark` 与 `data-theme`,Portal 必须继承;主题持久化键为 `xinyuan_color_theme`。 - 根节点固定为 `.light` 与 `data-theme="light"`;页面内容保持浅色,Sidebar 单独消费固定深色 Token,Portal 继承浅色内容主题。
## AI 工作区 ## AI 工作区
...@@ -65,7 +65,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳 ...@@ -65,7 +65,7 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳
## 本次落地结果 ## 本次落地结果
- 权威 `theme.css` SHA-256 为 `a0bf6f243aaf2c94cc8b577145d33d765f8ecab863b3a3b4a358bad4d1a05901`,与 Skill 资产逐字节一致。 - 基准 `theme.css` SHA-256 为 `7eedec7934b9da4575a5a1966621b91bd0319159f46a62b8c6804227d4c9936c`,与 `audit_project_manager` 当前实现逐字节一致。
- Umi 的未使用 `antd` 插件已移除,构建产物不再注入 Ant reset;暗色标题计算为 HeroUI 前景色。 - Umi 的未使用 `antd` 插件已移除,构建产物不再注入 Ant reset;暗色标题计算为 HeroUI 前景色。
- 页面、业务模块和共享 JSX 的私有视觉 utility 扫描为零;共享外观仅位于权威主题、批准 Tokens 和共享 Shell 设计系统层。 - 页面、业务模块和共享 JSX 的私有视觉 utility 扫描为零;共享外观仅位于权威主题、批准 Tokens 和共享 Shell 设计系统层。
- Ant Design、Ant Design Pro、Ant Design X、Ant X Markdown、antd-style、Headless UI 和 Catalyst UI Kit 活动引用为零;后续开发必须持续保持为零。 - Ant Design、Ant Design Pro、Ant Design X、Ant X Markdown、antd-style、Headless UI 和 Catalyst UI Kit 活动引用为零;后续开发必须持续保持为零。
...@@ -74,7 +74,13 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳 ...@@ -74,7 +74,13 @@ Inter 字体文件独立存放于 `public/fonts/`,并由 `.umirc.ts` 注入稳
## 消息通知 ## 消息通知
- `AI 应用` 不在六个完整功能应用白名单内,本次只接入消息通知;不得加入 `shortTitle`、Subject/View 权限或审计材料中心能力。 - `AI 应用` 不在六个完整功能应用白名单内,本次只接入消息通知;不得加入 `shortTitle`、Subject/View 权限或审计材料中心能力。
- Navbar 通知入口直接使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`,36px Bell 位于主题按钮左侧;Sidebar 不增加通知入口。 - Navbar 通知入口直接使用 HeroUI `Badge.Anchor + Badge + Button + Tooltip`,36px Bell 位于右侧;Sidebar 不增加通知入口。
- `/notifications` 使用 HeroUI Tabs、SearchField、Card、Chip、Button、Toast 和 HeroUI Pro EmptyState;通知分页使用 HeroUI compound Pagination。 - `/notifications` 使用 HeroUI Tabs、SearchField、Card、Chip、Button、Toast 和 HeroUI Pro EmptyState;通知分页使用 HeroUI compound Pagination。
- 徽标在未读数为 0 时隐藏,1–99 显示真实值,超过 99 显示 `99+`;按钮可访问名称始终保留真实未读数。 - 徽标在未读数为 0 时隐藏,1–99 显示真实值,超过 99 显示 `99+`;按钮可访问名称始终保留真实未读数。
- 通知正文只作为 React 文本渲染,不执行 HTML。站内链接只去除自身 `/ai_chat` base,其他应用路径与外部 HTTP(S) 链接均新窗口打开。 - 通知正文只作为 React 文本渲染,不执行 HTML。站内链接只去除自身 `/ai_chat` base,其他应用路径与外部 HTTP(S) 链接均新窗口打开。
## 图表与品牌色
- 品牌主色固定为 `#DA241A`,HeroUI `--primary`、`--accent`、`--focus` 以及 hover/pressed 状态均使用鑫元红阶。
- 数据可视化统一消费 `--xinyuan-chart-1..16` 与 `--rc-chart-1..16`,按品牌红深浅、暖色、中性色、灰蓝顺序排列,不使用绿色或荧光色。
- KPI/数据卡片图标色映射到同一 `rc-chart-*` Token,单系列默认使用品牌红。
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
## 审计基线 ## 审计基线
- 审计日期:2026-08-21 - 审计日期:2026-08-21
- 迁移分支:`codex/ui-ux-xinyuan-migration` - 迁移分支:`codex/notification-center`
- 视觉与交互唯一基准:已确认的“问题整改”应用 - 视觉与交互唯一基准:`/Users/liuchenlong/Desktop/job/audit_project_manager` 当前实现
- 技术栈:Umi 4、React、JavaScript/JSX、pnpm - 技术栈:Umi 4、React、JavaScript/JSX、pnpm
- 应用 base/publicPath:`/ai_chat/` - 应用 base/publicPath:`/ai_chat/`
- 未修改 UI 启动地址:`http://localhost:8001/ai_chat/` - 未修改 UI 启动地址:`http://localhost:8001/ai_chat/`
...@@ -36,14 +36,14 @@ ...@@ -36,14 +36,14 @@
- 登录:目标与基准桌面均为约 416px Card;36px Logo、40px 字段、24px 字段节奏、验证码与 filled 状态通过。手机均为 326px Card。 - 登录:目标与基准桌面均为约 416px Card;36px Logo、40px 字段、24px 字段节奏、验证码与 filled 状态通过。手机均为 326px Card。
- 退出:真实点击用户 Dropdown 的“退出登录”后,`current_user` 为 `null` 并回到登录界面。 - 退出:真实点击用户 Dropdown 的“退出登录”后,`current_user` 为 `null` 并回到登录界面。
- 主题:切换后 `xinyuan_color_theme=dark`,刷新后仍为 dark;Portal 弹窗计算为暗色主题背景与前景。 - 主题:页面内容固定浅色,Sidebar 使用固定深色 Token;Portal 弹窗继承浅色内容主题。
- Sidebar:桌面折叠后内容可见宽度为 0,主区域回到 x=0;移动打开宽 312px,Escape 关闭。 - Sidebar:桌面折叠后内容可见宽度为 0,主区域回到 x=0;移动打开宽 312px,Escape 关闭。
- 应用切换:实际 `/api/application/info` 返回 3 个可访问应用;弹窗并行读取 `/api/info` 的 `site.group`,按后端分组与组内顺序展示真实数量,未配置分组回退“全部应用”,未被分组引用的授权应用回退“其他应用”;桌面 736px、两列、仅 1 个当前状态;手机 358px、单列;Escape 关闭。 - 应用切换:实际 `/api/application/info` 返回 3 个可访问应用;弹窗并行读取 `/api/info` 的 `site.group`,按后端分组与组内顺序展示真实数量,未配置分组回退“全部应用”,未被分组引用的授权应用回退“其他应用”;桌面 736px、两列、仅 1 个当前状态;手机 358px、单列;Escape 关闭。
- 应用切换专项复验:与 `model_analysis` 在 1280×720 同屏数据下均为 736×484、28px inset、24px radius,Card 内容均为 38px;1440×900、1280×720、768×1024、390×844 的浅/深色共 8 组均无横向溢出,`sm` 起严格两列、手机一列,弹窗自身控制台 0 error / 0 warning。长列表保持 Header 固定且仅 Body 滚动;空分组过滤、站点配置失败、全部应用和其他应用回退均通过内存 fixture 验证,fixture 未写入源码或业务数据。 - 应用切换专项复验:与 `model_analysis` 在 1280×720 同屏数据下均为 736×484、28px inset、24px radius,Card 内容均为 38px;1440×900、1280×720、768×1024、390×844 的浅/深色共 8 组均无横向溢出,`sm` 起严格两列、手机一列,弹窗自身控制台 0 error / 0 warning。长列表保持 Header 固定且仅 Body 滚动;空分组过滤、站点配置失败、全部应用和其他应用回退均通过内存 fixture 验证,fixture 未写入源码或业务数据。
- 弹窗:审计提示词和 Excel 配置均使用共享 `4xl` HeroUI Modal 尺寸,最大宽度 1088px;手机宽度为视口减 32px,Footer 操作纵向满宽排列。DropZone 打开态、浅/深色和 Escape 已验证。 - 弹窗:审计提示词和 Excel 配置均使用共享 `4xl` HeroUI Modal 尺寸,最大宽度 1088px;手机宽度为视口减 32px,Footer 操作纵向满宽排列。DropZone 打开态、浅/深色和 Escape 已验证。
- Modal 尺寸实测:目标在 1440×900 为 `x=176 / width=1088`,在 3072×1200 仍为 `width=1088`,在 390×844 为 `x=16 / width=358`;“问题整改”基准在 1280×720 为 `width=1088`,在 390×844 同为 `x=16 / width=358`。目标弹窗 `scrollWidth === clientWidth`,页面无横向溢出,控制台 0 error / 0 warning。 - Modal 尺寸实测:目标在 1440×900 为 `x=176 / width=1088`,在 3072×1200 仍为 `width=1088`,在 390×844 为 `x=16 / width=358`;“问题整改”基准在 1280×720 为 `width=1088`,在 390×844 同为 `x=16 / width=358`。目标弹窗 `scrollWidth === clientWidth`,页面无横向溢出,控制台 0 error / 0 warning。
- 控制台:生产验收会话为 0 error / 0 warning;八种主题/视口组合均无页面横向溢出。 - 控制台:生产验收会话为 0 error / 0 warning;八种主题/视口组合均无页面横向溢出。
- 审计知识库操作:`回答规则`、`重新加载`、`更换 Excel` 位于 Navbar 主题按钮左侧;无文件时按业务条件显示 `配置 Excel`。移动端收敛为原生 HeroUI Dropdown。输入区不再重复展示知识库名称、文件名和有效风险点数量。 - 审计知识库操作:`回答规则`、`重新加载`、`更换 Excel` 位于 Navbar 通知按钮左侧;无文件时按业务条件显示 `配置 Excel`。移动端收敛为原生 HeroUI Dropdown。输入区不再重复展示知识库名称、文件名和有效风险点数量。
- 聊天工作区:消息区设置为唯一纵向滚动容器,PromptInput 固定在工作区底部;长消息滚动前后输入框坐标保持 `y=560 / bottom=688`(1280×720)不变。PromptInput 使用官方 stacked/lg 布局,实际高度 128px、正文输入区 72px。 - 聊天工作区:消息区设置为唯一纵向滚动容器,PromptInput 固定在工作区底部;长消息滚动前后输入框坐标保持 `y=560 / bottom=688`(1280×720)不变。PromptInput 使用官方 stacked/lg 布局,实际高度 128px、正文输入区 72px。
## Inter 字体 HMR 稳定性 ## Inter 字体 HMR 稳定性
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
| 文件/区域 | 当前 UI 所有者 | 主要问题 | 迁移目标 | | 文件/区域 | 当前 UI 所有者 | 主要问题 | 迁移目标 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `src/layouts/index.jsx` | Ant ConfigProvider + antd-style | 深色蓝青全局主题、渐变、私有圆角和阴影 | HeroUI CSS 顺序、权威 theme、鑫元 Tokens、根级浅/深色 | | `src/layouts/index.jsx` | Ant ConfigProvider + antd-style | 深色蓝青全局主题、渐变、私有圆角和阴影 | HeroUI CSS 顺序、权威 theme、鑫元 Tokens、固定浅色内容区与深色 Sidebar |
| `src/pages/Home/index.jsx` | Ant Button/Drawer/Dropdown/Modal/Tooltip + Ant X Conversations | 316px 浮动玻璃 Sidebar、icon rail 折叠、无基准 Navbar、页面私有状态 | HeroUI Pro Sidebar/Navbar 真 offcanvas、用户菜单、应用切换、对称 gutter | | `src/pages/Home/index.jsx` | Ant Button/Drawer/Dropdown/Modal/Tooltip + Ant X Conversations | 316px 浮动玻璃 Sidebar、icon rail 折叠、无基准 Navbar、页面私有状态 | HeroUI Pro Sidebar/Navbar 真 offcanvas、用户菜单、应用切换、对称 gutter |
| `src/components/LoginModal` | ProForm + Ant Modal | 920px 营销分栏、私有渐变、字段 48px、非基准交互 | 与“问题整改”一致的居中 HeroUI Card 登录页 | | `src/components/LoginModal` | ProForm + Ant Modal | 920px 营销分栏、私有渐变、字段 48px、非基准交互 | 与“问题整改”一致的居中 HeroUI Card 登录页 |
| `src/components/ApplicationSwitcherDialog` | Ant Modal/Alert/Empty/Skeleton | 统计、分组、三列倾向、装饰内容和私有外观 | HeroUI Modal、固定 46rem/42rem、两列应用 Card、单一当前状态 | | `src/components/ApplicationSwitcherDialog` | Ant Modal/Alert/Empty/Skeleton | 统计、分组、三列倾向、装饰内容和私有外观 | HeroUI Modal、固定 46rem/42rem、两列应用 Card、单一当前状态 |
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
- 登录 Modal、配置 Modal、Excel 导入 Modal、删除确认 Modal 和应用切换 Modal 均为 Ant Portal,必须迁移到 HeroUI 根并继承根主题。 - 登录 Modal、配置 Modal、Excel 导入 Modal、删除确认 Modal 和应用切换 Modal 均为 Ant Portal,必须迁移到 HeroUI 根并继承根主题。
- 用户菜单和会话操作菜单当前为 Ant Dropdown,必须迁移到 HeroUI Dropdown compound API。 - 用户菜单和会话操作菜单当前为 Ant Dropdown,必须迁移到 HeroUI Dropdown compound API。
- 移动侧栏当前为 Ant Drawer,必须由 HeroUI Pro Sidebar.Mobile 接管。 - 移动侧栏使用 HeroUI Pro `Sheet` + `Sidebar` 内容,桌面保持真正 offcanvas。
- 文件上传当前包含 Ant Upload/Attachments;活动页面不得保留可见原生 file 控件,最终只允许 DropZone 内部隐藏 input。 - 文件上传当前包含 Ant Upload/Attachments;活动页面不得保留可见原生 file 控件,最终只允许 DropZone 内部隐藏 input。
- 审计结果包含 Ant Table;若业务数据表仍活动,必须迁移到 HeroUI Pro DataGrid 原生选择/排序/固定列/EmptyState/Pagination 契约。 - 审计结果包含 Ant Table;若业务数据表仍活动,必须迁移到 HeroUI Pro DataGrid 原生选择/排序/固定列/EmptyState/Pagination 契约。
- 当前活动范围未发现 KPI 或 PieChart/BarChart/ComposedChart 业务数据。禁止为了验收伪造 KPI/图表;若后续配置启用真实数据,应直接使用 HeroUI Pro compound API。 - 当前活动范围未发现 KPI 或 PieChart/BarChart/ComposedChart 业务数据。禁止为了验收伪造 KPI/图表;若后续配置启用真实数据,应直接使用 HeroUI Pro compound API。
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
- 路由 base/publicPath 为 `/ai_chat/`;新增独立应用内路由 `/notifications`,浏览器地址为 `/ai_chat/notifications`。 - 路由 base/publicPath 为 `/ai_chat/`;新增独立应用内路由 `/notifications`,浏览器地址为 `/ai_chat/notifications`。
- 登录状态沿用 `localStorage.current_user`,Navbar 与通知页不读取、复制或合并其他应用的 `user.applications[]` 权限。 - 登录状态沿用 `localStorage.current_user`,Navbar 与通知页不读取、复制或合并其他应用的 `user.applications[]` 权限。
- 原 Shell 的页面操作由 `useNavbarActions` 注入;通知计数提升到 `AppShell`,登录用户变化、通知状态事件及 60 秒定时器触发刷新。 - 原 Shell 的页面操作由 `useNavbarActions` 注入;通知计数提升到 `AppShell`,登录用户变化、通知状态事件及 60 秒定时器触发刷新。
- Sidebar 保留原会话和用户数据流,不增加通知入口。Navbar 的通知入口位于页面操作与主题按钮之间。 - Sidebar 保留原会话和用户数据流,不增加通知入口。Navbar 的通知入口位于页面操作右侧。
- 通知接口沿用平台协议:`POST /notice/customer/query`、`PUT /notice/customer/read`、`GET /notice/customer/count`、`PUT /notice/customer/{action}/action/{method}`;目标项目请求层继续统一添加 `/api` 前缀。 - 通知接口沿用平台协议:`POST /notice/customer/query`、`PUT /notice/customer/read`、`GET /notice/customer/count`、`PUT /notice/customer/{action}/action/{method}`;目标项目请求层继续统一添加 `/api` 前缀。
- 查询固定每页 10 条,搜索 300ms 防抖并转义正则,未读筛选发送 `status: "unread"`;动态 action 只提交 `{notice_id}`。 - 查询固定每页 10 条,搜索 300ms 防抖并转义正则,未读筛选发送 `status: "unread"`;动态 action 只提交 `{notice_id}`。
- 现有目标项目无 Subject/View、record-workbench、RecordDetailDrawer、材料中心文件字段或通知模块;未新增相关能力,也未改变登录、会话和 AI 业务回调。 - 现有目标项目无 Subject/View、record-workbench、RecordDetailDrawer、材料中心文件字段或通知模块;未新增相关能力,也未改变登录、会话和 AI 业务回调。
......
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
"@umijs/max": "^4.4.12", "@umijs/max": "^4.4.12",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"embla-carousel": "^8.6.0", "embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
......
...@@ -70,6 +70,12 @@ importers: ...@@ -70,6 +70,12 @@ importers:
dayjs: dayjs:
specifier: ^1.11.18 specifier: ^1.11.18
version: 1.11.21 version: 1.11.21
echarts:
specifier: ^6.1.0
version: 6.1.0
echarts-for-react:
specifier: ^3.0.6
version: 3.0.6(echarts@6.1.0)(react@19.2.8)
embla-carousel: embla-carousel:
specifier: ^8.6.0 specifier: ^8.6.0
version: 8.6.0 version: 8.6.0
...@@ -4064,6 +4070,15 @@ packages: ...@@ -4064,6 +4070,15 @@ packages:
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
echarts-for-react@3.0.6:
resolution: {integrity: sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==}
peerDependencies:
echarts: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
react: ^15.0.0 || >=16.0.0
echarts@6.1.0:
resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==}
ee-first@1.1.1: ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
...@@ -7840,6 +7855,9 @@ packages: ...@@ -7840,6 +7855,9 @@ packages:
sisteransi@1.0.5: sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
size-sensor@1.0.3:
resolution: {integrity: sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==}
slash@3.0.0: slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'} engines: {node: '>=8'}
...@@ -8304,6 +8322,9 @@ packages: ...@@ -8304,6 +8322,9 @@ packages:
tslib@1.14.1: tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
tslib@2.4.0: tslib@2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
...@@ -8739,6 +8760,9 @@ packages: ...@@ -8739,6 +8760,9 @@ packages:
zod@4.4.3: zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
zrender@6.1.0:
resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==}
zwitch@2.0.4: zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
...@@ -13465,6 +13489,18 @@ snapshots: ...@@ -13465,6 +13489,18 @@ snapshots:
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
echarts-for-react@3.0.6(echarts@6.1.0)(react@19.2.8):
dependencies:
echarts: 6.1.0
fast-deep-equal: 3.1.3
react: 19.2.8
size-sensor: 1.0.3
echarts@6.1.0:
dependencies:
tslib: 2.3.0
zrender: 6.1.0
ee-first@1.1.1: {} ee-first@1.1.1: {}
electron-to-chromium@1.5.401: {} electron-to-chromium@1.5.401: {}
...@@ -18135,6 +18171,8 @@ snapshots: ...@@ -18135,6 +18171,8 @@ snapshots:
sisteransi@1.0.5: {} sisteransi@1.0.5: {}
size-sensor@1.0.3: {}
slash@3.0.0: {} slash@3.0.0: {}
slash@4.0.0: {} slash@4.0.0: {}
...@@ -18687,6 +18725,8 @@ snapshots: ...@@ -18687,6 +18725,8 @@ snapshots:
tslib@1.14.1: {} tslib@1.14.1: {}
tslib@2.3.0: {}
tslib@2.4.0: {} tslib@2.4.0: {}
tslib@2.8.1: {} tslib@2.8.1: {}
...@@ -19210,4 +19250,8 @@ snapshots: ...@@ -19210,4 +19250,8 @@ snapshots:
zod@4.4.3: {} zod@4.4.3: {}
zrender@6.1.0:
dependencies:
tslib: 2.3.0
zwitch@2.0.4: {} zwitch@2.0.4: {}
import '@/tailwind.css'; import '@/tailwind.css';
import { defineApp, matchRoutes } from 'umi'; import { defineApp, matchRoutes } from 'umi';
import { THEME_STORAGE_KEY } from './config/application';
import { createTracking } from './services/DataService'; import { createTracking } from './services/DataService';
import { import {
collectAppInfo, collectAppInfo,
...@@ -9,14 +8,12 @@ import { ...@@ -9,14 +8,12 @@ import {
} from './utils/trackingHelper'; } from './utils/trackingHelper';
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
const storedTheme = window.localStorage?.getItem?.(THEME_STORAGE_KEY);
const activeTheme = storedTheme === 'dark' ? 'dark' : 'light';
const root = document.documentElement; const root = document.documentElement;
root.classList.toggle('dark', activeTheme === 'dark'); root.classList.remove('dark');
root.classList.toggle('light', activeTheme === 'light'); root.classList.add('light');
root.dataset.theme = activeTheme; root.dataset.theme = 'light';
root.style.colorScheme = activeTheme; root.style.colorScheme = 'light';
} }
export const request = { export const request = {
......
...@@ -6,12 +6,10 @@ import DeleteOutlined from '@ant-design/icons/DeleteOutlined'; ...@@ -6,12 +6,10 @@ import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
import EditOutlined from '@ant-design/icons/EditOutlined'; import EditOutlined from '@ant-design/icons/EditOutlined';
import ExportOutlined from '@ant-design/icons/ExportOutlined'; import ExportOutlined from '@ant-design/icons/ExportOutlined';
import LogoutOutlined from '@ant-design/icons/LogoutOutlined'; import LogoutOutlined from '@ant-design/icons/LogoutOutlined';
import MoonOutlined from '@ant-design/icons/MoonOutlined';
import MoreOutlined from '@ant-design/icons/MoreOutlined'; import MoreOutlined from '@ant-design/icons/MoreOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined'; import PlusOutlined from '@ant-design/icons/PlusOutlined';
import PushpinOutlined from '@ant-design/icons/PushpinOutlined'; import PushpinOutlined from '@ant-design/icons/PushpinOutlined';
import SearchOutlined from '@ant-design/icons/SearchOutlined'; import SearchOutlined from '@ant-design/icons/SearchOutlined';
import SunOutlined from '@ant-design/icons/SunOutlined';
import WarningOutlined from '@ant-design/icons/WarningOutlined'; import WarningOutlined from '@ant-design/icons/WarningOutlined';
function withSize(Icon) { function withSize(Icon) {
...@@ -35,12 +33,10 @@ export const Edit = withSize(EditOutlined); ...@@ -35,12 +33,10 @@ export const Edit = withSize(EditOutlined);
export const OpenExternal = withSize(ExportOutlined); export const OpenExternal = withSize(ExportOutlined);
export const ExclamationTriangleIcon = withSize(WarningOutlined); export const ExclamationTriangleIcon = withSize(WarningOutlined);
export const LogOut = withSize(LogoutOutlined); export const LogOut = withSize(LogoutOutlined);
export const Moon = withSize(MoonOutlined);
export const More = withSize(MoreOutlined); export const More = withSize(MoreOutlined);
export const Plus = withSize(PlusOutlined); export const Plus = withSize(PlusOutlined);
export const Pin = withSize(PushpinOutlined); export const Pin = withSize(PushpinOutlined);
export const Search = withSize(SearchOutlined); export const Search = withSize(SearchOutlined);
export const Sun = withSize(SunOutlined);
export function SidebarPanel({ size = 16, style, ...props }) { export function SidebarPanel({ size = 16, style, ...props }) {
return ( return (
......
import { THEME_STORAGE_KEY } from '@/config/application';
import { import {
loadUnreadNotificationCount, loadUnreadNotificationCount,
NOTIFICATION_STATE_EVENT, NOTIFICATION_STATE_EVENT,
...@@ -31,18 +30,6 @@ export function useNavbarActions(actions) { ...@@ -31,18 +30,6 @@ export function useNavbarActions(actions) {
}, [actions, setNavbarActions]); }, [actions, setNavbarActions]);
} }
function getInitialTheme() {
if (typeof window === 'undefined') return 'light';
try {
return window.localStorage.getItem(THEME_STORAGE_KEY) === 'dark'
? 'dark'
: 'light';
} catch {
return 'light';
}
}
export default function AppShell({ export default function AppShell({
children, children,
open, open,
...@@ -50,24 +37,9 @@ export default function AppShell({ ...@@ -50,24 +37,9 @@ export default function AppShell({
title, title,
...sidebarProps ...sidebarProps
}) { }) {
const [theme, setTheme] = useState(getInitialTheme);
const [navbarActions, setNavbarActions] = useState(null); const [navbarActions, setNavbarActions] = useState(null);
const [notificationCount, setNotificationCount] = useState(0); const [notificationCount, setNotificationCount] = useState(0);
useLayoutEffect(() => {
const root = document.documentElement;
root.classList.toggle('dark', theme === 'dark');
root.classList.toggle('light', theme === 'light');
root.dataset.theme = theme;
root.style.colorScheme = theme;
try {
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
} catch {
// The theme still applies for this session when storage is unavailable.
}
}, [theme]);
useEffect(() => { useEffect(() => {
if (!sidebarProps.userInfo) { if (!sidebarProps.userInfo) {
setNotificationCount(0); setNotificationCount(0);
...@@ -101,20 +73,19 @@ export default function AppShell({ ...@@ -101,20 +73,19 @@ export default function AppShell({
return ( return (
<NavbarActionsContext.Provider value={setNavbarActions}> <NavbarActionsContext.Provider value={setNavbarActions}>
<Sidebar.Provider <Sidebar.Provider
className={`template-dashboard-shell rc-app-shell ${theme}`} className="template-dashboard-shell rc-app-shell light"
collapsible="offcanvas" collapsible="offcanvas"
navigate={(href) => history.push(href)}
open={open} open={open}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
> >
<DashboardSidebar theme={theme} {...sidebarProps} /> <DashboardSidebar {...sidebarProps} />
<Sidebar.Main className="app-main-glass rc-app-main"> <Sidebar.Main className="app-main-glass rc-app-main">
<DashboardNavbar <DashboardNavbar
actions={navbarActions} actions={navbarActions}
notificationCount={notificationCount} notificationCount={notificationCount}
theme={theme}
title={title} title={title}
onOpenNotifications={() => history.push('/notifications')} onOpenNotifications={() => history.push('/notifications')}
onThemeChange={setTheme}
/> />
<div className="app-shell-content-scroll min-h-0 flex-1 overflow-y-auto"> <div className="app-shell-content-scroll min-h-0 flex-1 overflow-y-auto">
<div className="rc-app-page">{children}</div> <div className="rc-app-page">{children}</div>
......
import { Navbar } from '@heroui-pro/react/navbar'; import { Navbar } from '@heroui-pro/react/navbar';
import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar'; import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar';
import { Badge, Button, Tooltip } from '@heroui/react'; import { Badge, Button, Tooltip } from '@heroui/react';
import { Bell, Moon, SidebarPanel, Sun } from './AppIcons'; import { Bell, SidebarPanel } from './AppIcons';
export default function DashboardNavbar({ export default function DashboardNavbar({
actions, actions,
notificationCount = 0, notificationCount = 0,
onOpenNotifications, onOpenNotifications,
onThemeChange,
theme,
title, title,
}) { }) {
const { isMobile, isOpen } = useSidebar(); const { isMobile, isOpen } = useSidebar();
const isDark = theme === 'dark';
const themeLabel = isDark ? '切换至浅色模式' : '切换至深色模式';
const normalizedNotificationCount = Math.max( const normalizedNotificationCount = Math.max(
0, 0,
Number(notificationCount) || 0, Number(notificationCount) || 0,
...@@ -61,20 +57,6 @@ export default function DashboardNavbar({ ...@@ -61,20 +57,6 @@ export default function DashboardNavbar({
</Badge.Anchor> </Badge.Anchor>
<Tooltip.Content>通知消息</Tooltip.Content> <Tooltip.Content>通知消息</Tooltip.Content>
</Tooltip> </Tooltip>
<Tooltip delay={0}>
<Button
isIconOnly
aria-label={themeLabel}
aria-pressed={isDark}
className="app-navbar-icon-button rc-navbar-icon-button"
size="sm"
variant="ghost"
onPress={() => onThemeChange(isDark ? 'light' : 'dark')}
>
{isDark ? <Sun size={16} /> : <Moon size={16} />}
</Button>
<Tooltip.Content>{themeLabel}</Tooltip.Content>
</Tooltip>
</div> </div>
</Navbar.Header> </Navbar.Header>
</Navbar> </Navbar>
......
import xyLogoRed from '@/assets/xy-logo-red.png';
import xyLogoWhite from '@/assets/xy-logo-white.png'; import xyLogoWhite from '@/assets/xy-logo-white.png';
import { APPLICATION_DISPLAY_NAME } from '@/config/application'; import { APPLICATION_DISPLAY_NAME } from '@/config/application';
import { Sidebar } from '@heroui-pro/react/sidebar'; import { Sheet } from '@heroui-pro/react';
import { Avatar, Button, Dropdown, Label, Tooltip } from '@heroui/react'; import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar';
import { Avatar, Button, Dropdown, Label } from '@heroui/react';
import { import {
AppGrid, AppGrid,
Comment, Comment,
...@@ -127,16 +127,7 @@ function SidebarContents({ ...@@ -127,16 +127,7 @@ function SidebarContents({
<Sidebar.Header> <Sidebar.Header>
<div className="app-sidebar-brand rc-sidebar-brand"> <div className="app-sidebar-brand rc-sidebar-brand">
<div className="app-sidebar-brand-mark rc-sidebar-brand-mark"> <div className="app-sidebar-brand-mark rc-sidebar-brand-mark">
<img <img alt="鑫元" className="rc-sidebar-logo" src={xyLogoWhite} />
alt="鑫元"
className="rc-sidebar-logo rc-sidebar-logo--light"
src={xyLogoRed}
/>
<img
alt="鑫元"
className="rc-sidebar-logo rc-sidebar-logo--dark"
src={xyLogoWhite}
/>
</div> </div>
<div <div
className="app-sidebar-brand-main rc-sidebar-brand-main" className="app-sidebar-brand-main rc-sidebar-brand-main"
...@@ -150,7 +141,6 @@ function SidebarContents({ ...@@ -150,7 +141,6 @@ function SidebarContents({
className="app-sidebar-app-switcher rc-sidebar-app-switcher" className="app-sidebar-app-switcher rc-sidebar-app-switcher"
data-sidebar="label" data-sidebar="label"
> >
<Tooltip delay={0}>
<Button <Button
isIconOnly isIconOnly
aria-label="切换应用" aria-label="切换应用"
...@@ -161,8 +151,6 @@ function SidebarContents({ ...@@ -161,8 +151,6 @@ function SidebarContents({
> >
<AppGrid size={16} /> <AppGrid size={16} />
</Button> </Button>
<Tooltip.Content>切换应用</Tooltip.Content>
</Tooltip>
</div> </div>
</div> </div>
</Sidebar.Header> </Sidebar.Header>
...@@ -230,17 +218,38 @@ function SidebarContents({ ...@@ -230,17 +218,38 @@ function SidebarContents({
} }
export default function DashboardSidebar(props) { export default function DashboardSidebar(props) {
const mobile = useSidebar();
function AccessibleMobileSidebar() {
if (!mobile.isMobile) return null;
return (
<Sheet.Root
isOpen={mobile.isMobileOpen}
placement={mobile.side}
onOpenChange={mobile.setMobileOpen}
>
<Sheet.Backdrop variant="blur">
<Sheet.Content className="sidebar__mobile-sheet">
<Sheet.Dialog
aria-label={`${APPLICATION_DISPLAY_NAME}导航`}
className="sidebar__mobile-dialog"
>
<div className="sidebar__mobile app-sidebar-glass rc-mobile-sidebar">
<SidebarContents {...props} />
</div>
</Sheet.Dialog>
</Sheet.Content>
</Sheet.Backdrop>
</Sheet.Root>
);
}
return ( return (
<> <>
<Sidebar className="app-sidebar-glass rc-sidebar"> <Sidebar className="app-sidebar-glass rc-sidebar">
<SidebarContents {...props} /> <SidebarContents {...props} />
</Sidebar> </Sidebar>
<Sidebar.Mobile className="app-sidebar-glass rc-mobile-sidebar"> <AccessibleMobileSidebar />
<h2 className="sr-only" slot="title">
{APPLICATION_DISPLAY_NAME}导航
</h2>
<SidebarContents {...props} />
</Sidebar.Mobile>
</> </>
); );
} }
export const APPLICATION_DISPLAY_NAME = 'AI 应用'; export const APPLICATION_DISPLAY_NAME = 'AI 应用';
export const APPLICATION_NAME = 'ai_chat';
export const APPLICATION_BASE_PATH = '/ai_chat/'; export const APPLICATION_BASE_PATH = '/ai_chat/';
export const THEME_STORAGE_KEY = 'xinyuan_color_theme';
export const ROUTE_TITLES = { export const ROUTE_TITLES = {
'/': APPLICATION_DISPLAY_NAME, '/': APPLICATION_DISPLAY_NAME,
......
This diff is collapsed.
...@@ -11,39 +11,57 @@ ...@@ -11,39 +11,57 @@
[data-theme='light'], [data-theme='light'],
[data-theme='default'] { [data-theme='default'] {
/* Theme Colors (Light Mode) */ /* Theme Colors (Light Mode) */
--accent: oklch(45.32% 0.1661 27.03);
/* Xinyuan Fund brand red. HeroUI's accent is the primary action color. */
--accent: #da241a;
--accent-hover: #c91f17;
--accent-pressed: #b51b14;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--background: oklch(97.02% 0.0045 27.03); --primary: #da241a;
--border: oklch(90% 0.0045 27.03); --primary-hover: #c91f17;
--danger: oklch(45.32% 0.1661 27.03); --primary-pressed: #b51b14;
--primary-foreground: #fff;
--primary-50: #fce9e7;
--primary-100: #f9d0cc;
--primary-200: #f3a29a;
--primary-300: #ec756d;
--primary-400: #e44a3f;
--primary-500: #da241a;
--primary-600: #c91f17;
--primary-700: #b51b14;
--primary-800: #941711;
--primary-900: #73120d;
--primary-950: #4a0c08;
--background: oklch(97.02% 0 29.2);
--border: oklch(90% 0 29.2);
--danger: #b51b14;
--danger-hover: #941711;
--danger-foreground: oklch(99.11% 0 0); --danger-foreground: oklch(99.11% 0 0);
--default: oklch(94% 0 29.2);
/* HeroUI control aliases: primary = L1, secondary = L2. */ --default-foreground: oklch(21.03% 0.0059 29.2);
--default: var(--surface-secondary); --field-background: oklch(100% 0 29.2);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--field-border: transparent; --field-border: transparent;
--field-foreground: var(--surface-foreground); --field-foreground: oklch(21.03% 0 29.2);
--field-placeholder: oklch(55.17% 0.009 27.03); --field-placeholder: oklch(55.17% 0 29.2);
--focus: oklch(45.32% 0.1661 27.03); --focus: #da241a;
--foreground: oklch(21.03% 0.0045 27.03); --foreground: oklch(21.03% 0 29.2);
--muted: oklch(55.17% 0.009 27.03); --muted: oklch(55.17% 0 29.2);
--overlay: var(--surface); --overlay: oklch(100% 0 29.2);
--overlay-foreground: var(--surface-foreground); --overlay-foreground: oklch(21.03% 0 29.2);
--scrollbar: oklch(87.1% 0.0045 27.03); --scrollbar: oklch(87.1% 0 29.2);
--segment: oklch(100% 0.0045 27.03); --segment: oklch(100% 0 29.2);
--segment-foreground: oklch(21.03% 0.0045 27.03); --segment-foreground: oklch(21.03% 0 29.2);
--separator: oklch(92% 0.0045 27.03); --separator: oklch(92% 0 29.2);
--success: oklch(73.29% 0.1952 123.59); --success: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.59); --success-foreground: oklch(21.03% 0.0059 123.85);
--surface: oklch(100% 0.0022 27.03); --surface: oklch(100% 0 29.2);
--surface-foreground: oklch(21.03% 0.0045 27.03); --surface-foreground: oklch(21.03% 0 29.2);
--surface-secondary: oklch(95.24% 0.0036 27.03); --surface-secondary: oklch(95.24% 0 29.2);
--surface-secondary-foreground: oklch(21.03% 0.0045 27.03); --surface-secondary-foreground: oklch(21.03% 0 29.2);
--surface-tertiary: oklch(93.73% 0.0036 27.03); --surface-tertiary: oklch(93.73% 0 29.2);
--surface-tertiary-foreground: oklch(21.03% 0.0045 27.03); --surface-tertiary-foreground: oklch(21.03% 0 29.2);
--warning: oklch(78.19% 0.1599 45.11); --warning: oklch(78.19% 0.1585 45.37);
--warning-foreground: oklch(21.03% 0.0059 45.11); --warning-foreground: oklch(21.03% 0.0059 45.37);
/* Border Radius */ /* Border Radius */
--radius: 0.5rem; --radius: 0.5rem;
...@@ -53,6 +71,55 @@ ...@@ -53,6 +71,55 @@
/* Make sure to load Inter font in your app */ /* Make sure to load Inter font in your app */
--font-sans: var(--font-inter); --font-sans: var(--font-inter);
/*
* Xinyuan fixed split-shell contract.
* The application canvas always uses this light theme. The Sidebar is the
* only persistent dark plane and must consume these semantic tokens.
*/
--xinyuan-sidebar-background: oklch(16% 0.02 255);
--xinyuan-sidebar-border: oklch(26% 0.022 255);
--xinyuan-sidebar-foreground: oklch(97% 0.004 255);
--xinyuan-sidebar-muted: oklch(76% 0.018 255);
--xinyuan-sidebar-item-hover: oklch(22% 0.026 255);
--xinyuan-sidebar-item-selected: oklch(28% 0.016 255);
--xinyuan-sidebar-item-selected-foreground: oklch(99% 0.002 255);
--xinyuan-sidebar-focus: oklch(78% 0.105 29.2);
/*
* ECharts is the required chart renderer for Xinyuan applications.
* Ant Design remains the icon source. Resolve these CSS variables to
* concrete colors before assigning them to an ECharts option object.
*/
/* Coordinated financial palette: brand reds -> warm neutrals -> gray blues. */
--xinyuan-chart-1: #da241a;
--xinyuan-chart-2: #b83a32;
--xinyuan-chart-3: #c95a52;
--xinyuan-chart-4: #d47a70;
--xinyuan-chart-5: #a65a45;
--xinyuan-chart-6: #b87958;
--xinyuan-chart-7: #c99772;
--xinyuan-chart-8: #6f6674;
--xinyuan-chart-9: #807783;
--xinyuan-chart-10: #938b96;
--xinyuan-chart-11: #4f6380;
--xinyuan-chart-12: #61758f;
--xinyuan-chart-13: #74879a;
--xinyuan-chart-14: #8a6f63;
--xinyuan-chart-15: #9a8174;
--xinyuan-chart-16: #b0a5ad;
--xinyuan-chart-sector-gap: var(--surface);
--xinyuan-chart-sector-radius: 10px;
--xinyuan-chart-grid: #e8e8e8;
--xinyuan-chart-axis: #737373;
--xinyuan-chart-label: #737373;
--xinyuan-chart-tooltip-background: #fff;
--xinyuan-chart-tooltip-border: #dedede;
--xinyuan-chart-tooltip-foreground: #222;
--xinyuan-chart-tooltip-shadow: 0 16px 40px rgb(15 23 42 / 14%);
--xinyuan-chart-legend-font-size: 0.75rem;
--xinyuan-chart-legend-line-height: 1rem;
} }
.dark, .dark,
...@@ -60,37 +127,53 @@ ...@@ -60,37 +127,53 @@
color-scheme: dark; color-scheme: dark;
/* Theme Colors (Dark Mode) */ /* Theme Colors (Dark Mode) */
--accent: oklch(45.32% 0.1661 27.03); --accent: #da241a;
--accent-hover: #c91f17;
--accent-pressed: #b51b14;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--background: oklch(12% 0.0045 27.03); --primary: #da241a;
--border: oklch(28% 0.0045 27.03); --primary-hover: #c91f17;
--danger: oklch(62% 0.16 27.03); --primary-pressed: #b51b14;
--primary-foreground: #fff;
--primary-50: #fce9e7;
--primary-100: #f9d0cc;
--primary-200: #f3a29a;
--primary-300: #ec756d;
--primary-400: #e44a3f;
--primary-500: #da241a;
--primary-600: #c91f17;
--primary-700: #b51b14;
--primary-800: #941711;
--primary-900: #73120d;
--primary-950: #4a0c08;
--background: oklch(12% 0 29.2);
--border: oklch(28% 0 29.2);
--danger: #b51b14;
--danger-hover: #941711;
--danger-foreground: oklch(99.11% 0 0); --danger-foreground: oklch(99.11% 0 0);
--default: oklch(27.4% 0 29.2);
/* HeroUI control aliases: primary = L1, secondary = L2. */ --default-foreground: oklch(99.11% 0 0);
--default: var(--surface-secondary); --field-background: oklch(21.03% 0 29.2);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--field-border: transparent; --field-border: transparent;
--field-foreground: var(--surface-foreground); --field-foreground: oklch(99.11% 0 29.2);
--field-placeholder: oklch(70.5% 0.009 27.03); --field-placeholder: oklch(70.5% 0 29.2);
--focus: oklch(45.32% 0.1661 27.03); --focus: #da241a;
--foreground: oklch(99.11% 0.0045 27.03); --foreground: oklch(99.11% 0 29.2);
--muted: oklch(70.5% 0.009 27.03); --muted: oklch(70.5% 0 29.2);
--overlay: var(--surface); --overlay: oklch(21.03% 0 29.2);
--overlay-foreground: var(--surface-foreground); --overlay-foreground: oklch(99.11% 0 29.2);
--scrollbar: oklch(70.5% 0.0045 27.03); --scrollbar: oklch(70.5% 0 29.2);
--segment: oklch(39.64% 0.0045 27.03); --segment: oklch(39.64% 0 29.2);
--segment-foreground: oklch(99.11% 0.0045 27.03); --segment-foreground: oklch(99.11% 0 29.2);
--separator: oklch(25% 0.0045 27.03); --separator: oklch(25% 0 29.2);
--success: oklch(73.29% 0.1952 123.59); --success: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.59); --success-foreground: oklch(21.03% 0.0059 123.85);
--surface: oklch(21.03% 0.009 27.03); --surface: oklch(21.03% 0 29.2);
--surface-foreground: oklch(99.11% 0.0045 27.03); --surface-foreground: oklch(99.11% 0 29.2);
--surface-secondary: oklch(25.7% 0.0067 27.03); --surface-secondary: oklch(25.7% 0 29.2);
--surface-secondary-foreground: oklch(99.11% 0.0045 27.03); --surface-secondary-foreground: oklch(99.11% 0 29.2);
--surface-tertiary: oklch(27.21% 0.0067 27.03); --surface-tertiary: oklch(27.21% 0 29.2);
--surface-tertiary-foreground: oklch(99.11% 0.0045 27.03); --surface-tertiary-foreground: oklch(99.11% 0 29.2);
--warning: oklch(82.03% 0.14 49.12); --warning: oklch(82.03% 0.1388 49.38);
--warning-foreground: oklch(21.03% 0.0059 49.12); --warning-foreground: oklch(21.03% 0.0059 49.38);
} }
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
.light, .light,
.default, .default,
[data-theme='light'], [data-theme='light'],
[data-theme='default'], [data-theme='default'] {
.dark,
[data-theme='dark'] {
/* 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: var(--accent);
--rc-brand-light: color-mix(in oklab, var(--accent) 84%, white); --rc-brand-light: color-mix(in oklab, var(--accent) 84%, white);
...@@ -26,6 +24,19 @@ ...@@ -26,6 +24,19 @@
var(--background) var(--background)
); );
--rc-text-muted: var(--muted); --rc-text-muted: var(--muted);
--rc-text-empty: color-mix(in oklab, var(--muted) 72%, var(--background));
/* Fixed dark Sidebar aliases. The content canvas remains light. */
--rc-sidebar-background: var(--xinyuan-sidebar-background);
--rc-sidebar-border: var(--xinyuan-sidebar-border);
--rc-sidebar-foreground: var(--xinyuan-sidebar-foreground);
--rc-sidebar-muted: var(--xinyuan-sidebar-muted);
--rc-sidebar-item-hover: var(--xinyuan-sidebar-item-hover);
--rc-sidebar-item-selected: var(--xinyuan-sidebar-item-selected);
--rc-sidebar-item-selected-foreground: var(
--xinyuan-sidebar-item-selected-foreground
);
--rc-sidebar-focus: var(--xinyuan-sidebar-focus);
--rc-font-sans: var(--font-sans); --rc-font-sans: var(--font-sans);
--rc-font-medium: 500; --rc-font-medium: 500;
--rc-font-semibold: 600; --rc-font-semibold: 600;
...@@ -42,6 +53,7 @@ ...@@ -42,6 +53,7 @@
/* Page geometry only. Component appearance remains owned by HeroUI. */ /* Page geometry only. Component appearance remains owned by HeroUI. */
--rc-shell-inline-gutter: 3rem; --rc-shell-inline-gutter: 3rem;
--rc-sidebar-width: 15rem;
--rc-navbar-inline-padding: var(--rc-shell-inline-gutter); --rc-navbar-inline-padding: var(--rc-shell-inline-gutter);
--rc-page-inline-start: var(--rc-shell-inline-gutter); --rc-page-inline-start: var(--rc-shell-inline-gutter);
--rc-page-inline-end: var(--rc-shell-inline-gutter); --rc-page-inline-end: var(--rc-shell-inline-gutter);
...@@ -70,31 +82,20 @@ ...@@ -70,31 +82,20 @@
--rc-login-brand-size: 1.5rem; --rc-login-brand-size: 1.5rem;
--rc-login-brand-weight: var(--rc-font-semibold); --rc-login-brand-weight: var(--rc-font-semibold);
--rc-login-brand-gap: 1.5rem; --rc-login-brand-gap: 1.5rem;
/* Shared modal width ladder from the Xinyuan overlay contract. */
--rc-dialog-width-xs: 20rem;
--rc-dialog-width-sm: 24rem;
--rc-dialog-width-md: 30rem;
--rc-dialog-width-lg: 40rem;
--rc-dialog-width-xl: 46rem;
--rc-dialog-width-2xl: 52.5rem;
--rc-dialog-width-3xl: 60rem;
--rc-dialog-width-4xl: 68rem;
--rc-dialog-width-5xl: 76rem;
--rc-shadow-raised: 0 1px 2px rgb(15 23 42 / 6%), --rc-shadow-raised: 0 1px 2px rgb(15 23 42 / 6%),
0 8px 24px rgb(15 23 42 / 8%); 0 8px 24px rgb(15 23 42 / 8%);
--rc-shadow-overlay: 0 16px 40px rgb(15 23 42 / 14%); --rc-shadow-overlay: 0 16px 40px rgb(15 23 42 / 14%);
--rc-shadow-card: 0 1px 2px rgb(15 23 42 / 2%), 0 4px 12px rgb(15 23 42 / 2%); --rc-shadow-card: 0 1px 2px 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-13);
--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-8);
--rc-metric-tone-indigo: oklch(53% 0.13 275); --rc-metric-tone-indigo: var(--rc-chart-9);
--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,
...@@ -143,39 +144,28 @@ ...@@ -143,39 +144,28 @@
); );
/* Data visualization only. Do not reuse UI status colors as a series palette. */ /* Data visualization only. Do not reuse UI status colors as a series palette. */
--rc-chart-1: oklch(45.32% 0.1661 27.03); --rc-chart-1: var(--xinyuan-chart-1);
--rc-chart-2: oklch(56% 0.135 255); --rc-chart-2: var(--xinyuan-chart-2);
--rc-chart-3: oklch(64% 0.11 220); --rc-chart-3: var(--xinyuan-chart-3);
--rc-chart-4: oklch(56% 0.115 280); --rc-chart-4: var(--xinyuan-chart-4);
--rc-chart-5: oklch(60% 0.095 200); --rc-chart-5: var(--xinyuan-chart-5);
--rc-chart-6: oklch(70% 0.12 80); --rc-chart-6: var(--xinyuan-chart-6);
--rc-chart-7: oklch(60% 0.1 295); --rc-chart-7: var(--xinyuan-chart-7);
--rc-chart-8: oklch(57% 0.035 250); --rc-chart-8: var(--xinyuan-chart-8);
--rc-chart-9: var(--xinyuan-chart-9);
--rc-chart-10: var(--xinyuan-chart-10);
--rc-chart-11: var(--xinyuan-chart-11);
--rc-chart-12: var(--xinyuan-chart-12);
--rc-chart-13: var(--xinyuan-chart-13);
--rc-chart-14: var(--xinyuan-chart-14);
--rc-chart-15: var(--xinyuan-chart-15);
--rc-chart-16: var(--xinyuan-chart-16);
--rc-chart-primary: var(--rc-chart-1); --rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: oklch(47% 0.145 27.03); --rc-chart-risk: var(--rc-chart-1);
--rc-chart-warning: var(--rc-chart-6); --rc-chart-warning: var(--rc-chart-3);
--rc-chart-positive: var(--rc-chart-2); --rc-chart-positive: var(--rc-chart-7);
--rc-chart-comparison: var(--rc-chart-4); --rc-chart-comparison: var(--rc-chart-5);
--rc-chart-neutral: var(--rc-chart-8); --rc-chart-neutral: var(--rc-chart-8);
--rc-chart-legend-font-size: 0.75rem; --rc-chart-legend-font-size: var(--xinyuan-chart-legend-font-size);
--rc-chart-legend-line-height: 1rem; --rc-chart-legend-line-height: var(--xinyuan-chart-legend-line-height);
}
.dark,
[data-theme='dark'] {
color-scheme: dark;
--rc-text-secondary: color-mix(
in oklab,
var(--foreground) 76%,
var(--background)
);
--rc-button-secondary-border: color-mix(
in oklab,
var(--foreground) 18%,
transparent
);
--rc-shadow-raised: 0 1px 2px rgb(0 0 0 / 24%), 0 8px 24px rgb(0 0 0 / 24%);
--rc-shadow-overlay: 0 16px 40px rgb(0 0 0 / 38%);
--rc-shadow-card: 0 1px 2px rgb(0 0 0 / 20%), 0 4px 12px rgb(0 0 0 / 18%);
} }
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