Commit 98b07c71 authored by Andy-bubu's avatar Andy-bubu

feat: sync Xinyuan UI and list interactions

parent 2da8483a
......@@ -11,6 +11,7 @@
/.umi-test
/dist
/.playwright-cli
/artifacts
/.mfsu
.swc
.pnpm-store
......@@ -16,9 +16,11 @@
- 不引入 Catalyst、Headless UI、兼容旧 props 的 HeroUI 包装层或可见原生选择/日期/文件控件。
- 列表使用 HeroUI Pro DataGrid、ActionBar、EmptyState 和 HeroUI Pagination。
- 筛选使用 HeroUI Surface、SearchField、Autocomplete、DatePicker 和 Disclosure。
- 受控 Autocomplete 的已选值必须可逆操作:单选使用官方 `Autocomplete.ClearButton` 清除当前值;多选在 Trigger 内使用官方 `TagGroup + Tag + Tag.RemoveButton` 将每个值显示为独立 Tag 并支持单项删除,同时保留官方清除入口清空集合;下拉中的官方 `ListBox.Item` 再次点选也可移除单项。Tag、清除按钮和选中态都从同一受控 `selectedKeys` 派生,不得合并为逗号文本、使用页面自制标签或维护第二套选择状态。
- 受控 Autocomplete 的已选值必须可逆操作:单选使用官方 `Autocomplete.ClearButton` 清除当前值;多选在 Trigger 内使用官方 `TagGroup + Tag + Tag.RemoveButton` 将每个值显示为独立 Tag 并支持单项删除,同时保留官方清除入口清空集合;下拉中的官方 `ListBox.Item` 再次点选也可移除单项。Tag、清除按钮和选中态都从同一受控 `selectedKeys` 派生,不得合并为逗号文本、使用页面自制标签或维护第二套选择状态。已选 TagGroup 使用 HeroUI `default` variant,Tag 使用官方 `accent-soft-hover` / `accent-soft-foreground` 语义 Token,继承鑫元品牌辅助色并与字段背景拉开层级,避免纯白 `surface`。
- `isOpen` 只在调用方明确提供布尔值时作为受控属性传给 Autocomplete;未提供时必须让 HeroUI 管理打开态,禁止用 `isOpen={undefined}` 锁死 Trigger。
- 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 分配颜色。
- 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 提供。
......@@ -26,9 +28,15 @@
- 字段标签必须使用共享 `getFieldDisplayLabel` 将字段定义别名映射为中文;`metadata.*` 仅是数据路径,禁止直接显示在标签、placeholder、ARIA 文案或列表表头中。未知技术字段统一显示“其他信息”,不得把英文下划线别名暴露给用户。
- 输入提示只出现在控件内部:文本/长文本使用中文 placeholder,选择、日期、人员/引用和附件使用各自 HeroUI 控件的内部提示。禁止在控件下方再渲染同一句自动生成的 `Description`;真实错误、加载、空状态仍分别由 Alert、Spinner、EmptyState 承担。
- 弹窗内编辑字段统一使用 HeroUI `secondary` variant:文本、TextArea、DatePicker、Autocomplete(含普通选项、人员、引用字段)必须共享 L2 字段背景与 disabled 状态,不得因组合组件漏传 variant 产生白灰混杂层级。
- 同一业务弹窗的查看态与编辑态必须切换结构层级,而不是只切换 disabled:查看态只用一个 `Surface variant="secondary"` 包住完整只读字段组,字段本身保持无框;编辑态移除该组 Surface,让所有 `secondary` 控件直接位于 Overlay。只读或锁定字段在编辑态保持无框文字,不得渲染成禁用灰色输入框;弹窗中的独立 DataGrid 使用官方 `secondary` variant。
- 同一业务弹窗的查看态与编辑态必须保持相同字段顺序和内容结构:预览态普通 Detail 字段直接使用 Modal 页面背景,不包裹灰色大块 Surface;编辑态让可编辑字段使用 HeroUI `secondary` 控件通铺整行,禁用字段保持无框只读文字。动态表单、关联记录和独立 DataGrid 等完整模块才使用官方 `secondary/tertiary` Surface。
- 列表页标题只由 HeroUI Pro Navbar 承担;内容区不重复渲染同名二级标题,搜索栏和业务工具栏直接位于页面内容起始位置。
- 筛选操作组在查询按钮左侧提供 HeroUI `Button isIconOnly variant="secondary"` 的重置筛选入口,使用 Ant Design `ArrowPathIcon` 16px 图标和 Tooltip;重置只调用现有 `onClearFilters` 回调。
- 筛选字段占位文案由共享类型规则统一为简短的“请输入”“请选择”或“请选择日期”,不重复拼接完整字段标签;未选择值不挂载 Autocomplete 清除图标,真实选中后才显示清除入口。
- 筛选区的引用字段必须与普通下拉、人员和日期字段一样使用 HeroUI `secondary` variant,`RecordWorkbench` 使用的共享引用选择器默认并下传该 variant,避免部分引用选择器回退为白色 primary 字段层。
- 详情弹窗预览态使用页面背景作为 Modal 内容背景;普通 Detail 字段不包裹灰色大块 Surface,按 Detail View 配置顺序单列排列,标签为 16px/400 muted,值为 16px/400,空值显示“暂无内容”。动态表单、关联记录等完整模块才使用 HeroUI 模块 Surface。
- 分页页大小使用 HeroUI 原生 Autocomplete,控件宽度固定为 128px;页大小是必选当前值,不显示清除/删除图标,仍通过下拉选项切换。
- 全局 `DataTable` 必须向 HeroUI Pro `DataGrid` 传递 populated/loading/empty 状态类;populated 状态的 root、scroll、resizable、content 链使用 `min-height: 0` 并按真实表格高度收缩,不能保留空状态的最小高度。
- DataGrid 的纵向滚动必须允许滚动链:列表到达第一行或最后一行后继续滚轮/触控,页面内容容器应继续滚动以露出分页区域;仅横向滚动保持在列表内。共享 `DataTable` 在滚轮到达边界且页面仍可滚动时转发当前增量,避免浏览器或触控板吞掉边界手势;普通列表滚动、Modal 内部表格和页面已到边界时不转发。
- DataGrid 普通字段单行截断,详情关联表的长文本最多两行;完整值通过共享 HeroUI `Tooltip` 提供。页面不得使用 `whitespace-normal`、`font-mono` 或原生 `title` 来替代表格文本契约。
- DataGrid 表头、普通字段、主字段、次字段和 Chip 统一使用 `--rc-data-grid-*` Token 与 `pm-data-grid-*` 共享排版类,页面不得单独声明表格字重或颜色。
- View `settings.groups` 驱动列分组:工具栏分组按钮显示真实分组名与列数,Tooltip 列出所含字段;Hover/Focus 时仅通过语义 Token 联动高亮该组表头,点击切换整组列显隐。分组字段的 DataGrid 表头采用“分组名 + 字段名”两层结构,分组名称按 `label`、`title`、`name` 的顺序解析,不得硬编码业务组名或另建页面状态。
......@@ -39,6 +47,7 @@
- 批量动作只通过 `@heroui-pro/react/action-bar` 的浮动 ActionBar 呈现;普通工具栏保留导入、全量导出和新建,主操作在最右侧。
- 共享 DataGrid 以真实列最小可读宽度之和与当前 viewport 比较决定内部滚动和固定列,不以列数阈值决定宽表。
- 应用切换严格遵循 `model_analysis` 专项基准:HeroUI Modal 宽度为 `min(46rem, 100vw - 2rem)`、最大高度为 `min(42rem, 100dvh - 2rem)`、28px inset,仅 Body 滚动;移动端一列、`sm` 起固定两列,卡片列表与加载骨架均使用 12px 间距,桌面不扩展为三列。
- 应用切换 Header 仅保留一个 HeroUI `SearchField`,不渲染可见标题或关闭图标;Modal 通过 `aria-label="应用切换"` 保留可访问名称,隐藏的官方 `Modal.CloseTrigger`、Esc 和 Backdrop 关闭行为保持可用。
- 应用切换分组必须读取 `GET /info -> site.group` 的 `{ label, applications }` 动态配置,按用户真实可访问应用匹配并保持分组和组内顺序;空组和无权限应用不展示,未分组应用进入 `其他应用`,无分组配置时展示单个 `全部应用`,每组右侧显示真实 `{count} 个应用`。禁止写死真实组名、成员或数量。
## 布局约定
......
......@@ -94,7 +94,7 @@
- `XinyuanModal`、详情 Modal、应用切换 Modal 均使用 `rc-dialog-panel--{size}` 共享宽度阶梯;HeroUI Overlay 继续拥有弹窗背景、24px 圆角、阴影、焦点和关闭交互。
- 新增并应用 `--rc-data-grid-*` 与 `pm-data-grid-*` 共享排版 Token:表头 12/500、普通字段 14/400、主字段 14/550、次字段 13/400、Chip 12/500;页面不再单独声明表格字重或颜色。
- ReferenceSearchSelect 与 ResourceFilterMultiSelect 已标记共享 semantic class,仍由 HeroUI Autocomplete compound API 负责 trigger、search、ListBox、选中态和 Portal。
- 受控选择器复核(2026-08-24):`XinyuanAutocomplete` 的多选 Trigger 使用 HeroUI `TagGroup + Tag + Tag.RemoveButton` 显示独立可删除 Tag;单选清除、多选清空和下拉单项移除继续使用 HeroUI `Autocomplete.ClearButton`、`ListBox` 与 `ListBox.Item` 官方 API。责任单位、人员、引用和普通 Select 均回传原有空值/ID 集合协议,未增加第二套业务状态。
- 受控选择器复核(2026-08-24):`XinyuanAutocomplete` 的多选 Trigger 使用 HeroUI `TagGroup + Tag + Tag.RemoveButton` 显示独立可删除 Tag;Tag 使用官方 `accent-soft-hover` / `accent-soft-foreground` 语义 Token,采用更深的鑫元品牌辅助层并与输入框背景区分,避免已选 Tag 呈纯白 `surface`。单选清除、多选清空和下拉单项移除继续使用 HeroUI `Autocomplete.ClearButton`、`ListBox` 与 `ListBox.Item` 官方 API。责任单位、人员、引用和普通 Select 均回传原有空值/ID 集合协议,未增加第二套业务状态。
### 全局组件复核(2026-08-21)
......@@ -149,6 +149,28 @@
- `subjectView.js` 只读取 `user.applications[]` 中名称为 `integrity_risk_prevention_new` 的一项,不读取用户顶层 roles/groups/permissions,也不合并其他应用授权。系统管理员、系统账号和本应用“管理”角色旁路普通授权,但 `disableColumns` 始终先判定并保持只读。
- 详情加载通过 `GET /subject/:subjectName/view/:viewName` 获取完整 View;当前 Detail 存在任一有效 `auth` 或 `disableColumns` 时整套使用 Detail 权限,两项均无有效配置或没有实际 Detail 时整套回退活动 Grid。Detail 与 Grid 不合并,也不逐字段回退;`hasEditableFields` 只检查当前激活 Detail 的展示字段。
- Playwright 权限矩阵结果:其他应用“管理”和顶层“管理”均为记录查询 0、Grid 0;系统账号与本应用“管理”均可进入 Grid;管理员遇到 Detail `disableColumns` 时编辑按钮为 0;Detail 允许/Grid 拒绝时编辑按钮与输入框均为 1;空 Detail 回退到拒绝 Grid 时为 0、回退到允许 Grid 时为 1;Detail 只配置其他字段 auth 时当前字段不会回退 Grid;切换可编辑/只读 Detail 后编辑按钮从 1 立即变为 0。矩阵控制台 0 error / 0 warning。
### 共享弹窗与多选复核(2026-09-02)
- 详情 `Modal.Dialog` 明确使用 HeroUI `bg-background`,并通过共享 `rc-dialog-panel` / `rc-dialog-panel--4xl` 提供宽度阶梯;标题、记录摘要、辅助说明和详情 Tabs 保持在同一 Header 层级,Body 继续作为唯一滚动区域。
- `XinyuanModal` 的辅助说明统一渲染为 HeroUI `Modal.Header` 内的 `Description`,缺省时使用共享兜底文案,不在 Body 追加自定义说明块。
- `XinyuanAutocomplete` 多选只由受控字符串 key 驱动:ListBox 仅使用单一 `Item.onPress` 提交一次变化,勾选标记、Tag 删除、清空和默认回显共享同一 key 集合;未显式传入 `isOpen` 时保持 HeroUI 非受控打开态。
- 应用切换加载态与真实列表均为移动单列、`sm` 起固定两列且 `gap-3`;Header 移除可见标题与关闭图标,仅保留通栏搜索;Modal 可访问名称、隐藏关闭触发器、Esc/Backdrop 关闭、动态分组和应用授权数据流未改变。
- 分页页大小 Autocomplete 已复核为 128px 固定宽度,并关闭不适用于必选页大小的清除图标;页码、页大小变更回调和查询数据流未改变。
- 筛选区已补齐问题整改基准的重置图标按钮:HeroUI secondary icon-only Button、16px ArrowPath 图标、Tooltip,位于查询按钮左侧;仅触发既有 `onClearFilters`,不改变筛选或查询协议。
- 筛选控件已修复空值哨兵导致的误显示:`FilterSelect` 空值不再编码为选中 key,`XinyuanAutocomplete` 仅在真实选中值存在时挂载清除图标;占位文案统一为短格式,不再重复字段长名称。
- 复核发现两个引用筛选字段未传 `variant` 导致白色 primary 层,已在共享 `RecordWorkbench` 调用中补传 `secondary`,与其他筛选字段层级一致。
- 引用筛选控件复核(2026-09-03):`RecordWorkbench` 使用的详情引用选择器已接收并下传默认 `secondary` variant,修复部分引用筛选框回退白色 primary 层的问题;查询值、引用 ID、搜索请求和清空回调均保持不变。
- 详情预览态已移除普通字段外层灰色 `Surface secondary`,字段按 Detail View 顺序单列展示;标签统一 16px/400 muted,值 16px/400,空值统一为“暂无内容”,动态表单和关联记录模块仍保留各自 HeroUI 模块边界。
- 数据列表滚动链已修复:`.data-table-scroll` 的纵向 `overscroll-behavior` 恢复为 `auto`,到达首/末行后继续滚动会传递到页面容器;横向滚动仍保持列表内,不改变分页和数据流。
- 数据列表滚动链增强(2026-09-03):共享 `DataTable` 在 HeroUI DataGrid 到达首/末端且最近页面滚动容器仍可移动时,将当前滚轮增量转发给页面容器;列表中间滚动、页面到达自身边界、Modal 内无页面滚动容器时保持原生行为。分页区域可在列表末端继续滚轮后自然露出,不改变行数据、分页回调或查询时机。
- 本轮 `npm run build`、`git diff --check` 和 HeroUI Pro 子路径导入复核通过;Playwright 登录页四视口截图位于 `artifacts/integrity-login-{1440,1280,768,390}.png`,控制台无 Error/Warning。
### 品牌主色与图表配色专项(2026-09-02)
- 复核 `theme.css`、`tokens.css`、`tailwind.css` 和浏览器计算样式:HeroUI primary 按钮实际背景为 `rgb(218, 36, 26)`,即 `#DA241A`;hover、pressed、focus 和 disabled 均使用同一品牌红阶。
- 图表 Token 固定为 16 个低饱和品牌红、暖色、中性色和灰蓝色阶,没有绿色、荧光色或彩虹式相邻色。`src/utils/chartColors.js` 通过业务 key 哈希和确定性冲突递进映射到 `--rc-chart-*`,过滤、排序不会按当前索引换色;单系列固定品牌红。
- 当前应用没有真实图表业务路由,未新增伪造图表数据;现有 KPI 图标色已统一落在灰蓝/暖中性色和品牌红范围内。
# 五项跨应用能力迁移前审计(2026-08-26)
- 应用范围:统一展示名为“廉洁风险防控”,属于完整功能白名单,需实施 Grid/Detail `shortTitle`、Subject View 权限、通知中心和审计材料中心文件流程。
......
......@@ -28,6 +28,8 @@
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"jszip": "^3.10.1",
......@@ -2595,18 +2597,22 @@
}
},
"node_modules/@floating-ui/core": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.6.2.tgz",
"integrity": "sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==",
"license": "MIT"
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/dom": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.4.5.tgz",
"integrity": "sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^0.6.2"
"@floating-ui/core": "^1.8.0",
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/react-dom": {
......@@ -2636,12 +2642,26 @@
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"node_modules/@floating-ui/react-dom/node_modules/@floating-ui/core": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.6.2.tgz",
"integrity": "sha512-jktYRmZwmau63adUG3GKOAVCofBXkk55S/zQ94XOorAHhwqFIOFAy1rSp2N0Wp6/tGbe9V3u/ExlGZypyY17rg==",
"license": "MIT"
},
"node_modules/@floating-ui/react-dom/node_modules/@floating-ui/dom": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.4.5.tgz",
"integrity": "sha512-b+prvQgJt8pieaKYMSJBXHxX/DYwdLsAWxKYqnO5dO2V4oo/TYBZJAUQCVNjTWWsrs6o4VDrNcP9+E70HAhJdw==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^0.6.2"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/@fontsource/inter": {
"version": "5.3.0",
......@@ -2652,6 +2672,52 @@
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz",
"integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/intl-localematcher": "0.6.2",
"decimal.js": "^10.4.3",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.11.4",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz",
"integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.6",
"@formatjs/icu-skeleton-parser": "1.8.16",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.8.16",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz",
"integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.6",
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/intl-displaynames": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-1.2.10.tgz",
......@@ -2670,6 +2736,16 @@
"@formatjs/intl-utils": "^2.3.0"
}
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz",
"integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@formatjs/intl-relativetimeformat": {
"version": "4.5.16",
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.16.tgz",
......@@ -2852,18 +2928,42 @@
}
},
"node_modules/@internationalized/date": {
"version": "3.12.3",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz",
"integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==",
"version": "3.12.4",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz",
"integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@internationalized/message": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.10.tgz",
"integrity": "sha512-nc0Or6EdWHqZRcsXb6P9hBIpLsfSl/ILh0rk5h/OVBpzmhdExXtPy2cQtWsq8XKRBpRHwDNnAHt4OpolcB7dog==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/helpers": "^0.5.0",
"intl-messageformat": "^10.1.0"
}
},
"node_modules/@internationalized/message/node_modules/intl-messageformat": {
"version": "10.7.18",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz",
"integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.6",
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/icu-messageformat-parser": "2.11.4",
"tslib": "^2.8.0"
}
},
"node_modules/@internationalized/number": {
"version": "3.6.7",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz",
"integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==",
"version": "3.6.8",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz",
"integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
......@@ -4042,6 +4142,42 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/i18n": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.13.1.tgz",
"integrity": "sha512-z56ZYcbfpNmMyiGLhyEjytpmEfoTlBaksk84q4kds3HvNkf7QWKj+DJVfVDrJX+c1LyuBsszLSX7yxJRiHsYKQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/message": "^3.1.9",
"@internationalized/string": "^3.2.8",
"@swc/helpers": "^0.5.0",
"react-aria": "^3.48.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.10.1.tgz",
"integrity": "sha512-jn038/ZYmu6DpfXJ6r2U9zFFppjbc9wnApPJSCxao2RZVEqep4YyoniHSy8qv6V21/xyS4IV7W9a+X2jOjSuag==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/helpers": "^0.5.0",
"react-aria": "^3.48.0"
},
"engines": {
"node": ">= 12"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/utils": {
"version": "3.34.1",
"resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.34.1.tgz",
......@@ -4072,6 +4208,21 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-spectrum/provider": {
"version": "3.11.1",
"resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.11.1.tgz",
"integrity": "sha512-TsoNdVdmlQ7L+75ILq5Yb3+wp/I1AtIeat0o+Y+ZBxP+TtWpwT1ZtCB5l3cplFVzHzOpZlzO0VaDrDP9ElGYDw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@adobe/react-spectrum": "^3.47.0",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-stately/color": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.10.1.tgz",
......@@ -4161,12 +4312,6 @@
"url": "https://opencollective.com/immer"
}
},
"node_modules/@reduxjs/toolkit/node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/@shikijs/core": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz",
......@@ -5011,27 +5156,6 @@
"@tiptap/pm": "3.30.2"
}
},
"node_modules/@tiptap/extension-bubble-menu/node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@tiptap/extension-bubble-menu/node_modules/@floating-ui/dom": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/core": "^1.8.0",
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.30.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.30.2.tgz",
......@@ -5782,6 +5906,13 @@
"@types/unist": "*"
}
},
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"license": "MIT",
"peer": true
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
......@@ -5806,6 +5937,13 @@
"integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==",
"license": "MIT"
},
"node_modules/@types/isomorphic-fetch": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.34.tgz",
"integrity": "sha512-BmJKuPCZCR6pbYYgi5nKFJrPC4pLoBgsi/B1nFN64Ba+hLLGUcKPIh7eVlR2xG763Ap08hgQafq/Wx4RFb0omQ==",
"license": "MIT",
"peer": true
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
......@@ -5882,14 +6020,12 @@
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
......@@ -5900,12 +6036,47 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
}
},
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz",
"integrity": "sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/react-router-redux": {
"version": "5.0.27",
"resolved": "https://registry.npmjs.org/@types/react-router-redux/-/react-router-redux-5.0.27.tgz",
"integrity": "sha512-qC5lbuP2K/kMR/HE3e5ltCJptyiQhmfV0wbklqcqWDbNdpJBDwUsBGP4f/0RDYJf09+OTbz43u6iG+8E0Zcwqw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "^5.1.0",
"redux": ">= 3.7.2"
}
},
"node_modules/@types/resolve": {
"version": "1.20.6",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz",
......@@ -7970,6 +8141,32 @@
"styled-components": ">= 2"
}
},
"node_modules/@umijs/plugins/node_modules/dva": {
"version": "2.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva/-/dva-2.5.0-beta.2.tgz",
"integrity": "sha512-kc2+CHhF1cNIU3Rg1miMhHgOKJ/VDrq9d6ynVBZf1EN2YKWU3MVFq/uTTBqMr2qkR0m9f8VKHOFmfKLtfMI93Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.0.0",
"@types/isomorphic-fetch": "^0.0.34",
"@types/react-router-dom": "^4.2.7",
"@types/react-router-redux": "^5.0.13",
"dva-core": "^1.5.0-beta.2",
"global": "^4.3.2",
"history": "^4.6.3",
"invariant": "^2.2.2",
"isomorphic-fetch": "^2.2.1",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.2",
"react-router-redux": "5.0.0-alpha.9",
"redux": "^3.7.2"
},
"peerDependencies": {
"react": "15.x || ^16.0.0-0",
"react-dom": "15.x || ^16.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva-core": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-2.0.4.tgz",
......@@ -8010,6 +8207,124 @@
"dva": "^2.5.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva-loading": {
"version": "3.0.25",
"resolved": "https://registry.npmjs.org/dva-loading/-/dva-loading-3.0.25.tgz",
"integrity": "sha512-RYbpSjvPj2NF+3YSc9QkDpwM1wO8uRAVfU6VGSgIxA2kA/Xd6yoY+VQM7GDRnvCvT579pESeBZBXk+8WKWOdDA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0"
},
"peerDependencies": {
"dva-core": "^1.1.0 || ^1.5.0-0 || ^1.6.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/dva-core": {
"version": "1.5.0-beta.2",
"resolved": "https://registry.npmjs.org/dva-core/-/dva-core-1.5.0-beta.2.tgz",
"integrity": "sha512-xmtr/J63EZXBdVXNBW+QCD7p9CaE8kAo2U1faRyv3PIGcy0G3Y6IBDNtoBB/Cj3nzk/jvX0dv96Hnh1kpSnI7Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.0.0",
"flatten": "^1.0.2",
"global": "^4.3.2",
"invariant": "^2.2.1",
"is-plain-object": "^2.0.3",
"redux": "^3.7.1",
"redux-saga": "^0.16.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"redux": "3.x"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/react-redux": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz",
"integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"loose-envify": "^1.1.0",
"prop-types": "^15.6.1",
"react-is": "^16.6.0",
"react-lifecycles-compat": "^3.0.0"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0",
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"license": "MIT",
"peer": true,
"dependencies": {
"lodash": "^4.2.1",
"lodash-es": "^4.2.1",
"loose-envify": "^1.1.0",
"symbol-observable": "^1.0.3"
}
},
"node_modules/@umijs/plugins/node_modules/dva/node_modules/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/@umijs/plugins/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/@umijs/plugins/node_modules/react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@umijs/plugins/node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@umijs/plugins/node_modules/react-intl": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.12.1.tgz",
......@@ -8078,6 +8393,69 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/@umijs/plugins/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router-dom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.1",
"react-router": "^4.3.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/@umijs/plugins/node_modules/react-router/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/@umijs/plugins/node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/@umijs/plugins/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/@umijs/plugins/node_modules/styled-components": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz",
......@@ -8610,6 +8988,181 @@
"vite": "^4.2.0"
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"license": "MIT",
"peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/@zowe/secrets-for-zowe-sdk": {
"version": "8.29.4",
"resolved": "https://registry.npmjs.org/@zowe/secrets-for-zowe-sdk/-/secrets-for-zowe-sdk-8.29.4.tgz",
......@@ -8725,6 +9278,48 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
......@@ -10019,6 +10614,16 @@
"node": ">=18"
}
},
"node_modules/chrome-trace-event": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
......@@ -11391,6 +11996,13 @@
"node": ">=0.10.0"
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT",
"peer": true
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
......@@ -12081,18 +12693,6 @@
"stream-shift": "^1.0.2"
}
},
"node_modules/dva-loading": {
"version": "3.0.25",
"resolved": "https://registry.npmjs.org/dva-loading/-/dva-loading-3.0.25.tgz",
"integrity": "sha512-RYbpSjvPj2NF+3YSc9QkDpwM1wO8uRAVfU6VGSgIxA2kA/Xd6yoY+VQM7GDRnvCvT579pESeBZBXk+8WKWOdDA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.0.0"
},
"peerDependencies": {
"dva-core": "^1.1.0 || ^1.5.0-0 || ^1.6.0-0"
}
},
"node_modules/earcut": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.2.3.tgz",
......@@ -12105,6 +12705,36 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/echarts": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz",
"integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "6.1.0"
}
},
"node_modules/echarts-for-react": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.6.tgz",
"integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"size-sensor": "^1.0.1"
},
"peerDependencies": {
"echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
"react": "^15.0.0 || >=16.0.0"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
......@@ -12221,9 +12851,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.21.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.1.tgz",
"integrity": "sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ==",
"version": "5.24.5",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
"integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
......@@ -12418,6 +13048,13 @@
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
"integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
"license": "MIT",
"peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
......@@ -18010,84 +18647,233 @@
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/min-document": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
"integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
"license": "MIT",
"dependencies": {
"dom-walk": "^0.1.0"
}
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
"license": "MIT"
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minimist-options": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
"license": "MIT",
"dependencies": {
"arrify": "^1.0.1",
"is-plain-obj": "^1.1.0",
"kind-of": "^6.0.3"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/minimizer-webpack-plugin": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz",
"integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.3",
"terser": "^5.51.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
"@minify-html/node": {
"optional": true
},
"@swc/core": {
"optional": true
},
"@swc/css": {
"optional": true
},
"@swc/html": {
"optional": true
},
"clean-css": {
"optional": true
},
"cssnano": {
"optional": true
},
"csso": {
"optional": true
},
"esbuild": {
"optional": true
},
"html-minifier-terser": {
"optional": true
},
"lightningcss": {
"optional": true
},
"postcss": {
"optional": true
},
"uglify-js": {
"optional": true
}
}
},
"node_modules/minimizer-webpack-plugin/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"engines": {
"node": ">= 0.6"
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"node_modules/minimizer-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"engines": {
"node": ">=6"
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/min-document": {
"version": "2.19.2",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
"integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
"node_modules/minimizer-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"license": "MIT",
"peer": true,
"dependencies": {
"dom-walk": "^0.1.0"
}
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"license": "MIT",
"engines": {
"node": ">=4"
"node": ">= 10.13.0"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
"license": "MIT"
"node_modules/minimizer-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"node_modules/minimizer-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": "*"
}
"node": ">= 10.13.0"
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/minimist-options": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
"node_modules/minimizer-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"arrify": "^1.0.1",
"is-plain-obj": "^1.1.0",
"kind-of": "^6.0.3"
"has-flag": "^4.0.0"
},
"engines": {
"node": ">= 6"
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/minipass": {
......@@ -20050,6 +20836,22 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.9.6",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
"integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
......@@ -21213,19 +22015,19 @@
}
},
"node_modules/react-aria": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.48.0.tgz",
"integrity": "sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==",
"version": "3.52.0",
"resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.52.0.tgz",
"integrity": "sha512-eGU8GOqT6Stn4Se/X3esz0Wum6TqSwQJBbK3jSLJIbFAS7XkvpICCVwmqZQG1/siXuCgB9DBRq9CttI+K9HPTA==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/number": "^3.6.6",
"@internationalized/string": "^3.2.8",
"@react-types/shared": "^3.34.0",
"@internationalized/date": "^3.12.4",
"@internationalized/number": "^3.6.8",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"aria-hidden": "^1.2.3",
"clsx": "^2.0.0",
"react-stately": "3.46.0",
"react-stately": "3.50.0",
"use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
......@@ -21481,16 +22283,73 @@
"react-dom": ">=16.8"
}
},
"node_modules/react-router-redux": {
"version": "5.0.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-5.0.0-alpha.9.tgz",
"integrity": "sha512-euSgNIANnRXr4GydIuwA7RZCefrLQzIw5WdXspS8NPYbV+FxrKSS9MKG7U9vb6vsKHONnA4VxrVNWfnMUnUQAw==",
"deprecated": "This project is no longer maintained.",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"prop-types": "^15.6.0",
"react-router": "^4.2.0"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-router-redux/node_modules/history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/react-router-redux/node_modules/hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/react-router-redux/node_modules/react-router": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
"license": "MIT",
"peer": true,
"dependencies": {
"history": "^4.7.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.2.4",
"loose-envify": "^1.3.1",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.1",
"warning": "^4.0.1"
},
"peerDependencies": {
"react": ">=15"
}
},
"node_modules/react-stately": {
"version": "3.46.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.46.0.tgz",
"integrity": "sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==",
"version": "3.50.0",
"resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.50.0.tgz",
"integrity": "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==",
"license": "Apache-2.0",
"dependencies": {
"@internationalized/date": "^3.12.1",
"@internationalized/number": "^3.6.6",
"@internationalized/string": "^3.2.8",
"@react-types/shared": "^3.34.0",
"@internationalized/date": "^3.12.4",
"@internationalized/number": "^3.6.8",
"@internationalized/string": "^3.2.10",
"@react-types/shared": "^3.36.1",
"@swc/helpers": "^0.5.0",
"use-sync-external-store": "^1.6.0"
},
......@@ -21780,13 +22639,10 @@
}
},
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-saga": {
"version": "0.16.2",
......@@ -22126,6 +22982,13 @@
"node": ">=4"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==",
"license": "MIT",
"peer": true
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
......@@ -22868,6 +23731,12 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/size-sensor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.3.tgz",
"integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==",
"license": "ISC"
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
......@@ -23546,6 +24415,30 @@
"inline-style-parser": "0.2.7"
}
},
"node_modules/styled-jsx": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.7.tgz",
"integrity": "sha512-HPLmEIYprxCeWDMLYiaaAhsV3yGfIlCqzuVOybE6fjF3SUJmH67nCoMDO+nAvHNHo46OfvpCNu4Rcue82dMNFg==",
"license": "MIT",
"peer": true,
"dependencies": {
"client-only": "0.0.1"
},
"engines": {
"node": ">= 12.0.0"
},
"peerDependencies": {
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"babel-plugin-macros": {
"optional": true
}
}
},
"node_modules/stylelint": {
"version": "14.8.2",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.8.2.tgz",
......@@ -23795,6 +24688,16 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/synckit": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
......@@ -23932,9 +24835,9 @@
}
},
"node_modules/terser": {
"version": "5.47.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz",
"integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==",
"version": "5.51.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz",
"integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==",
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
......@@ -24011,6 +24914,13 @@
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
"license": "MIT",
"peer": true
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
......@@ -24324,7 +25234,6 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
......@@ -24749,6 +25658,13 @@
}
}
},
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==",
"license": "MIT",
"peer": true
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
......@@ -25296,6 +26212,19 @@
"loose-envify": "^1.0.0"
}
},
"node_modules/watchpack": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz",
"integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==",
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/wbuf": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
......@@ -25324,6 +26253,49 @@
"node": ">= 8"
}
},
"node_modules/webpack": {
"version": "5.110.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.3.tgz",
"integrity": "sha512-GuizBzRvo9YPpyoNMf3ag7AzxbaW85qrRSqTha345KyJbAFPt3/cMzBM0h+RWg7SK/7DdzRLINP3LvQ0hvr4hg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.16.0",
"browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.24.4",
"es-module-lexer": "^2.1.0",
"events": "^3.2.0",
"graceful-fs": "^4.2.11",
"mime-db": "^1.54.0",
"minimizer-webpack-plugin": "^5.7.0",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.3",
"tapable": "^2.3.0",
"watchpack": "^2.5.2",
"webpack-sources": "^3.5.1"
},
"bin": {
"webpack": "bin/webpack.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependenciesMeta": {
"webpack-cli": {
"optional": true
}
}
},
"node_modules/webpack-5-chain": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webpack-5-chain/-/webpack-5-chain-8.0.1.tgz",
......@@ -25346,6 +26318,83 @@
"node": ">=0.10.0"
}
},
"node_modules/webpack-sources": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz",
"integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack/node_modules/ajv": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/webpack/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/webpack/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT",
"peer": true
},
"node_modules/webpack/node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
......@@ -25708,6 +26757,21 @@
"zod": "^3.18.0"
}
},
"node_modules/zrender": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz",
"integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==",
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"license": "0BSD"
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
......
......@@ -30,6 +30,8 @@
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"embla-carousel": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"jszip": "^3.10.1",
......
import '@/global.less';
import '@/tailwind.css';
import { defineApp } from 'umi';
import { APPLICATION_DISPLAY_NAME, THEME_STORAGE_KEY } from '@/config/application';
import {
APPLICATION_BASE_PATH,
APPLICATION_NAME,
} from '@/config/application';
if (typeof document !== 'undefined') {
const theme = window.localStorage?.getItem?.(THEME_STORAGE_KEY) === 'dark' ? 'dark' : 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
document.documentElement.classList.toggle('light', theme === 'light');
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
document.documentElement.dataset.theme = 'light';
document.documentElement.style.colorScheme = 'light';
}
const LEGACY_LOGIN_PATH = '/login';
const appBasePath = String(
INTEGRITY_RISK_PREVENTION_APP_BASE_PATH || APPLICATION_BASE_PATH || '/',
).replace(/\/+$/, '');
if (
typeof window !== 'undefined' &&
appBasePath &&
window.location.pathname === LEGACY_LOGIN_PATH
) {
window.location.replace(
`${appBasePath}${LEGACY_LOGIN_PATH}${window.location.search}${window.location.hash}`,
);
}
export async function getInitialState() {
return {
name: APPLICATION_DISPLAY_NAME,
name: APPLICATION_NAME,
};
}
......
import { useLayoutEffect, useState } from 'react';
import { Sidebar } from '@heroui-pro/react/sidebar';
import { Toast } from '@heroui/react';
import { THEME_STORAGE_KEY } from '@/config/application';
import { history } from 'umi';
import DashboardNavbar from './DashboardNavbar';
import DashboardSidebar from './DashboardSidebar';
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({
children,
open,
onOpenChange,
notificationCount,
notificationPath,
open,
onOpenChange,
title,
...sidebarProps
}) {
const [theme, setTheme] = useState(getInitialTheme);
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 selected theme still applies for this session when storage is unavailable.
}
}, [theme]);
return (
<Sidebar.Provider
className={`template-dashboard-shell rc-app-shell ${theme}`}
className="template-dashboard-shell rc-app-shell light"
collapsible="offcanvas"
navigate={(href) => history.push(href)}
open={open}
......@@ -56,9 +26,7 @@ export default function AppShell({
<DashboardNavbar
notificationCount={notificationCount}
onOpenNotifications={notificationPath ? () => history.push(notificationPath) : undefined}
theme={theme}
title={title}
onThemeChange={setTheme}
/>
<div className="app-shell-content-scroll min-h-0 flex-1 overflow-y-auto">
<div className="rc-app-page">{children}</div>
......
import { Navbar } from '@heroui-pro/react/navbar';
import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar';
import { Badge, Button, Tooltip } from '@heroui/react';
import { Bell, Moon, SidebarPanel, Sun } from './AppIcons';
import { Bell, SidebarPanel } from './AppIcons';
export default function DashboardNavbar({
notificationCount = 0,
onOpenNotifications,
onThemeChange,
theme,
title,
}) {
const { isMobile, isOpen } = useSidebar();
const isDark = theme === 'dark';
const themeLabel = isDark ? '切换至浅色模式' : '切换至深色模式';
const normalizedNotificationCount = Math.max(0, Number(notificationCount) || 0);
return (
......@@ -53,20 +49,6 @@ export default function DashboardNavbar({
<Tooltip.Content>通知消息</Tooltip.Content>
</Tooltip>
) : null}
<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>
</Navbar.Header>
</Navbar>
......
import xyLogoRed from '@/assets/xy-logo-red.png';
import xyLogoWhite from '@/assets/xy-logo-white.png';
import { APPLICATION_DISPLAY_NAME } from '@/config/application';
import { Sheet } from '@heroui-pro/react/sheet';
import { Sheet } from '@heroui-pro/react';
import { Sidebar, useSidebar } from '@heroui-pro/react/sidebar';
import { Avatar, Button, Dropdown, Label, Tooltip } from '@heroui/react';
import { AppGrid, LogOut } from './AppIcons';
import { Avatar, Button, Disclosure, Dropdown, Label } from '@heroui/react';
import { AppGrid, ChevronDown, LogOut } from './AppIcons';
function getUserDisplay(userInfo) {
const displayName = userInfo?.display_name || userInfo?.username || '当前用户';
......@@ -64,9 +63,56 @@ function SidebarContents({
onLogout,
onOpenApplicationSwitcher,
pathname,
theme,
userInfo,
}) {
const renderMenu = (group) => (
<Sidebar.Menu aria-label={group.title}>
{group.items.map((item) => {
const Icon = item.icon;
return (
<Sidebar.MenuItem
href={item.path}
isCurrent={pathname === item.path}
key={item.path}
textValue={item.name}
tooltip={item.name}
>
<Sidebar.MenuIcon>
<Icon size={16} />
</Sidebar.MenuIcon>
<Sidebar.MenuLabel>{item.name}</Sidebar.MenuLabel>
</Sidebar.MenuItem>
);
})}
</Sidebar.Menu>
);
const renderGroup = (group) => {
const hasCurrentItem = group.items.some((item) => pathname === item.path);
const GroupIcon = group.icon;
return (
<Sidebar.Group key={group.title}>
<Disclosure defaultExpanded={hasCurrentItem}>
<Disclosure.Heading>
<Sidebar.GroupLabel>
<Disclosure.Trigger className="flex w-full min-w-0 items-center gap-2 text-left">
{GroupIcon ? <GroupIcon size={16} /> : null}
<span className="min-w-0 flex-1 truncate">{group.title}</span>
<Disclosure.Indicator>
<ChevronDown size={14} />
</Disclosure.Indicator>
</Disclosure.Trigger>
</Sidebar.GroupLabel>
</Disclosure.Heading>
<Disclosure.Content>
<Disclosure.Body>{renderMenu(group)}</Disclosure.Body>
</Disclosure.Content>
</Disclosure>
</Sidebar.Group>
);
};
return (
<>
<Sidebar.Header>
......@@ -74,12 +120,7 @@ function SidebarContents({
<div className="app-sidebar-brand-mark rc-sidebar-brand-mark">
<img
alt="鑫元"
className="rc-sidebar-logo rc-sidebar-logo--light"
src={xyLogoRed}
/>
<img
alt="鑫元"
className="rc-sidebar-logo rc-sidebar-logo--dark"
className="rc-sidebar-logo"
src={xyLogoWhite}
/>
</div>
......@@ -95,7 +136,6 @@ function SidebarContents({
className="app-sidebar-app-switcher rc-sidebar-app-switcher"
data-sidebar="label"
>
<Tooltip delay={0}>
<Button
isIconOnly
aria-label="切换应用"
......@@ -106,38 +146,15 @@ function SidebarContents({
>
<AppGrid size={16} />
</Button>
<Tooltip.Content>切换应用</Tooltip.Content>
</Tooltip>
</div>
</div>
</Sidebar.Header>
<Sidebar.Content>
{menuGroups.map((group) => (
<Sidebar.Group key={group.title}>
{group.showLabel !== false ? (
<Sidebar.GroupLabel>{group.title}</Sidebar.GroupLabel>
) : null}
<Sidebar.Menu aria-label={group.title}>
{group.items.map((item) => {
const Icon = item.icon;
return (
<Sidebar.MenuItem
href={item.path}
isCurrent={pathname === item.path}
key={item.path}
textValue={item.name}
tooltip={item.name}
>
<Sidebar.MenuIcon>
<Icon size={16} />
</Sidebar.MenuIcon>
<Sidebar.MenuLabel>{item.name}</Sidebar.MenuLabel>
</Sidebar.MenuItem>
);
})}
</Sidebar.Menu>
</Sidebar.Group>
group.showLabel === false ? (
<Sidebar.Group key={group.title}>{renderMenu(group)}</Sidebar.Group>
) : renderGroup(group)
))}
</Sidebar.Content>
......@@ -148,7 +165,7 @@ function SidebarContents({
);
}
function MobileDashboardSidebar(props) {
function AccessibleMobileSidebar(props) {
const { isMobile, isMobileOpen, setMobileOpen, side } = useSidebar();
if (!isMobile) {
......@@ -156,32 +173,27 @@ function MobileDashboardSidebar(props) {
}
return (
<Sheet isOpen={isMobileOpen} placement={side} onOpenChange={setMobileOpen}>
<Sheet.Root
isOpen={isMobileOpen}
placement={side}
onOpenChange={setMobileOpen}
>
<Sheet.Backdrop variant="blur">
<Sheet.Content className="sidebar__mobile-sheet">
<Sheet.Dialog
aria-label={`${APPLICATION_DISPLAY_NAME}导航`}
className="sidebar__mobile-dialog"
>
<Sheet.Heading className="sr-only">
{APPLICATION_DISPLAY_NAME}导航
</Sheet.Heading>
<div
className="sidebar__mobile app-sidebar-glass rc-mobile-sidebar"
data-slot="sidebar-mobile"
>
<SidebarContents
{...props}
onOpenApplicationSwitcher={() => {
setMobileOpen(false);
props.onOpenApplicationSwitcher?.();
}}
/>
<SidebarContents {...props} />
</div>
</Sheet.Dialog>
</Sheet.Content>
</Sheet.Backdrop>
</Sheet>
</Sheet.Root>
);
}
......@@ -191,7 +203,7 @@ export default function DashboardSidebar(props) {
<Sidebar className="app-sidebar-glass rc-sidebar">
<SidebarContents {...props} />
</Sidebar>
<MobileDashboardSidebar {...props} />
<AccessibleMobileSidebar {...props} />
</>
);
}
import { Close } from '@/components/AppIcons';
import XinyuanAutocomplete from '@/components/XinyuanAutocomplete';
import { ActionBar } from '@heroui-pro/react/action-bar';
import { DataGrid } from '@heroui-pro/react/data-grid';
import { EmptyState } from '@heroui-pro/react/empty-state';
......@@ -8,9 +9,28 @@ import {
Pagination,
Separator,
Spinner,
Surface,
} from '@heroui/react';
import { Tooltip } from '@heroui/react';
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useLayoutEffect, useRef, useState } from 'react';
const DATA_TABLE_LONG_TEXT_TYPES = new Set([
'long_text',
'rich_text',
'object',
'form',
'subject',
]);
const DATA_TABLE_LONG_TEXT_LABEL_PATTERN =
/需求明细|需求内容|材料清单|工作内容|检查内容|审计范围|整改情况|问题简述|整改措施|问题描述|具体问题|整改要求|描述|措施|原因|意见|备注|说明/;
export function isDataTableLongTextField(fieldDef, label = '') {
return DATA_TABLE_LONG_TEXT_TYPES.has(
String(fieldDef?.type || '').toLowerCase(),
) || DATA_TABLE_LONG_TEXT_LABEL_PATTERN.test(
String(label || fieldDef?.label || ''),
);
}
function normalizeDataTableEmptyTitle(title) {
const text = String(title || '').trim();
......@@ -25,15 +45,30 @@ function normalizeDataTableEmptyTitle(title) {
return title;
}
export function DataTableEmptyState({ title = '暂无数据' }) {
export function EmptyStateSurface({ children, className = '' }) {
return (
<Surface
className={['w-full min-w-0 rounded-2xl p-4', className].filter(Boolean).join(' ')}
variant="secondary"
>
{children}
</Surface>
);
}
export function DataTableEmptyState({ title = '暂无数据', size = 'md' }) {
const resolvedTitle = normalizeDataTableEmptyTitle(title);
return (
<EmptyState size="md">
<EmptyStateSurface>
<EmptyState size={size}>
<EmptyState.Header>
<EmptyState.Title>{resolvedTitle}</EmptyState.Title>
<EmptyState.Description className="rc-empty-state-message">
{resolvedTitle}
</EmptyState.Description>
</EmptyState.Header>
</EmptyState>
</EmptyStateSurface>
);
}
......@@ -77,67 +112,132 @@ function resolveColumnWidth(width) {
return undefined;
}
function findScrollableAncestor(node, excludedNode) {
let current = node?.parentElement;
while (current) {
if (current !== excludedNode) {
const style = window.getComputedStyle(current);
const scrollable = /(auto|scroll|overlay)/.test(style.overflowY);
if (scrollable && current.scrollHeight > current.clientHeight + 1) {
return current;
}
}
current = current.parentElement;
}
return null;
}
function forwardBoundaryWheel(event) {
if (
event.defaultPrevented ||
event.deltaY === 0 ||
event.shiftKey ||
Math.abs(event.deltaX) > Math.abs(event.deltaY)
) {
return;
}
const tableViewport = event.currentTarget.querySelector('.data-table-scroll');
if (!tableViewport || tableViewport.scrollHeight <= tableViewport.clientHeight + 1) {
return;
}
const atTop = tableViewport.scrollTop <= 1;
const atBottom =
tableViewport.scrollTop + tableViewport.clientHeight >=
tableViewport.scrollHeight - 1;
const movingPastBoundary =
(atTop && event.deltaY < 0) || (atBottom && event.deltaY > 0);
if (!movingPastBoundary) {
return;
}
const pageViewport = findScrollableAncestor(event.currentTarget, tableViewport);
if (!pageViewport) {
return;
}
const pageAtTop = pageViewport.scrollTop <= 1;
const pageAtBottom =
pageViewport.scrollTop + pageViewport.clientHeight >=
pageViewport.scrollHeight - 1;
const pageCanMove =
(event.deltaY < 0 && !pageAtTop) ||
(event.deltaY > 0 && !pageAtBottom);
if (!pageCanMove) {
return;
}
event.preventDefault();
pageViewport.scrollTop += event.deltaY;
}
export function DataTable({
ariaLabel = '数据表格',
columns,
rows,
variant = 'primary',
isLoading = false,
getRowKey,
selectedRowKeys,
onSelectedRowKeysChange,
emptyTitle = '暂无数据',
emptyStateSize = 'md',
className = '',
isWide,
disabledRowKeys,
onRowAction,
sortDescriptor,
onSortChange,
renderSelectionActions,
allowsColumnResize = true,
}) {
const viewportRef = useRef(null);
const [viewportWidth, setViewportWidth] = useState(0);
const selectionEnabled = Boolean(selectedRowKeys && onSelectedRowKeysChange);
const selectedKeys = selectionEnabled ? new Set(selectedRowKeys) : undefined;
const minimumContentWidth = useMemo(
() =>
(selectionEnabled ? 48 : 0) +
columns.reduce((total, column) => {
const isActions = column.isActions || column.key === 'actions';
const configuredWidth = resolveColumnWidth(column.width);
const numericWidth =
typeof configuredWidth === 'number' ? configuredWidth : undefined;
return total + Number(column.minWidth || numericWidth || (isActions ? 104 : 128));
}, 0),
[columns, selectionEnabled],
);
const wide = viewportWidth > 0 && minimumContentWidth > viewportWidth + 1;
useLayoutEffect(() => {
const root = viewportRef.current;
if (!root) return undefined;
const measure = () => {
const scrollContainer = root.querySelector('.data-table-scroll');
const nextWidth = scrollContainer?.clientWidth || root.clientWidth || 0;
setViewportWidth((current) => current === nextWidth ? current : nextWidth);
};
const observer = new ResizeObserver(measure);
observer.observe(root);
const scrollContainer = root.querySelector('.data-table-scroll');
if (scrollContainer) observer.observe(scrollContainer);
const animationFrame = requestAnimationFrame(measure);
return () => {
cancelAnimationFrame(animationFrame);
observer.disconnect();
};
}, [columns]);
const gridViewportRef = useRef(null);
const [hasHorizontalOverflow, setHasHorizontalOverflow] = useState(false);
const firstBusinessColumnIndex = columns.findIndex(
(column) =>
!column.isSelection &&
!column.isActions &&
column.key !== 'actions',
);
useLayoutEffect(() => {
if (typeof isWide === 'boolean') {
setHasHorizontalOverflow(isWide);
return undefined;
}
const viewport = gridViewportRef.current;
if (!viewport) {
return undefined;
}
const updateOverflow = () => {
const scrollContainer =
viewport.querySelector('.table__resizable-container') ||
viewport.querySelector('.data-table-scroll');
if (!scrollContainer) return;
setHasHorizontalOverflow(
scrollContainer.scrollWidth > scrollContainer.clientWidth + 1,
);
};
updateOverflow();
if (typeof ResizeObserver === 'undefined') {
return undefined;
}
const resizeObserver = new ResizeObserver(updateOverflow);
resizeObserver.observe(viewport);
const scrollContainer =
viewport.querySelector('.table__resizable-container') ||
viewport.querySelector('.data-table-scroll');
if (scrollContainer) resizeObserver.observe(scrollContainer);
return () => resizeObserver.disconnect();
}, [columns, isWide, rows]);
const wide = typeof isWide === 'boolean' ? isWide : hasHorizontalOverflow;
const gridColumns = columns.map((column, index) => {
const isActions = column.isActions || column.key === 'actions';
const isPrimary =
......@@ -146,7 +246,7 @@ export function DataTable({
const numericWidth =
typeof configuredWidth === 'number' ? configuredWidth : undefined;
const pinned = wide
? column.pin || (isActions ? 'end' : undefined)
? column.pin || (isActions ? 'end' : isPrimary ? 'start' : undefined)
: undefined;
const minWidth =
column.minWidth ||
......@@ -167,28 +267,32 @@ export function DataTable({
cell: (row) => {
const value = column.render ? column.render(row) : row?.[column.key];
if (value === undefined || value === null || value === '') {
return column.emptyValue === null ? null : column.emptyValue || '-';
return '-';
}
if (typeof value === 'string' || typeof value === 'number') {
return (
<DataTableText
text={value}
showTooltip={false}
className={
isPrimary ? 'pm-data-grid-primary' : 'pm-data-grid-body'
isPrimary ? 'font-medium' : ''
}
/>
);
}
return value;
return isActions ? value : (
<div className="min-w-0 max-w-full">
{value}
</div>
);
},
isRowHeader: index === 0,
isRowHeader: index === firstBusinessColumnIndex,
allowsSorting: column.sortable === true,
headerClassName: ['pm-data-grid-header', column.headerClassName]
.filter(Boolean)
.join(' '),
cellClassName: ['pm-data-grid-body', column.cellClassName]
.filter(Boolean)
.join(' '),
headerClassName: column.headerClassName,
cellClassName: [
'min-w-0',
column.cellClassName,
].filter(Boolean).join(' '),
allowsResizing: true,
minWidth,
width,
......@@ -216,15 +320,12 @@ export function DataTable({
};
return (
<div className={['relative min-w-0 w-full', className].filter(Boolean).join(' ')}>
<div
className={[
'app-data-grid-shell relative min-w-0 w-full',
rows.length > 0 ? 'app-data-grid-shell--populated' : isLoading ? 'app-data-grid-shell--loading' : 'app-data-grid-shell--empty',
className,
].filter(Boolean).join(' ')}
data-state={rows.length > 0 ? 'populated' : isLoading ? 'loading' : 'empty'}
ref={gridViewportRef}
className="relative min-w-0 w-full"
onWheelCapture={forwardBoundaryWheel}
>
<div ref={viewportRef} className="app-data-grid-viewport relative min-w-0 w-full">
{isLoading ? (
<div
className="absolute inset-0 z-10 grid place-items-center"
......@@ -234,28 +335,25 @@ export function DataTable({
</div>
) : null}
<DataGrid
className={[
'app-data-grid',
rows.length > 0 ? 'app-data-grid--populated' : isLoading ? 'app-data-grid--loading' : 'app-data-grid--empty',
].join(' ')}
showSelectionCheckboxes={selectionEnabled}
allowsColumnResize
allowsColumnResize={allowsColumnResize}
aria-label={ariaLabel}
columns={gridColumns}
contentClassName={[
'app-data-grid__content',
wide ? 'min-w-max' : 'min-w-full',
].join(' ')}
contentClassName={wide ? 'min-w-max' : 'min-w-full'}
data={rows}
disabledKeys={disabledRowKeys}
getRowId={(row) => resolveRowKey(row, rows.indexOf(row), getRowKey)}
renderEmptyState={() =>
isLoading ? <DataTableLoadingState /> : <DataTableEmptyState title={emptyTitle} />
isLoading ? (
<DataTableLoadingState />
) : (
<DataTableEmptyState title={emptyTitle} size={emptyStateSize} />
)
}
scrollContainerClassName="data-table-scroll app-data-grid__scroll"
scrollContainerClassName="data-table-scroll"
selectedKeys={selectedKeys}
selectionMode={selectionEnabled ? 'multiple' : 'none'}
variant={variant}
variant="primary"
verticalAlign="middle"
onRowAction={onRowAction}
sortDescriptor={sortDescriptor}
......@@ -291,6 +389,7 @@ export function DataTableActionBar({
return (
<ActionBar
aria-label={ariaLabel}
className="rc-data-grid-action-bar"
isOpen={selectedCount > 0}
>
<ActionBar.Prefix>
......@@ -325,8 +424,10 @@ export function DataTableText({
}) {
const fullText = String(text ?? children ?? '').trim();
const multilineClassName =
maxLines === 2
? 'app-data-grid-text--clamp-2 block min-w-0 overflow-hidden whitespace-normal break-words [overflow-wrap:anywhere]'
maxLines > 1
? `overflow-hidden whitespace-normal break-words [display:-webkit-box] [-webkit-box-orient:vertical] ${
maxLines === 3 ? '[-webkit-line-clamp:3]' : '[-webkit-line-clamp:2]'
} [overflow-wrap:anywhere]`
: '';
const trigger = showTooltip ? (
<DataTableTextTooltipTrigger
......@@ -337,16 +438,16 @@ export function DataTableText({
{children ?? (fullText || '-')}
</DataTableTextTooltipTrigger>
) : (
<span className={['pm-data-grid-body block min-w-0 truncate', className].filter(Boolean).join(' ')}>
<span className={[
maxLines > 1 ? multilineClassName : 'truncate',
'block min-w-0 max-w-full overflow-hidden',
className,
].filter(Boolean).join(' ')}>
{children ?? (fullText || '-')}
</span>
);
if (
!fullText ||
fullText === '-' ||
(!showTooltip && fullText.length <= 18)
) {
if (!showTooltip || !fullText || fullText === '-') {
return trigger;
}
......@@ -371,14 +472,18 @@ function DataTableTextTooltipTrigger({
<Tooltip>
<Tooltip.Trigger
className={[
'pm-data-grid-body block min-w-0',
truncate ? 'truncate' : '',
'block min-w-0 max-w-full overflow-hidden',
truncate ? 'min-w-0 truncate' : '',
className,
].filter(Boolean).join(' ')}
>
{children}
</Tooltip.Trigger>
<Tooltip.Content>{text}</Tooltip.Content>
<Tooltip.Content>
<span className="block max-w-[min(32rem,calc(100vw-2rem))] whitespace-pre-wrap break-words">
{text}
</span>
</Tooltip.Content>
</Tooltip>
);
}
......@@ -393,7 +498,7 @@ export function DataTablePrimaryText({
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-primary', className].filter(Boolean).join(' ')}
className={['font-medium', className].filter(Boolean).join(' ')}
>
{children}
</DataTableText>
......@@ -414,7 +519,6 @@ export function DataTablePrimaryAction({
const handlePress = onPress || onClick;
return (
<Tooltip>
<Button
aria-label={ariaLabel || fullText}
className={className}
......@@ -427,8 +531,6 @@ export function DataTablePrimaryAction({
{children ?? fullText}
</span>
</Button>
<Tooltip.Content>{fullText}</Tooltip.Content>
</Tooltip>
);
}
......@@ -442,7 +544,7 @@ export function DataTableSecondaryText({
<DataTableText
text={text}
showTooltip={showTooltip}
className={['pm-data-grid-secondary', className]
className={['text-sm text-muted', className]
.filter(Boolean)
.join(' ')}
>
......@@ -466,14 +568,14 @@ export function DataTableTextPair({
return (
<DataTableTextTooltipTrigger
className={['pm-data-grid-text-pair flex min-w-0 flex-col', className]
className={['flex min-w-0 flex-col', className]
.filter(Boolean)
.join(' ')}
text={tooltipText}
truncate={false}
>
<span
className={['pm-data-grid-primary min-w-0 truncate', primaryClassName]
className={['min-w-0 truncate font-medium', primaryClassName]
.filter(Boolean)
.join(' ')}
>
......@@ -482,7 +584,7 @@ export function DataTableTextPair({
{hasSecondary ? (
<span
className={[
'pm-data-grid-secondary min-w-0 truncate',
'min-w-0 truncate text-sm text-muted',
secondaryClassName,
]
.filter(Boolean)
......@@ -510,7 +612,7 @@ export function DataTablePersonIdentity({ name, account, className = '' }) {
export function DataTableChip({ children, text, className = '' }) {
const fullText = String(text ?? children ?? '').trim();
const trigger = <Chip className={['pm-data-grid-chip', className].filter(Boolean).join(' ')} size="sm">{children ?? (fullText || '-')}</Chip>;
const trigger = <Chip className={className} size="sm">{children ?? (fullText || '-')}</Chip>;
if (!fullText || fullText === '-' || fullText.length <= 18) {
return trigger;
......@@ -527,8 +629,10 @@ export function DataTableChip({ children, text, className = '' }) {
export function DataTablePagination({
page,
pageSize,
pageSizeOptions = [],
total,
onPageChange,
onPageSizeChange,
isDisabled = false,
itemLabel = '条',
}) {
......@@ -537,13 +641,44 @@ export function DataTablePagination({
const pageWindowEnd = total
? Math.min(totalPages, pageWindowStart + 5)
: pageWindowStart;
const normalizedPageSizeOptions = pageSizeOptions
.map((value) => Number(value))
.filter((value, index, values) => Number.isFinite(value) && value > 0 && values.indexOf(value) === index);
const pageSizeOptionsForPicker = normalizedPageSizeOptions.map((value) => ({
id: String(value),
label: `${value} 条/页`,
}));
const hasPageSizePicker =
pageSizeOptionsForPicker.length > 0 && typeof onPageSizeChange === 'function';
return (
<div className="flex flex-wrap items-center justify-between gap-3">
<Pagination size="sm">
<Pagination.Summary>
共 {total} {itemLabel},第{' '}
{total ? page + 1 : 0} / {total ? totalPages : 0} 页
</Pagination.Summary>
<div className="flex min-w-0 flex-wrap items-center justify-between gap-3">
<div className="flex min-w-0 shrink-0 items-center gap-3">
<span className="min-w-0 text-sm text-muted">
共 {total} {itemLabel},第 {total ? page + 1 : 0} / {total ? totalPages : 0} 页
</span>
{hasPageSizePicker ? (
<XinyuanAutocomplete
aria-label="每页条数"
className="w-32 shrink-0"
isDisabled={isDisabled}
isClearable={false}
options={pageSizeOptionsForPicker}
placeholder="每页条数"
selectedKey={String(pageSize)}
showSearch={false}
valueText={`${pageSize} 条/页`}
variant="secondary"
onSelectionChange={(key) => {
const nextPageSize = Number(key);
if (normalizedPageSizeOptions.includes(nextPageSize)) {
onPageSizeChange(nextPageSize);
}
}}
/>
) : null}
</div>
<Pagination className="ml-auto w-auto shrink-0" size={hasPageSizePicker ? 'lg' : 'sm'}>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
......
......@@ -202,7 +202,13 @@ function NestedSubjectFields({ fieldDef, value, onChange, disabled, depth }) {
}, [subjectName, depth]);
if (!subjectName || depth >= MAX_SUBJECT_DEPTH) {
return <JsonInput value={value} onChange={onChange} placeholder={fieldPlaceholder} />;
return (
<JsonInput
value={value}
onChange={onChange}
placeholder={getFieldPlaceholder(fieldDef)}
/>
);
}
if (error) {
......
......@@ -43,7 +43,7 @@ export default function FilterSelect({
const selectedOption = normalizedOptions.find(
(option) => option.value === selectedValue,
);
const selectedKey = selectedValue === '' ? EMPTY_OPTION_KEY : selectedValue;
const selectedKey = selectedValue === '' ? null : selectedValue;
return (
<XinyuanAutocomplete
......
......@@ -29,7 +29,8 @@ import {
loadSubjectWorkbenchResources,
} from '@/services/SubjectWorkbenchService';
const DEFAULT_PAGE_SIZE = 10;
const DEFAULT_PAGE_SIZE = 20;
const PAGE_SIZE_OPTIONS = [20, 50, 100];
const BOOLEAN_FILTER_OPTIONS = [{ label: '是', value: 'true' }, { label: '否', value: 'false' }];
function normalizeFilterFieldPath(field) {
......@@ -72,7 +73,7 @@ function buildFilterSchema(columns = []) {
else if (isReferenceFieldType(fieldType)) type = getReferenceSubjectName(fieldDef) ? 'reference' : 'text';
else if (['text', 'long_text', 'rich_text'].includes(fieldType)) type = 'text';
if (!type) return fields;
fields.push({ key: `field:${field}`, field, fieldDef, label: column.label || field, type, options, placeholder: `筛选${column.label || field}` });
fields.push({ key: `field:${field}`, field, fieldDef, label: column.label || field, type, options });
return fields;
}, []);
}
......@@ -196,7 +197,7 @@ export default function SubjectRecordWorkbench({
const [pendingFilters, setPendingFilters] = useState({});
const [selectedIds, setSelectedIds] = useState([]);
const [page, setPage] = useState(0);
const pageSize = DEFAULT_PAGE_SIZE;
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [recordDialogOpen, setRecordDialogOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState(null);
const [deleteTargets, setDeleteTargets] = useState([]);
......@@ -393,7 +394,7 @@ export default function SubjectRecordWorkbench({
? buildSummaryItems({ records, filteredRecords, selectedIds, activeView })
: [
{ label: '记录总数', value: records.length, hint: '当前对象全部记录', tone: 'default' },
{ label: '筛选结果', value: filteredRecords.length, hint: activeView?.title || '当前视图', tone: 'emerald' },
{ label: '筛选结果', value: filteredRecords.length, hint: activeView?.title || '当前视图', tone: 'info' },
{ label: '当前已选', value: selectedIds.length, hint: '可执行批量操作', tone: 'amber' },
];
......@@ -565,12 +566,19 @@ export default function SubjectRecordWorkbench({
pagination={{
page: safePage,
pageSize,
pageSizeOptions: PAGE_SIZE_OPTIONS,
totalRecords: filteredRecords.length,
totalPages,
onPageChange: (nextPage) => {
setPage(nextPage);
setSelectedIds([]);
},
onPageSizeChange: (nextPageSize) => {
if (!PAGE_SIZE_OPTIONS.includes(nextPageSize)) return;
setPageSize(nextPageSize);
setPage(0);
setSelectedIds([]);
},
}}
stickyTable
showContentTitle={false}
......
......@@ -45,11 +45,13 @@ export default function XinyuanAutocomplete({
fullWidth = true,
inputValue,
isDisabled = false,
isClearable,
isLoading = false,
isOpen,
isRequired = false,
name,
onInputChange,
onClear,
onOpenChange,
onSelectionChange,
options = [],
......@@ -58,6 +60,7 @@ export default function XinyuanAutocomplete({
popoverHeader,
renderOption,
searchPlaceholder = '输入关键词搜索',
showSearch = true,
showClearButton = true,
selectedKey,
selectedKeys,
......@@ -67,6 +70,7 @@ export default function XinyuanAutocomplete({
variant,
}) {
const resolvedInputValue = inputValue ?? '';
const resolvedShowClearButton = isClearable ?? showClearButton;
const selectedKeySet = getSelectedKeySet(
selectionMode,
selectedKey,
......@@ -75,7 +79,12 @@ export default function XinyuanAutocomplete({
const selectedKeyStringSet = new Set(
Array.from(selectedKeySet).map(toSelectionKey),
);
const selectedOptions = options.filter((option) => selectedKeyStringSet.has(toSelectionKey(option.id)));
const optionMap = new Map(
options.map((option) => [toSelectionKey(option.id), option]),
);
const selectedOptions = [...selectedKeyStringSet].map((key) =>
optionMap.get(key) || { id: key, label: key },
);
const resolvedValueText =
valueText ??
(selectedOptions.length > 0
......@@ -89,9 +98,13 @@ export default function XinyuanAutocomplete({
activeFilter(option.textValue || option.label, resolvedInputValue),
)
: options;
const hasSelection = showClearButton && selectedKeySet.size > 0;
const hasSelection = selectedKeySet.size > 0;
const clearLabel = selectionMode === 'multiple' ? '清空已选项' : '清除已选项';
const handleClear = () => {
if (onClear) {
onClear();
return;
}
onSelectionChange?.(selectionMode === 'multiple' ? new Set() : null);
};
const handleMultipleOptionPress = (option) => {
......@@ -125,29 +138,23 @@ export default function XinyuanAutocomplete({
);
onSelectionChange?.(nextKeys);
};
const handleMultipleOptionAction = (optionKey) => {
const nextKeys = new Set(selectedKeyStringSet);
const key = toSelectionKey(optionKey);
if (!key) return;
if (nextKeys.has(key)) nextKeys.delete(key); else nextKeys.add(key);
onSelectionChange?.(nextKeys);
};
const rootOpenProps = typeof isOpen === 'boolean' ? { isOpen } : {};
const normalizedSelectionProps = selectionMode === 'multiple'
? { selectedKey: null }
: { selectedKey: selectedKeySet.values().next().value || null };
const normalizedListProps = selectionMode === 'multiple'
? { selectionMode: 'single', onAction: handleMultipleOptionAction }
? { selectionMode: 'single' }
: { selectionMode: 'single', selectedKeys: selectedKeySet, onSelectionChange };
return (
<Autocomplete
{...rootOpenProps}
{...normalizedSelectionProps}
aria-label={ariaLabel || placeholder}
allowsEmptyCollection={allowsEmptyCollection}
className={className}
fullWidth={fullWidth}
isDisabled={isDisabled}
isOpen={isOpen}
isRequired={isRequired}
name={name}
selectionMode={selectionMode === 'multiple' ? 'single' : selectionMode}
......@@ -170,7 +177,7 @@ export default function XinyuanAutocomplete({
className="min-w-0 flex-1"
selectionMode="none"
size="sm"
variant="surface"
variant="default"
onRemove={handleTagRemove}
>
<TagGroup.List
......@@ -178,7 +185,11 @@ export default function XinyuanAutocomplete({
items={selectedOptions}
>
{(option) => (
<Tag id={option.id} textValue={option.label}>
<Tag
id={option.id}
textValue={option.label}
className="bg-accent-soft-hover text-accent-soft-foreground"
>
<span className="min-w-0 truncate">{option.label}</span>
<Tag.RemoveButton
aria-label={`移除${option.label}`}
......@@ -191,11 +202,9 @@ export default function XinyuanAutocomplete({
) : selectionMode === 'multiple' ? (
<span className="block min-w-0 flex-1 truncate">{placeholder}</span>
) : null}
{showClearButton ? (
{resolvedShowClearButton && hasSelection ? (
<Autocomplete.ClearButton
aria-hidden={hasSelection ? undefined : true}
aria-label={clearLabel}
data-empty={hasSelection ? 'false' : 'true'}
/>
) : null}
<Autocomplete.Indicator>
......@@ -209,6 +218,7 @@ export default function XinyuanAutocomplete({
onInputChange={onInputChange}
>
{popoverHeader}
{showSearch ? (
<SearchField
aria-label={searchPlaceholder}
className="reference-search-select__input"
......@@ -225,6 +235,7 @@ export default function XinyuanAutocomplete({
</SearchField.ClearButton>
</SearchField.Group>
</SearchField>
) : null}
<ListBox
aria-label={ariaLabel || placeholder}
dependencies={[
......
......@@ -3,16 +3,14 @@ import { Description, Modal } from '@heroui/react';
import clsx from 'clsx';
const DIALOG_WIDTH_CLASS = {
xs: 'rc-dialog-panel--xs',
sm: 'rc-dialog-panel--sm',
md: 'rc-dialog-panel--md',
lg: 'rc-dialog-panel--lg',
xl: 'rc-dialog-panel--xl',
'2xl': 'rc-dialog-panel--2xl',
'3xl': 'rc-dialog-panel--3xl',
'4xl': 'rc-dialog-panel--4xl',
'5xl': 'rc-dialog-panel--5xl',
md: 'sm:max-w-[30rem]',
xl: 'sm:max-w-[46rem]',
'2xl': 'sm:max-w-[52.5rem]',
'3xl': 'sm:max-w-[60rem]',
'4xl': 'sm:max-w-[68rem]',
'5xl': 'sm:max-w-[76rem]',
};
const STANDARD_PANEL_PADDING_SIZES = new Set(['xl', '2xl', '3xl', '4xl', '5xl']);
export default function XinyuanModal({
isOpen,
......@@ -26,7 +24,6 @@ export default function XinyuanModal({
footer,
}) {
const modalSize = ['xs', 'sm', 'md', 'lg'].includes(size) ? size : 'lg';
const panelPaddingClass = ['xs', 'sm', 'md'].includes(size) ? 'p-6' : 'p-7';
return (
<Modal.Root>
......@@ -35,8 +32,8 @@ export default function XinyuanModal({
<Modal.Container placement="center" scroll="inside" size={modalSize}>
<Modal.Dialog
className={clsx(
'rc-dialog-panel',
panelPaddingClass,
STANDARD_PANEL_PADDING_SIZES.has(size) && 'p-7',
'bg-background',
DIALOG_WIDTH_CLASS[size],
dialogClassName,
)}
......@@ -46,7 +43,7 @@ export default function XinyuanModal({
</Modal.CloseTrigger>
<Modal.Header>
<Modal.Heading>{title}</Modal.Heading>
<Description>{description}</Description>
{description ? <Description>{description}</Description> : null}
</Modal.Header>
<Modal.Body className={bodyClassName}>
{children}
......
import {
DataTable,
DataTablePagination,
DataTablePersonIdentity,
DataTablePrimaryAction,
DataTableText,
isDataTableLongTextField,
} from '@/components/DataTable';
import { KPI } from '@heroui-pro/react/kpi';
import {
......@@ -26,15 +30,19 @@ import UserSearchSelect from '@/components/UserSearchSelect';
import { formatFieldValue, getFieldValue, normalizeText } from '@/utils/subjectView';
import {
Alert,
Avatar,
Button,
Chip,
Disclosure,
Input,
Label,
SearchField,
Surface,
Tabs,
toast,
ToggleButton,
Tooltip,
buttonVariants,
toast,
} from '@heroui/react';
import clsx from 'clsx';
import { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
......@@ -64,7 +72,7 @@ function resolveActionColumnWidth(rowActions = [], explicitWidth) {
function FilterField({ label, children }) {
return (
<div className="w-full min-w-0">
<div className="mb-1.5 text-sm font-medium leading-5">{label}</div>
<Label className="mb-1.5 block">{label}</Label>
<div className="min-w-0">{children}</div>
</div>
);
......@@ -235,7 +243,7 @@ function SummaryCard({ label, value, tone, icon: Icon }) {
const numericValue = Number(value);
return (
<KPI>
<KPI className="rc-card-surface rc-dashboard-card">
<KPI.Header>
<KPI.Title>{label}</KPI.Title>
</KPI.Header>
......@@ -277,6 +285,21 @@ function getActionButtonVariant(action) {
return action?.variant || (action?.tone === 'primary' ? 'primary' : 'secondary');
}
function orderActionsWithPrimaryLast(actions) {
const supportingActions = [];
const primaryActions = [];
actions.forEach((action) => {
if (getActionButtonVariant(action) === 'primary') {
primaryActions.push(action);
} else {
supportingActions.push(action);
}
});
return [...supportingActions, ...primaryActions];
}
function defaultGetRecordId(record) {
return record?.id || record?._id || record?.name || '';
}
......@@ -289,21 +312,78 @@ function defaultGetRecordLabel(record) {
return record?.title || record?.name || record?.id || '当前记录';
}
function getFilterPlaceholder(field) {
if (field?.placeholder) return field.placeholder;
if (field?.type === 'text' || field?.type === 'number') return '请输入';
if (field?.type === 'date' || field?.type === 'datetime' || field?.type === 'month') {
return '请选择日期';
}
return '请选择';
}
function defaultRenderCell(record, column) {
const sourceRecord = record?.raw || record;
const value = getFieldValue(sourceRecord, column.field);
const text = formatFieldValue(value, column.fieldDef) || '-';
if (column.fieldDef?.type === 'user') {
const users = (Array.isArray(value) ? value : [value]).filter(Boolean);
if (users.length === 0) {
return <span className="text-muted">-</span>;
}
return (
<div className="flex min-w-0 flex-col gap-2">
{users.map((user, index) => {
const userObject = typeof user === 'object' ? user : {};
const name = String(
userObject.display_name || userObject.name || userObject.username || user,
);
const account = String(userObject.username || userObject.email || '');
return (
<div className="truncate text-sm text-foreground">
{formatFieldValue(value, column.fieldDef) || '-'}
<div className="flex min-w-0 items-center gap-2" key={userObject._id || account || `${name}-${index}`}>
<Avatar size="sm" variant="soft">
{userObject.avatar ? <Avatar.Image alt={name} src={userObject.avatar} /> : null}
<Avatar.Fallback>{name.slice(0, 1) || 'U'}</Avatar.Fallback>
</Avatar>
<DataTablePersonIdentity account={account} className="min-w-0" name={name} />
</div>
);
})}
</div>
);
}
if (/状态|进度|评级|程度/.test(String(column.label || '')) && text !== '-') {
const color = /失败|驳回|拒绝|逾期|终止|整改/.test(text)
? 'danger'
: /完成|通过|归档|正常/.test(text)
? 'success'
: /执行|进行/.test(text)
? 'accent'
: /计划|待/.test(text)
? 'warning'
: 'default';
return <Chip color={color} size="sm">{text}</Chip>;
}
const isLongText = isDataTableLongTextField(column.fieldDef, column.label);
return (
<DataTableText
className="text-sm text-foreground"
maxLines={isLongText ? 2 : 1}
showTooltip={false}
text={text}
/>
);
}
function getReadableColumnWidth(column) {
const configuredWidth = Number(column?.width) || 180;
const label = String(column?.label || '');
if (/整改情况/.test(label)) {
if (isDataTableLongTextField(column?.fieldDef, label)) {
return Math.max(configuredWidth, 420);
}
if (/问题简述|整改措施|问题描述|具体问题|整改要求|原因|意见|备注|说明/.test(label)) {
......@@ -332,7 +412,7 @@ function getWorkbenchColumnWidth(column, stickyMeta) {
}
const label = String(column?.label || '');
if (/整改情况/.test(label)) {
if (isDataTableLongTextField(column?.fieldDef, label)) {
return Math.min(readableWidth, 360);
}
if (/问题简述|整改措施|问题描述|具体问题|整改要求|原因|意见|备注|说明/.test(label)) {
......@@ -342,13 +422,7 @@ function getWorkbenchColumnWidth(column, stickyMeta) {
return Math.min(readableWidth, 280);
}
function ColumnGroupControl({
group,
collapsed,
index,
onPreviewChange,
onToggle,
}) {
function ColumnGroupControl({ group, collapsed, index, onPreviewChange, onToggle }) {
const Icon = index % 2 === 0 ? Group : ChartBarSquareIcon;
const actionLabel = `${collapsed ? '显示' : '隐藏'}${group.name}列`;
const columnSummary = group.columnLabels.join('、');
......@@ -359,10 +433,10 @@ function ColumnGroupControl({
size="sm"
isSelected={collapsed}
onChange={onToggle}
onBlur={() => onPreviewChange(null)}
onFocus={() => onPreviewChange(group.id)}
onHoverEnd={() => onPreviewChange(null)}
onHoverStart={() => onPreviewChange(group.id)}
onBlur={() => onPreviewChange?.(null)}
onFocus={() => onPreviewChange?.(group.id)}
onHoverEnd={() => onPreviewChange?.(null)}
onHoverStart={() => onPreviewChange?.(group.id)}
aria-label={actionLabel}
>
<Icon size={16} />
......@@ -388,10 +462,7 @@ function ColumnHeaderLabel({ label, groupName }) {
return (
<span className="flex min-w-0 flex-col items-start gap-0.5 py-0.5">
<span
aria-hidden="true"
className="text-[11px] font-normal leading-4 text-muted"
>
<span aria-hidden="true" className="text-[11px] font-normal leading-4 text-muted">
{groupName}
</span>
<span className="text-xs font-medium leading-4 text-foreground">
......@@ -444,8 +515,10 @@ function WorkbenchPagination({ pagination }) {
isDisabled={Boolean(pagination.disabled)}
page={Math.max(0, Number(pagination.page || 0))}
pageSize={pageSize}
pageSizeOptions={pagination.pageSizeOptions}
total={totalRecords}
onPageChange={pagination.onPageChange}
onPageSizeChange={pagination.onPageSizeChange}
/>
);
}
......@@ -458,8 +531,8 @@ export default function RecordWorkbench({
filters = {},
filterSchema = [],
onFiltersChange,
onClearFilters,
onQuery,
onClearFilters,
title,
columns = EMPTY_ARRAY,
columnGroups = EMPTY_ARRAY,
......@@ -482,14 +555,7 @@ export default function RecordWorkbench({
pagination = null,
stickyTable = false,
showExpandColumn = true,
showContentTitle = true,
}) {
useEffect(() => {
if (states?.success) {
toast.success(states.success, { timeout: 3500 });
}
}, [states?.success]);
const tableScrollRef = useRef(null);
const pendingTableScrollRef = useRef(null);
const pendingColumnGroupFocusRef = useRef(null);
......@@ -504,6 +570,11 @@ export default function RecordWorkbench({
const loading = Boolean(states?.loading);
const error = states?.error || '';
const totalRecords = pagination?.totalRecords ?? records.length;
useEffect(() => {
if (states?.success) {
toast.success(states.success, { timeout: 4000 });
}
}, [states?.success]);
const columnGroupMeta = useMemo(
() => buildColumnGroupMeta(columnGroups, columns),
[columnGroups, columns],
......@@ -689,11 +760,11 @@ export default function RecordWorkbench({
openDetail,
};
const visibleToolbarActions = toolbarActions.filter(
(action) => !isActionHidden(action, toolbarContext),
const visibleToolbarActions = orderActionsWithPrimaryLast(
toolbarActions.filter((action) => !isActionHidden(action, toolbarContext)),
);
const visibleBulkActions = bulkActions.filter(
(action) => !isActionHidden(action, toolbarContext),
const visibleBulkActions = orderActionsWithPrimaryLast(
bulkActions.filter((action) => !isActionHidden(action, toolbarContext)),
);
const dataGridRows = [];
records.forEach((record) => {
......@@ -763,7 +834,6 @@ export default function RecordWorkbench({
{
key: '__expand',
title: '',
emptyValue: null,
width: 48,
minWidth: 48,
render: (row) =>
......@@ -843,6 +913,18 @@ export default function RecordWorkbench({
}
}
if (columnIndex === 0 && detailPanel) {
const value = getFieldValue(row.record?.raw || row.record, column.field);
const text = formatFieldValue(value, column.fieldDef) || '-';
return (
<DataTablePrimaryAction
className="h-auto w-full min-w-0 justify-start px-0 text-left"
text={text}
onPress={() => row.rowContext.openDetail(row.record)}
/>
);
}
return defaultRenderCell(row.record, column);
},
};
......@@ -852,7 +934,6 @@ export default function RecordWorkbench({
{
key: 'actions',
title: '操作',
emptyValue: null,
width: resolvedActionColumnWidth,
minWidth: resolvedActionColumnWidth,
pin: 'end',
......@@ -889,13 +970,7 @@ export default function RecordWorkbench({
);
const primaryFilterFields = structuredFilterFields.slice(0, 3);
const expandedFilterFields = structuredFilterFields.slice(3);
const useInlineFilters =
structuredFilterFields.length > 0 && structuredFilterFields.length <= 2;
const primaryFilterGridClass = 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-3';
const inlineFilterGridClass =
structuredFilterFields.length === 1
? 'w-full sm:max-w-md'
: 'w-full md:grid-cols-2';
const renderFilterField = (field) => (
<FilterField key={field.key} label={field.label}>
{field.type === 'select' ? (
......@@ -904,11 +979,8 @@ export default function RecordWorkbench({
className="w-full"
value={filters[field.key] || ''}
onChange={(value) => onFiltersChange?.(field.key, value)}
options={[
{ value: '', label: field.placeholder || `全部${field.label}` },
...(field.options || []),
]}
placeholder={field.placeholder || `全部${field.label}`}
options={field.options || []}
placeholder={getFilterPlaceholder(field)}
variant={filterFieldVariant}
/>
) : field.type === 'date' || field.type === 'datetime' || field.type === 'month' ? (
......@@ -924,15 +996,16 @@ export default function RecordWorkbench({
) : field.type === 'reference' ? (
<ReferenceSearchSelect
fieldDef={field.fieldDef}
placeholder={field.placeholder || `筛选${field.label}`}
placeholder={getFilterPlaceholder(field)}
value={filters[field.key] || ''}
variant={filterFieldVariant}
onChange={(value) => onFiltersChange?.(field.key, value)}
/>
) : field.type === 'user' ? (
<UserSearchSelect
ariaLabel={field.label}
multiple={Boolean(field.fieldDef?.multiple)}
placeholder={field.placeholder || `筛选${field.label}`}
placeholder={getFilterPlaceholder(field)}
value={filters[field.key] || ''}
onChange={(value) => onFiltersChange?.(field.key, value)}
/>
......@@ -942,7 +1015,7 @@ export default function RecordWorkbench({
type={field.type === 'number' ? 'number' : 'text'}
value={filters[field.key] || ''}
onChange={(event) => onFiltersChange?.(field.key, event.target.value)}
placeholder={field.placeholder}
placeholder={getFilterPlaceholder(field)}
variant={filterFieldVariant}
/>
)}
......@@ -972,6 +1045,7 @@ export default function RecordWorkbench({
<Tabs
className="w-fit max-w-full min-w-0"
selectedKey={activeViewName}
variant="primary"
onSelectionChange={(key) => onViewChange?.(String(key))}
>
<Tabs.ListContainer className="w-fit max-w-full min-w-0">
......@@ -992,8 +1066,11 @@ export default function RecordWorkbench({
</div>
) : null}
{structuredFilterFields.length > 0 && !useInlineFilters ? (
<Surface className="p-6" variant="default">
{structuredFilterFields.length > 0 ? (
<Surface
className="rc-filter-surface p-6"
variant="default"
>
<Disclosure
isExpanded={filtersExpanded}
onExpandedChange={setFiltersExpanded}
......@@ -1041,7 +1118,7 @@ export default function RecordWorkbench({
</Button>
) : null}
{expandedFilterFields.length > 0 ? (
<Disclosure.Trigger>
<Disclosure.Trigger className={buttonVariants({ size: 'sm', variant: 'ghost' })}>
<span>{filtersExpanded ? '收起' : '展开'}</span>
<Disclosure.Indicator>
<ChevronDownIcon aria-hidden="true" size={14} />
......@@ -1069,48 +1146,8 @@ export default function RecordWorkbench({
) : null}
<section className={tableSectionClass}>
{showContentTitle ? (
<div className="flex min-w-0 items-center">
<h2 className="text-base font-medium text-foreground">{title}</h2>
</div>
) : null}
{useInlineFilters ? (
<form
className="flex min-w-0 flex-wrap items-end gap-2"
onSubmit={(event) => {
event.preventDefault();
onQuery?.();
}}
>
<div
className={clsx(
'grid min-w-0 flex-1 gap-3',
inlineFilterGridClass,
)}
>
{structuredFilterFields.map(renderFilterField)}
</div>
{onClearFilters ? (
<Tooltip delay={0}>
<Button
aria-label="重置筛选"
isDisabled={loading}
isIconOnly
size="sm"
variant="secondary"
type="button"
onPress={onClearFilters}
>
<ArrowPathIcon size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip>
) : null}
</form>
) : null}
<div className="rc-page-toolbar">
<div className="flex min-w-0 basis-full flex-wrap items-center gap-2 sm:basis-auto sm:flex-1">
<div className="rc-page-toolbar rc-page-toolbar-responsive">
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-2">
{toolbarSearchField ? (
<div className="w-full min-w-0 flex-none sm:w-80 lg:w-96">
<SearchField
......@@ -1154,7 +1191,7 @@ export default function RecordWorkbench({
</div>
{visibleToolbarActions.length > 0 ? (
<div className="flex w-full flex-wrap items-center justify-end gap-2 sm:w-auto">
<div className="flex flex-wrap items-center justify-end gap-2">
{visibleToolbarActions.map((action) => {
const disabled = isActionDisabled(action, toolbarContext);
return (
......@@ -1182,6 +1219,7 @@ export default function RecordWorkbench({
<div ref={tableScrollRef} className={tableContentClass}>
<DataTable
ariaLabel={title || '业务数据列表'}
className={stickyTable ? 'rc-data-table-sticky' : ''}
columns={dataGridColumns}
rows={dataGridRows}
isLoading={loading}
......
......@@ -3,7 +3,6 @@ export const APPLICATION_NAME = 'integrity_risk_prevention_new';
export const APPLICATION_BASE_PATH = `/${APPLICATION_NAME}/`;
export const AUTHORIZATION_APPLICATION_NAME = APPLICATION_NAME;
export const AUDIT_MATERIAL_SOURCE_APP = 'integrity_risk_prevention';
export const THEME_STORAGE_KEY = 'xinyuan_color_theme';
export const ROUTE_TITLES = Object.freeze({
'/risk_prevention_control_measures_table': '廉洁风险防控表 (过程表)',
......
......@@ -7,6 +7,15 @@
background: var(--rc-bg-canvas);
}
/* Shared overlay width ladder used by detail and form modals. */
.rc-dialog-panel {
width: min(100%, calc(100vw - 2rem));
}
.rc-dialog-panel--4xl {
max-width: 68rem;
}
.rc-sidebar,
.rc-mobile-sidebar {
display: flex;
......@@ -89,7 +98,7 @@
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: var(--rc-font-semibold);
font-weight: 650;
line-height: 1.2rem;
font-synthesis: none;
text-rendering: optimizeLegibility;
......@@ -435,6 +444,41 @@
gap: var(--rc-page-module-gap);
}
.rc-section-title {
margin: 0;
color: var(--rc-text-primary);
font-size: 1.125rem;
font-weight: var(--rc-font-bold);
line-height: 1.5rem;
letter-spacing: 0;
}
.rc-module-title {
margin: 0;
color: var(--rc-text-primary);
font-size: 1rem;
font-weight: var(--rc-font-semibold);
line-height: 1.5rem;
letter-spacing: 0;
}
.rc-modal-header-actions {
align-self: flex-start;
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
justify-content: flex-end;
gap: 0.5rem;
margin-inline-start: auto;
max-width: 100%;
}
@media (max-width: 40rem) {
.rc-modal-header-actions {
flex-basis: 100%;
}
}
.rc-page-grid {
min-width: 0;
gap: var(--rc-page-module-gap);
......@@ -453,7 +497,7 @@
grid-column: 1 / -1;
}
/* Recharts keeps keyboard navigation while pointer focus stays visually quiet. */
/* Charts keep keyboard navigation while pointer focus stays visually quiet. */
.rc-chart-interaction:focus:not(:focus-visible),
.rc-chart-interaction :focus:not(:focus-visible) {
outline: none;
......@@ -465,6 +509,10 @@
outline-offset: 2px;
}
.rc-echarts-tooltip {
position: fixed !important;
}
.rc-kpi-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
......@@ -501,6 +549,41 @@
}
}
/* Shared HeroUI/HeroUI Pro business-card contract. */
.rc-card-surface {
border: 1px solid var(--rc-border-default);
background-color: var(--rc-bg-surface);
color: var(--rc-text-primary);
box-shadow: var(--rc-shadow-card);
}
.rc-filter-surface {
border-radius: var(--rc-radius-overlay);
}
.rc-dashboard-card {
border-radius: 1.5rem;
}
.rc-workspace-module {
border-radius: 1.5rem;
border: 1px solid var(--border);
background: transparent;
box-shadow: none;
}
.rc-empty-state-message {
color: var(--rc-text-empty);
font-size: var(--text-sm);
font-weight: var(--font-weight-normal);
}
.rc-empty-field-text {
color: var(--rc-text-empty);
font-size: var(--text-sm);
font-weight: var(--font-weight-normal);
}
/* HeroUI Pro KPI.Icon keeps its compound role; this class supplies Xinyuan tones. */
.rc-metric-icon-surface {
--rc-metric-icon-color: var(--rc-metric-tone-neutral);
......@@ -638,38 +721,122 @@
align-items: stretch;
}
/* Shared modal width ladder. HeroUI owns the overlay surface and radius. */
.rc-dialog-panel {
width: min(calc(100vw - 5rem), var(--rc-dialog-width, 40rem));
max-width: min(calc(100vw - 5rem), var(--rc-dialog-width, 40rem));
.rc-list-stack,
.rc-list-data-stack {
gap: var(--rc-list-stack-gap);
}
/* Keep long data sets inside the table while the column headings remain visible. */
.rc-data-table-sticky .data-table-scroll {
position: relative;
max-height: min(60dvh, 42rem);
overflow-x: auto;
overflow-y: auto;
/* Keep horizontal drag inside the grid, but let vertical wheel/touch
gestures continue into the page once the first/last row is reached. */
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
}
.rc-dialog-panel--xs { --rc-dialog-width: 20rem; }
.rc-dialog-panel--sm { --rc-dialog-width: 24rem; }
.rc-dialog-panel--md { --rc-dialog-width: 30rem; }
.rc-dialog-panel--lg { --rc-dialog-width: 40rem; }
.rc-dialog-panel--xl { --rc-dialog-width: 46rem; }
.rc-dialog-panel--2xl { --rc-dialog-width: 52.5rem; }
.rc-dialog-panel--3xl { --rc-dialog-width: 60rem; }
.rc-dialog-panel--4xl { --rc-dialog-width: 68rem; }
.rc-dialog-panel--5xl { --rc-dialog-width: 76rem; }
/* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the
outer scroll container as their containing block instead of this wrapper. */
.rc-data-table-sticky .table__resizable-container {
overflow: visible;
}
.rc-application-switcher-dialog {
width: min(46rem, calc(100vw - 2rem));
max-width: min(46rem, calc(100vw - 2rem));
max-height: min(42rem, calc(100dvh - 2rem));
.rc-data-table-sticky [data-slot='table-header'] .table__column {
position: sticky;
top: 0;
inset-block-start: 0;
z-index: 4;
display: table-cell;
color: var(--muted);
background: var(--surface-secondary);
box-shadow: inset 0 -1px 0 var(--separator);
}
@media (max-width: 640px) {
.rc-dialog-panel {
width: calc(100vw - 2rem);
max-width: calc(100vw - 2rem);
.rc-data-table-sticky [data-slot='table-header'] .table__column.bg-accent-soft {
background: var(--accent-soft);
color: var(--accent-soft-foreground);
}
/* The leading pinned column must sit above scrolling body cells. Keep its
surface opaque so text from adjacent columns cannot bleed through while
horizontal or vertical scrolling. */
.rc-data-table-sticky [data-pinned='start'] {
position: sticky;
z-index: 6;
background: var(--surface);
}
.rc-data-table-sticky [data-slot='table-header'] [data-pinned='start'] {
z-index: 8;
background: var(--surface-secondary);
}
/* Mirror HeroUI's row states on the opaque pinned surface so the leading
column still responds to hover and selection instead of looking detached. */
@media (hover: hover) {
.rc-data-table-sticky [data-slot='data-grid']
.table__row:hover
[data-pinned='start'].table__cell,
.rc-data-table-sticky [data-slot='data-grid']
.table__row[data-hovered='true']
[data-pinned='start'].table__cell {
/* Keep the pinned cell opaque; transparent hover fills let scrolled
sibling-cell text show through the fixed column. */
background-color: var(--color-surface-secondary);
}
@supports (color: color-mix(in oklab, white, black)) {
.rc-data-table-sticky [data-slot='data-grid']
.table__row:hover
[data-pinned='start'].table__cell,
.rc-data-table-sticky [data-slot='data-grid']
.table__row[data-hovered='true']
[data-pinned='start'].table__cell {
background-color: color-mix(
in oklab,
var(--color-surface) 40%,
var(--color-surface-secondary)
);
}
}
}
.rc-list-stack,
.rc-list-data-stack {
gap: var(--rc-list-stack-gap);
.rc-data-table-sticky [data-slot='data-grid']
.table__row[data-selected='true']
[data-pinned='start'].table__cell {
background-color: var(--color-surface-secondary);
}
@supports (color: color-mix(in oklab, white, black)) {
.rc-data-table-sticky [data-slot='data-grid']
.table__row[data-selected='true']
[data-pinned='start'].table__cell {
background-color: color-mix(
in oklab,
var(--color-surface) 10%,
var(--color-surface-secondary)
);
}
}
@media (max-width: 48rem) {
.rc-data-table-sticky .data-table-scroll {
max-height: min(54dvh, 32rem);
}
}
@media (min-width: 769px) {
.action-bar:has(.rc-data-grid-action-bar) {
inset-inline: 0;
}
body:has(.rc-app-shell[data-state='expanded'])
.action-bar:has(.rc-data-grid-action-bar) {
inset-inline-start: var(--rc-sidebar-width);
}
}
@media (min-width: 80rem) {
......@@ -718,9 +885,6 @@
@media (max-width: 1023px) {
.rc-app-main {
--rc-shell-inline-gutter: var(--rc-space-8);
--rc-navbar-inline-padding: var(--rc-space-8);
--rc-page-inline-start: var(--rc-space-8);
--rc-page-inline-end: var(--rc-space-8);
}
.rc-sidebar {
......@@ -742,9 +906,6 @@
@media (max-width: 640px) {
.rc-app-main {
--rc-shell-inline-gutter: var(--rc-space-4);
--rc-navbar-inline-padding: var(--rc-space-4);
--rc-page-inline-start: var(--rc-space-4);
--rc-page-inline-end: var(--rc-space-4);
--rc-page-padding-block-start: var(--rc-space-4);
--rc-page-padding-block-end: var(--rc-space-6);
--rc-page-section-gap: var(--rc-space-6);
......@@ -811,7 +972,7 @@
min-width: 0;
color: var(--foreground);
font-size: 0.95rem;
font-weight: var(--rc-font-semibold);
font-weight: 650;
line-height: 1.2rem;
}
......@@ -885,6 +1046,23 @@
font-weight: var(--rc-font-semibold);
}
.rc-application-identity-well {
display: grid;
width: 2.25rem;
height: 2.25rem;
flex: none;
place-items: center;
overflow: hidden;
border-radius: var(--rc-radius-control);
}
.rc-application-identity-avatar {
width: 100%;
height: 100%;
border-radius: var(--rc-radius-control);
background: transparent;
}
.rc-app-shell .sidebar__content,
.rc-mobile-sidebar .sidebar__content {
min-height: 0;
......@@ -1184,7 +1362,7 @@
overflow: hidden;
color: var(--foreground);
font-size: 0.95rem;
font-weight: var(--rc-font-semibold);
font-weight: 650;
line-height: 1.2rem;
letter-spacing: 0;
font-synthesis: none;
......@@ -1208,12 +1386,12 @@
place-items: center;
padding: 0;
border-radius: 50%;
color: var(--foreground);
color: var(--rc-sidebar-foreground);
background-color: transparent;
line-height: 1;
--button-bg-hover: var(--accent-soft);
--button-bg-pressed: var(--accent-soft);
--button-fg: var(--foreground);
--button-bg-hover: var(--rc-sidebar-item-selected);
--button-bg-pressed: var(--rc-sidebar-item-selected);
--button-fg: var(--rc-sidebar-item-selected-foreground);
}
.app-sidebar-app-trigger .anticon {
......@@ -1233,16 +1411,16 @@
@media (hover: hover) {
.app-sidebar-app-trigger.button:hover,
.app-sidebar-app-trigger.button[data-hovered='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
color: var(--rc-sidebar-item-selected-foreground);
background-color: var(--rc-sidebar-item-selected);
}
}
.app-sidebar-app-trigger.button:active,
.app-sidebar-app-trigger.button[data-pressed='true'],
.app-sidebar-app-trigger.button[aria-expanded='true'] {
color: var(--foreground);
background-color: var(--accent-soft);
color: var(--rc-sidebar-item-selected-foreground);
background-color: var(--rc-sidebar-item-selected);
}
.app-sidebar-app-dialog {
......@@ -1426,3 +1604,89 @@
border-right: 0;
}
}
/* Fixed split-shell theme: dark navigation, permanently light content canvas. */
.template-dashboard-shell .app-sidebar-glass,
.rc-mobile-sidebar.app-sidebar-glass {
color: var(--rc-sidebar-foreground);
border-color: var(--rc-sidebar-border) !important;
background-color: var(--rc-sidebar-background) !important;
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content {
background: var(--rc-sidebar-item-selected);
}
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.template-dashboard-shell
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content *,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-current='true']
.sidebar__menu-item-content * {
color: var(--rc-sidebar-item-selected-foreground) !important;
}
.template-dashboard-shell .sidebar__menu-item:hover .sidebar__menu-item-content,
.template-dashboard-shell
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.rc-mobile-sidebar .sidebar__menu-item:hover .sidebar__menu-item-content,
.rc-mobile-sidebar
.sidebar__menu-item[data-hovered='true']
.sidebar__menu-item-content,
.app-sidebar-user-trigger.dropdown__trigger:hover,
.app-sidebar-user-trigger.dropdown__trigger[data-hovered='true'] {
background: var(--rc-sidebar-item-hover);
}
.app-sidebar-user-trigger.dropdown__trigger[aria-expanded='true'],
.app-sidebar-app-trigger.button[data-pressed='true'],
.app-sidebar-app-trigger.button[aria-expanded='true'] {
background: var(--rc-sidebar-item-selected);
}
.app-sidebar-user-avatar,
.app-sidebar-app-trigger.button,
.app-sidebar-user-trigger.dropdown__trigger {
color: var(--rc-sidebar-foreground);
background: transparent;
}
.app-sidebar-user-avatar {
background: var(--rc-sidebar-item-hover);
}
.template-dashboard-shell .sidebar__group-label,
.rc-mobile-sidebar .sidebar__group-label,
.template-dashboard-shell .sidebar__menu-icon,
.rc-mobile-sidebar .sidebar__menu-icon,
.app-sidebar-user-description {
color: var(--rc-sidebar-muted);
}
.template-dashboard-shell .sidebar__menu-label,
.rc-mobile-sidebar .sidebar__menu-label,
.app-sidebar-title,
.app-sidebar-user-name,
.app-sidebar-app-trigger.button {
color: var(--rc-sidebar-foreground);
}
.app-sidebar-user-trigger.dropdown__trigger:focus-visible,
.app-sidebar-app-trigger.button:focus-visible,
.template-dashboard-shell .sidebar__menu-item:focus-visible,
.rc-mobile-sidebar .sidebar__menu-item:focus-visible {
outline: 2px solid var(--rc-sidebar-focus);
outline-offset: 2px;
}
......@@ -11,38 +11,57 @@
[data-theme="light"],
[data-theme="default"] {
/* 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);
--background: oklch(97.02% 0.0045 27.03);
--border: oklch(90.00% 0.0045 27.03);
--danger: oklch(45.32% 0.1661 27.03);
--primary: #DA241A;
--primary-hover: #C91F17;
--primary-pressed: #B51B14;
--primary-foreground: #FFFFFF;
--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.0000 29.20);
--border: oklch(90.00% 0.0000 29.20);
--danger: #B51B14;
--danger-hover: #941711;
--danger-foreground: oklch(99.11% 0 0);
/* HeroUI control aliases: primary = L1, secondary = L2. */
--default: var(--surface-secondary);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--default: oklch(94.00% 0.0000 29.20);
--default-foreground: oklch(21.03% 0.0059 29.20);
--field-background: oklch(100.00% 0.0000 29.20);
--field-border: transparent;
--field-foreground: var(--surface-foreground);
--field-placeholder: oklch(55.17% 0.0090 27.03);
--focus: oklch(45.32% 0.1661 27.03);
--foreground: oklch(21.03% 0.0045 27.03);
--muted: oklch(55.17% 0.0090 27.03);
--overlay: var(--surface);
--overlay-foreground: var(--surface-foreground);
--scrollbar: oklch(87.10% 0.0045 27.03);
--segment: oklch(100.00% 0.0045 27.03);
--segment-foreground: oklch(21.03% 0.0045 27.03);
--separator: oklch(92.00% 0.0045 27.03);
--success: oklch(73.29% 0.1952 123.59);
--success-foreground: oklch(21.03% 0.0059 123.59);
--surface: oklch(100.00% 0.0022 27.03);
--surface-foreground: oklch(21.03% 0.0045 27.03);
--surface-secondary: oklch(95.24% 0.0036 27.03);
--surface-secondary-foreground: oklch(21.03% 0.0045 27.03);
--surface-tertiary: oklch(93.73% 0.0036 27.03);
--surface-tertiary-foreground: oklch(21.03% 0.0045 27.03);
--warning: oklch(78.19% 0.1599 45.11);
--warning-foreground: oklch(21.03% 0.0059 45.11);
--field-foreground: oklch(21.03% 0.0000 29.20);
--field-placeholder: oklch(55.17% 0.0000 29.20);
--focus: #DA241A;
--foreground: oklch(21.03% 0.0000 29.20);
--muted: oklch(55.17% 0.0000 29.20);
--overlay: oklch(100.00% 0.0000 29.20);
--overlay-foreground: oklch(21.03% 0.0000 29.20);
--scrollbar: oklch(87.10% 0.0000 29.20);
--segment: oklch(100.00% 0.0000 29.20);
--segment-foreground: oklch(21.03% 0.0000 29.20);
--separator: oklch(92.00% 0.0000 29.20);
/* Success stays readable without introducing a green hue into the brand system. */
--success: #61758F;
--success-foreground: #FFFFFF;
--surface: oklch(100.00% 0.0000 29.20);
--surface-foreground: oklch(21.03% 0.0000 29.20);
--surface-secondary: oklch(95.24% 0.0000 29.20);
--surface-secondary-foreground: oklch(21.03% 0.0000 29.20);
--surface-tertiary: oklch(93.73% 0.0000 29.20);
--surface-tertiary-foreground: oklch(21.03% 0.0000 29.20);
--warning: oklch(78.19% 0.1585 45.37);
--warning-foreground: oklch(21.03% 0.0059 45.37);
/* Border Radius */
--radius: 0.5rem;
......@@ -51,42 +70,107 @@
/* Font Family */
/* Make sure to load Inter font in your app */
--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.020 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.20);
/*
* 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: #ffffff;
--xinyuan-chart-tooltip-border: #dedede;
--xinyuan-chart-tooltip-foreground: #222222;
--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,
[data-theme="dark"] {
color-scheme: dark;
/* 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);
--background: oklch(12.00% 0.0045 27.03);
--border: oklch(28.00% 0.0045 27.03);
--danger: oklch(62.00% 0.1600 27.03);
--primary: #DA241A;
--primary-hover: #C91F17;
--primary-pressed: #B51B14;
--primary-foreground: #FFFFFF;
--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.00% 0.0000 29.20);
--border: oklch(28.00% 0.0000 29.20);
--danger: #B51B14;
--danger-hover: #941711;
--danger-foreground: oklch(99.11% 0 0);
/* HeroUI control aliases: primary = L1, secondary = L2. */
--default: var(--surface-secondary);
--default-foreground: var(--surface-secondary-foreground);
--field-background: var(--surface);
--default: oklch(27.40% 0.0000 29.20);
--default-foreground: oklch(99.11% 0 0);
--field-background: oklch(21.03% 0.0000 29.20);
--field-border: transparent;
--field-foreground: var(--surface-foreground);
--field-placeholder: oklch(70.50% 0.0090 27.03);
--focus: oklch(45.32% 0.1661 27.03);
--foreground: oklch(99.11% 0.0045 27.03);
--muted: oklch(70.50% 0.0090 27.03);
--overlay: var(--surface);
--overlay-foreground: var(--surface-foreground);
--scrollbar: oklch(70.50% 0.0045 27.03);
--segment: oklch(39.64% 0.0045 27.03);
--segment-foreground: oklch(99.11% 0.0045 27.03);
--separator: oklch(25.00% 0.0045 27.03);
--success: oklch(73.29% 0.1952 123.59);
--success-foreground: oklch(21.03% 0.0059 123.59);
--surface: oklch(21.03% 0.0090 27.03);
--surface-foreground: oklch(99.11% 0.0045 27.03);
--surface-secondary: oklch(25.70% 0.0067 27.03);
--surface-secondary-foreground: oklch(99.11% 0.0045 27.03);
--surface-tertiary: oklch(27.21% 0.0067 27.03);
--surface-tertiary-foreground: oklch(99.11% 0.0045 27.03);
--warning: oklch(82.03% 0.1400 49.12);
--warning-foreground: oklch(21.03% 0.0059 49.12);
--field-foreground: oklch(99.11% 0.0000 29.20);
--field-placeholder: oklch(70.50% 0.0000 29.20);
--focus: #DA241A;
--foreground: oklch(99.11% 0.0000 29.20);
--muted: oklch(70.50% 0.0000 29.20);
--overlay: oklch(21.03% 0.0000 29.20);
--overlay-foreground: oklch(99.11% 0.0000 29.20);
--scrollbar: oklch(70.50% 0.0000 29.20);
--segment: oklch(39.64% 0.0000 29.20);
--segment-foreground: oklch(99.11% 0.0000 29.20);
--separator: oklch(25.00% 0.0000 29.20);
--success: #8A9AAF;
--success-foreground: #141820;
--surface: oklch(21.03% 0.0000 29.20);
--surface-foreground: oklch(99.11% 0.0000 29.20);
--surface-secondary: oklch(25.70% 0.0000 29.20);
--surface-secondary-foreground: oklch(99.11% 0.0000 29.20);
--surface-tertiary: oklch(27.21% 0.0000 29.20);
--surface-tertiary-foreground: oklch(99.11% 0.0000 29.20);
--warning: oklch(82.03% 0.1388 49.38);
--warning-foreground: oklch(21.03% 0.0059 49.38);
}
......@@ -2,11 +2,14 @@
.light,
.default,
[data-theme='light'],
[data-theme='default'],
.dark,
[data-theme='dark'] {
[data-theme='default'] {
/* Xinyuan aliases and extension tokens. HeroUI core tokens are owned by theme.css. */
--rc-brand-primary: var(--accent);
--rc-brand-hover: var(--accent-hover);
--rc-brand-pressed: var(--accent-pressed);
--rc-brand-disabled: color-mix(in oklab, var(--accent) 38%, var(--surface-secondary));
--rc-brand-focus: var(--focus);
--rc-brand-on-primary: var(--accent-foreground);
--rc-brand-light: color-mix(in oklab, var(--accent) 84%, white);
--rc-brand-dark: color-mix(in oklab, var(--accent) 82%, black);
......@@ -27,6 +30,17 @@
var(--background)
);
--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-medium: 500;
......@@ -35,23 +49,6 @@
--rc-text-xs: 0.75rem;
--rc-text-sm: 0.875rem;
/* Shared DataGrid typography. Pages choose field meaning; the wrapper owns type. */
--rc-data-grid-header-size: 0.75rem;
--rc-data-grid-header-weight: 500;
--rc-data-grid-header-color: var(--rc-text-muted);
--rc-data-grid-body-size: 0.875rem;
--rc-data-grid-body-weight: 400;
--rc-data-grid-body-color: var(--rc-text-secondary);
--rc-data-grid-primary-size: 0.875rem;
--rc-data-grid-primary-weight: 550;
--rc-data-grid-primary-color: var(--rc-text-primary);
--rc-data-grid-secondary-size: 0.8125rem;
--rc-data-grid-secondary-weight: 400;
--rc-data-grid-secondary-color: #7a8596;
--rc-data-grid-chip-size: 0.75rem;
--rc-data-grid-chip-weight: 500;
--rc-data-grid-chip-color: var(--rc-text-secondary);
--rc-space-1: 0.25rem;
--rc-space-2: 0.5rem;
--rc-space-3: 0.75rem;
......@@ -62,6 +59,7 @@
/* Page geometry only. Component appearance remains owned by HeroUI. */
--rc-shell-inline-gutter: 3rem;
--rc-sidebar-width: 15rem;
--rc-navbar-inline-padding: var(--rc-shell-inline-gutter);
--rc-page-inline-start: var(--rc-shell-inline-gutter);
--rc-page-inline-end: var(--rc-shell-inline-gutter);
......@@ -99,14 +97,14 @@
0 4px 12px rgb(15 23 42 / 2%);
/* 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-success: oklch(54% 0.13 145);
--rc-metric-tone-teal: oklch(55% 0.1 205);
--rc-metric-tone-danger: oklch(52% 0.17 25);
--rc-metric-tone-warning: oklch(62% 0.12 75);
--rc-metric-tone-violet: oklch(56% 0.12 300);
--rc-metric-tone-indigo: oklch(53% 0.13 275);
--rc-metric-tone-coral: oklch(60% 0.12 48);
--rc-metric-tone-info: #4F6380;
--rc-metric-tone-success: #61758F;
--rc-metric-tone-teal: #74879A;
--rc-metric-tone-danger: #DA241A;
--rc-metric-tone-warning: #B87958;
--rc-metric-tone-violet: #6F6674;
--rc-metric-tone-indigo: #807783;
--rc-metric-tone-coral: #C95A52;
--rc-metric-tone-neutral: var(--muted);
--rc-metric-tone-info-soft: color-mix(
in oklab,
......@@ -155,41 +153,29 @@
);
/* 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-2: oklch(56% 0.135 255);
--rc-chart-3: oklch(64% 0.11 220);
--rc-chart-4: oklch(56% 0.115 280);
--rc-chart-5: oklch(60% 0.095 200);
--rc-chart-6: oklch(70% 0.12 80);
--rc-chart-7: oklch(60% 0.1 295);
--rc-chart-8: oklch(57% 0.035 250);
--rc-chart-1: var(--xinyuan-chart-1);
--rc-chart-2: var(--xinyuan-chart-2);
--rc-chart-3: var(--xinyuan-chart-3);
--rc-chart-4: var(--xinyuan-chart-4);
--rc-chart-5: var(--xinyuan-chart-5);
--rc-chart-6: var(--xinyuan-chart-6);
--rc-chart-7: var(--xinyuan-chart-7);
--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);
/* Stable semantic meanings. The palette is intentionally free of green and neon hues. */
--rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: oklch(47% 0.145 27.03);
--rc-chart-warning: var(--rc-chart-6);
--rc-chart-positive: var(--rc-chart-2);
--rc-chart-comparison: var(--rc-chart-4);
--rc-chart-risk: var(--rc-chart-1);
--rc-chart-warning: var(--rc-chart-3);
--rc-chart-positive: var(--rc-chart-11);
--rc-chart-comparison: var(--rc-chart-5);
--rc-chart-neutral: var(--rc-chart-8);
--rc-chart-legend-font-size: 0.75rem;
--rc-chart-legend-line-height: 1rem;
}
.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-data-grid-secondary-color: #8b8b95;
--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%);
--rc-chart-legend-font-size: var(--xinyuan-chart-legend-font-size);
--rc-chart-legend-line-height: var(--xinyuan-chart-legend-line-height);
}
:root {
--pm-brand: var(--rc-brand-primary);
--pm-brand-strong: var(--rc-brand-dark, #7e1515);
--pm-brand-strong: var(--rc-brand-pressed, #b51b14);
--pm-surface: var(--rc-bg-surface);
--pm-bg: var(--rc-bg-canvas);
--pm-text: var(--rc-text-primary);
......
......@@ -14,6 +14,7 @@ export default function ReferenceSearchSelect({
value,
onChange,
placeholder,
variant = 'secondary',
}) {
const [keyword, setKeyword] = useState('');
const [options, setOptions] = useState([]);
......@@ -112,6 +113,7 @@ export default function ReferenceSearchSelect({
selectionMode={multiple ? 'multiple' : 'single'}
shouldFilter={false}
valueText={valueText || placeholder || '搜索引用记录标题'}
variant={variant}
onInputChange={setKeyword}
onSelectionChange={handleSelectionChange}
renderOption={(option) => (
......
import clsx from 'clsx';
import { EmptyState } from '@heroui-pro/react/empty-state';
import { Surface } from '@heroui/react';
import TailwindEditableField from './TailwindEditableField';
import {
buildViewColumns,
......@@ -10,20 +9,6 @@ import {
isFieldDisabledByView,
} from '@/utils/subjectView';
const FULL_WIDTH_FIELD_TYPES = new Set([
'long_text',
'rich_text',
'object',
'form',
'subject',
'file',
'reference',
'document',
'view',
'template',
'task',
]);
export default function SubjectViewContent({
view,
subject,
......@@ -57,13 +42,11 @@ export default function SubjectViewContent({
const fields = (
<section
className="grid min-w-0 grid-cols-1 gap-x-8 gap-y-2 md:grid-cols-2"
className="grid min-w-0 grid-cols-1 gap-y-2"
>
{contentFields.map((field) => {
const value = getFieldValue(editableRecord, field.field);
const fieldType = field.fieldDef?.type || 'text';
const fieldLabel = getFieldDisplayLabel(field.fieldDef, field.field);
const isFullWidth = FULL_WIDTH_FIELD_TYPES.has(fieldType);
const fieldDisabled = isFieldDisabled
? isFieldDisabled(field.field, view)
: isFieldDisabledByView(field.field, view);
......@@ -74,13 +57,12 @@ export default function SubjectViewContent({
className={clsx(
'min-w-0',
minimal ? 'py-1' : 'py-3',
isFullWidth && 'md:col-span-2',
)}
>
<div className={minimal ? 'sr-only' : 'text-sm font-medium text-muted'}>
<div className={minimal ? 'sr-only' : 'text-base font-normal text-muted'}>
{fieldLabel}
</div>
<div className={minimal ? undefined : 'mt-1.5 min-w-0'}>
<div className={minimal ? undefined : 'mt-1.5 min-w-0 text-base font-normal'}>
<TailwindEditableField
value={value}
fieldDef={field.fieldDef}
......@@ -109,13 +91,5 @@ export default function SubjectViewContent({
</section>
);
if (mode === 'view' && !minimal) {
return (
<Surface className="rounded-2xl px-5 py-2 sm:px-6 sm:py-3" variant="secondary">
{fields}
</Surface>
);
}
return fields;
}
......@@ -49,7 +49,6 @@ function getClearedValue(fieldDef) {
function Pill({ children, tone = 'neutral' }) {
const semanticTone = {
amber: 'warning',
emerald: 'success',
rose: 'danger',
zinc: 'neutral',
}[tone] || tone;
......@@ -114,7 +113,7 @@ function FieldValue({ value, fieldDef }) {
return (
<div className="flex flex-wrap gap-1.5">
{toArray(value).map((item, index) => (
<Pill key={`${normalizeUserLabel(item)}-${index}`} tone="success">
<Pill key={`${normalizeUserLabel(item)}-${index}`} tone="info">
{normalizeUserLabel(item) || '-'}
</Pill>
))}
......
......@@ -359,7 +359,7 @@ const RecordDetailDrawer = (props, ref) => {
variant="blur"
>
<Modal.Container placement="center" scroll="inside" size={isFullscreen ? 'full' : 'lg'}>
<Modal.Dialog className={isFullscreen ? undefined : 'rc-dialog-panel rc-dialog-panel--4xl p-7'}>
<Modal.Dialog className={isFullscreen ? 'bg-background' : 'rc-dialog-panel rc-dialog-panel--4xl bg-background p-7'}>
<div className="absolute right-7 top-7 z-10 flex items-center gap-2">
{!minimalContent ? (
<Tooltip>
......@@ -387,7 +387,9 @@ const RecordDetailDrawer = (props, ref) => {
) : null}
</div>
<Modal.Header className="pr-16">
<Modal.Header className="shrink-0 pr-16">
<div className="flex w-full min-w-0 flex-wrap items-start gap-3">
<div className="min-w-0 flex-1">
<Modal.Heading>{title || '记录详情'}</Modal.Heading>
{!hideRecordTitle ? (
<Tooltip delay={0}>
......@@ -399,12 +401,10 @@ const RecordDetailDrawer = (props, ref) => {
) : (
<Description>查看并维护当前记录信息。</Description>
)}
</Modal.Header>
{detailViews.length > 1 ? (
<Tabs
aria-label="详情视图"
className="w-fit max-w-full min-w-0"
className="mt-3 w-fit max-w-full min-w-0"
selectedKey={activeView?.name}
variant="secondary"
onSelectionChange={setActiveTabKey}
......@@ -425,6 +425,9 @@ const RecordDetailDrawer = (props, ref) => {
</Tabs.ListContainer>
</Tabs>
) : null}
</div>
</div>
</Modal.Header>
<Modal.Body>
{loading ? (
......
import { getApplications } from '@/services/DataService';
import { ExclamationTriangleIcon } from '@/components/AppIcons';
import ApplicationIdentityWell from '@/components/ApplicationIdentityWell';
import xinyuanLogo from '@/assets/xy-logo-red.png';
import {
APPLICATION_BASE_PATH,
APPLICATION_DISPLAY_NAME,
APPLICATION_NAME,
} from '@/config/application';
import { getApplications } from '@/services/DataService';
import { EmptyStateSurface } from '@/components/DataTable';
import { ExclamationTriangleIcon, MagnifyingGlassIcon } from '@/components/AppIcons';
import { EmptyState } from '@heroui-pro/react/empty-state';
import { ItemCard } from '@heroui-pro/react/item-card';
import { ItemCardGroup } from '@heroui-pro/react/item-card-group';
import {
Alert,
Card,
Avatar,
Chip,
Description,
Modal,
ScrollShadow,
SearchField,
Skeleton,
Surface,
} from '@heroui/react';
import { useEffect, useMemo, useState } from 'react';
const CURRENT_APPLICATION_NAME = APPLICATION_NAME;
function unwrapApplications(response) {
const candidates = [
response,
response?.data,
response?.applications,
response?.apps,
response?.records,
response?.rows,
response?.list,
response?.result,
];
return candidates.find(Array.isArray) || [];
}
function normalizeLabelList(items) {
if (!Array.isArray(items)) {
return [];
......@@ -43,7 +65,7 @@ function resolveApplicationUrl(application) {
function normalizeApplication(application, fallback = {}) {
const name = application?.name || fallback?.name || '';
const current = name === APPLICATION_NAME;
const current = name === CURRENT_APPLICATION_NAME;
return {
name,
......@@ -56,10 +78,8 @@ function normalizeApplication(application, fallback = {}) {
fallback?.title ||
application?.title ||
'进入对应应用继续处理业务',
logo: current ? xinyuanLogo : application?.logo || fallback?.logo || null,
url: resolveApplicationUrl({
name,
}),
logo: xinyuanLogo,
url: resolveApplicationUrl({ name }),
access: application?.access || fallback?.access || '',
status: application?.status || fallback?.status || '',
type: application?.type || fallback?.type || '',
......@@ -71,14 +91,7 @@ function normalizeApplication(application, fallback = {}) {
function mergeApplications(fetchedApplications, fallbackApplications) {
const fallbackMap = new Map(
[
...fallbackApplications,
{
name: APPLICATION_NAME,
title: APPLICATION_DISPLAY_NAME,
logo: xinyuanLogo,
},
].map((application) => [application.name, application]),
fallbackApplications.map((application) => [application.name, application]),
);
const normalizedFetched = fetchedApplications
......@@ -91,7 +104,7 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
normalizedFetched.map((application) => [application.name, application]),
);
fallbackMap.forEach((application) => {
fallbackApplications.forEach((application) => {
if (!application?.name || mergedMap.has(application.name)) {
return;
}
......@@ -99,6 +112,15 @@ function mergeApplications(fetchedApplications, fallbackApplications) {
mergedMap.set(application.name, normalizeApplication(application, application));
});
const currentApplication = mergedMap.get(CURRENT_APPLICATION_NAME);
mergedMap.set(
CURRENT_APPLICATION_NAME,
normalizeApplication(
{ name: CURRENT_APPLICATION_NAME },
currentApplication || { title: APPLICATION_DISPLAY_NAME },
),
);
return Array.from(mergedMap.values()).sort((left, right) => {
const orderDifference = Number(left.order || 0) - Number(right.order || 0);
if (orderDifference !== 0) {
......@@ -161,7 +183,7 @@ function buildApplicationSections(applications, applicationGroups) {
}
function isCurrentApplication(application) {
if (application?.name === APPLICATION_NAME) {
if (application?.name === CURRENT_APPLICATION_NAME) {
return true;
}
......@@ -191,49 +213,76 @@ function isCurrentApplication(application) {
function ApplicationCard({ application, current }) {
const target = application?.url?.startsWith('http') ? '_blank' : undefined;
const initial = application.title.trim().slice(0, 1).toUpperCase() || 'A';
const description = application.description?.trim();
const hasDistinctDescription =
description && description !== application.title.trim();
return (
<Card className="h-full" variant="secondary">
<Card.Content>
<a
<ItemCard
aria-current={current ? 'page' : undefined}
className="h-full min-w-0 no-underline"
href={application.url}
target={target}
rel={target ? 'noreferrer' : undefined}
aria-current={current ? 'page' : undefined}
className="flex min-w-0 items-center gap-3 no-underline"
render={(props) => <a {...props} />}
target={target}
variant={current ? 'outline' : 'default'}
>
<ApplicationIdentityWell initial={initial} logo={application.logo} />
<span className="min-w-0 flex-1">
<span className="block min-w-0 truncate text-sm font-medium leading-5">
<ItemCard.Icon aria-hidden="true">
<Surface variant="default" className="rc-application-identity-well">
<Avatar
className="rc-application-identity-avatar"
color="accent"
variant="soft"
>
<Avatar.Image
alt=""
className="object-contain p-1.5"
src={application.logo || xinyuanLogo}
/>
<Avatar.Fallback>
<img alt="" className="object-contain p-1.5" src={xinyuanLogo} />
</Avatar.Fallback>
</Avatar>
</Surface>
</ItemCard.Icon>
<ItemCard.Content>
<ItemCard.Title title={application.title}>
{application.title}
</span>
<Description className="mt-0.5 block min-w-0 truncate leading-4">
{application.description || application.name || '应用'}
</Description>
</span>
{current ? <Chip color="accent" size="sm">当前</Chip> : null}
</a>
</Card.Content>
</Card>
</ItemCard.Title>
{hasDistinctDescription ? (
<ItemCard.Description title={description}>
{description}
</ItemCard.Description>
) : null}
</ItemCard.Content>
{current ? (
<ItemCard.Action>
<Chip color="accent" size="sm">当前</Chip>
</ItemCard.Action>
) : null}
</ItemCard>
);
}
function LoadingSkeleton() {
return (
<div className="grid gap-3 sm:grid-cols-2" aria-label="正在加载应用">
<ItemCardGroup
aria-label="正在加载应用"
className="grid-cols-1 gap-3 sm:grid-cols-2"
columns={2}
layout="grid"
variant="transparent"
>
{Array.from({ length: 6 }).map((_, index) => (
<Card key={index} variant="secondary">
<Card.Content className="flex items-center gap-3">
<Skeleton className="size-9" />
<div className="flex-1 space-y-2">
<ItemCard key={index} variant="default">
<ItemCard.Icon><Skeleton className="size-9" /></ItemCard.Icon>
<ItemCard.Content className="gap-2">
<Skeleton className="h-4 w-2/3" />
<Skeleton className="h-3 w-full" />
</div>
</Card.Content>
</Card>
</ItemCard.Content>
</ItemCard>
))}
</div>
</ItemCardGroup>
);
}
......@@ -265,30 +314,45 @@ export default function ApplicationSwitcherDialog({
fallbackApplications.length > 0 ? 'success' : 'idle',
);
const [errorMessage, setErrorMessage] = useState('');
const [applicationSearch, setApplicationSearch] = useState('');
const currentApplicationName = useMemo(() => {
return applications.find(isCurrentApplication)?.name || APPLICATION_NAME;
return applications.find(isCurrentApplication)?.name || CURRENT_APPLICATION_NAME;
}, [applications]);
const applicationSections = useMemo(
() => buildApplicationSections(applications, siteInfo?.group),
[applications, siteInfo?.group],
);
const filteredApplicationSections = useMemo(() => {
const keyword = applicationSearch.trim().toLocaleLowerCase();
if (!keyword) {
return applicationSections;
}
return applicationSections
.map((section) => ({
...section,
applications: section.applications.filter((application) => [
application.title,
application.description,
application.name,
].some((value) => String(value || '').toLocaleLowerCase().includes(keyword))),
}))
.filter((section) => section.applications.length > 0);
}, [applicationSearch, applicationSections]);
useEffect(() => {
if (fallbackApplications.length === 0) {
return;
}
setApplications((previous) => {
if (previous.length > 0) {
return previous;
}
return fallbackApplications;
});
setApplications((previous) =>
mergeApplications(previous, fallbackApplications),
);
}, [fallbackApplications]);
useEffect(() => {
if (!open) {
setApplicationSearch('');
return;
}
......@@ -300,7 +364,7 @@ export default function ApplicationSwitcherDialog({
try {
const response = await getApplications();
const fetchedApplications = Array.isArray(response) ? response : [];
const fetchedApplications = unwrapApplications(response);
if (cancelled) {
return;
......@@ -344,19 +408,41 @@ export default function ApplicationSwitcherDialog({
<Modal.Container placement="center" scroll="inside" size="lg">
<Modal.Dialog
aria-label="应用切换"
className="rc-application-switcher-dialog p-7"
className="max-h-[min(42rem,calc(100dvh-2rem))] bg-background p-7"
style={{
maxWidth: 'min(46rem, calc(100vw - 2rem))',
width: 'min(46rem, calc(100vw - 2rem))',
}}
>
<Modal.CloseTrigger
aria-label="关闭"
style={{ insetInlineEnd: '1.75rem', top: '1.75rem' }}
/>
<Modal.CloseTrigger className="sr-only" aria-label="关闭" />
<Modal.Header>
<Modal.Heading>应用切换</Modal.Heading>
<Description className="leading-5">选择你可访问的应用</Description>
<div className="flex w-full min-w-0 flex-col gap-3">
<SearchField
aria-label="搜索应用"
className="w-full min-w-0"
value={applicationSearch}
variant="secondary"
onChange={setApplicationSearch}
>
<SearchField.Group>
<SearchField.SearchIcon>
<MagnifyingGlassIcon size={16} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="搜索应用" />
<SearchField.ClearButton aria-label="清空应用搜索" />
</SearchField.Group>
</SearchField>
</div>
</Modal.Header>
<Modal.Body className="mt-3">
<Modal.Body className="flex min-h-0 overflow-hidden" style={{ marginTop: '0.75rem' }}>
<ScrollShadow
className="h-full min-h-0 flex-1 overflow-y-auto pe-1 pb-1"
hideScrollBar={false}
offset={12}
size={24}
>
{status === 'error' && errorMessage ? (
<Alert status="warning">
<Alert className="mb-4" status="warning">
<Alert.Indicator><ExclamationTriangleIcon size={20} /></Alert.Indicator>
<Alert.Content><Alert.Description>{errorMessage}</Alert.Description></Alert.Content>
</Alert>
......@@ -364,17 +450,22 @@ export default function ApplicationSwitcherDialog({
{status === 'loading' && applications.length === 0 ? (
<LoadingSkeleton />
) : applications.length > 0 ? (
) : filteredApplicationSections.length > 0 ? (
<div className="space-y-6">
{applicationSections.map((section) => (
<section key={section.key}>
<div className="mb-3 flex items-center justify-between gap-3">
<h3 className="text-sm font-medium leading-5">
{section.label}
</h3>
<Description>{section.applications.length} 个应用</Description>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{filteredApplicationSections.map((section) => (
<ItemCardGroup
key={section.key}
className="grid-cols-1 gap-3 sm:grid-cols-2"
columns={2}
layout="grid"
variant="transparent"
>
<ItemCardGroup.Header className="flex items-center justify-between gap-3">
<ItemCardGroup.Title>{section.label}</ItemCardGroup.Title>
<ItemCardGroup.Description className="mt-0">
{section.applications.length} 个应用
</ItemCardGroup.Description>
</ItemCardGroup.Header>
{section.applications.map((application) => (
<ApplicationCard
key={application.name}
......@@ -382,18 +473,26 @@ export default function ApplicationSwitcherDialog({
current={application.name === currentApplicationName}
/>
))}
</div>
</section>
</ItemCardGroup>
))}
</div>
) : (
<EmptyStateSurface>
<EmptyState size="md">
<EmptyState.Header>
<EmptyState.Title>暂无可切换应用</EmptyState.Title>
<EmptyState.Description>当前账号还没有分配其它应用权限。</EmptyState.Description>
<EmptyState.Title>
{applicationSearch.trim() ? '没有找到匹配的应用' : '暂无可切换应用'}
</EmptyState.Title>
<EmptyState.Description>
{applicationSearch.trim()
? '请尝试其他关键词。'
: '当前账号还没有分配其它应用权限。'}
</EmptyState.Description>
</EmptyState.Header>
</EmptyState>
</EmptyStateSurface>
)}
</ScrollShadow>
</Modal.Body>
</Modal.Dialog>
</Modal.Container>
......
......@@ -147,7 +147,7 @@ export default function ControlMeasureFormPage() {
toolbarActions.push({
key: 'submit',
label: actionLoading === 'submit' ? '送审中...' : '送审',
variant: 'solid',
variant: 'primary',
tone: 'primary',
disabled: Boolean(actionLoading) || records.length === 0,
onClick: () => runFlowAction(
......@@ -167,7 +167,7 @@ export default function ControlMeasureFormPage() {
{
key: 'approve',
label: actionLoading === 'approve' ? '复核中...' : '一键复核',
variant: 'solid',
variant: 'primary',
tone: 'primary',
disabled: Boolean(actionLoading) || records.length === 0,
onClick: () => runFlowAction(
......@@ -179,7 +179,7 @@ export default function ControlMeasureFormPage() {
{
key: 'reject',
label: '驳回',
variant: 'outline',
variant: 'secondary',
tone: 'danger',
disabled: Boolean(actionLoading),
onClick: () => setRejectType('auditor'),
......@@ -196,7 +196,7 @@ export default function ControlMeasureFormPage() {
{
key: 'final-approve',
label: actionLoading === 'final-approve' ? '审核中...' : '一键最终审核',
variant: 'solid',
variant: 'primary',
tone: 'primary',
disabled: Boolean(actionLoading) || records.length === 0,
onClick: () => runFlowAction(
......@@ -208,7 +208,7 @@ export default function ControlMeasureFormPage() {
{
key: 'final-reject',
label: '驳回审核',
variant: 'outline',
variant: 'secondary',
tone: 'danger',
disabled: Boolean(actionLoading),
onClick: () => setRejectType('final'),
......
......@@ -146,7 +146,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
toolbarActions: [{
key: 'export-process',
label: busyKey === 'export-process' ? '导出中...' : '导出过程表',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => runAction({
key: 'export-process',
......@@ -159,14 +159,14 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'push-flow',
label: busyKey === 'push-flow' ? '推送中...' : '推送风险防控表',
variant: 'ghost',
variant: 'secondary',
disabled: ({ selectedRecords }) => disabledWhileBusy() || selectedRecords.length === 0,
onClick: (context) => setPushContext(context),
},
{
key: 'archive-process',
label: busyKey === 'archive-process' ? '归档中...' : '一键归档',
variant: 'ghost',
variant: 'secondary',
disabled: ({ selectedRecords }) => disabledWhileBusy() || selectedRecords.length === 0,
onClick: (context) => runAction({
key: 'archive-process',
......@@ -186,14 +186,14 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'import-show',
label: '导入过程表',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => requestImport('show', context),
},
{
key: 'export-show',
label: busyKey === 'export-show' ? '导出中...' : '导出展示表',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => runAction({
key: 'export-show',
......@@ -205,7 +205,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'export-measures',
label: busyKey === 'export-measures' ? '导出中...' : '导出防控措施',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => runAction({
key: 'export-measures',
......@@ -219,7 +219,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'export-selected-show',
label: busyKey === 'export-selected-show' ? '导出中...' : '导出所选',
variant: 'ghost',
variant: 'secondary',
disabled: ({ selectedRecords }) => disabledWhileBusy() || selectedRecords.length === 0,
onClick: (context) => runAction({
key: 'export-selected-show',
......@@ -231,7 +231,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'archive-show',
label: busyKey === 'archive-show' ? '归档中...' : '一键归档',
variant: 'ghost',
variant: 'secondary',
disabled: ({ selectedRecords }) => disabledWhileBusy() || selectedRecords.length === 0,
onClick: (context) => runAction({
key: 'archive-show',
......@@ -251,14 +251,14 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
{
key: 'import-measure',
label: '导入防控措施',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => requestImport('measure', context),
},
{
key: 'export-measure',
label: busyKey === 'export-measure' ? '导出中...' : '导出措施描述',
variant: 'outline',
variant: 'secondary',
disabled: disabledWhileBusy,
onClick: (context) => runAction({
key: 'export-measure',
......@@ -271,7 +271,7 @@ export default function IntegrityRiskWorkbenchPage({ pageType }) {
bulkActions: [{
key: 'archive-measure',
label: busyKey === 'archive-measure' ? '归档中...' : '一键归档',
variant: 'ghost',
variant: 'secondary',
disabled: ({ selectedRecords }) => disabledWhileBusy() || selectedRecords.length === 0,
onClick: (context) => runAction({
key: 'archive-measure',
......
@import "@fontsource/inter/400.css";
@import "@fontsource/inter/500.css";
@import "@fontsource/inter/600.css";
@import "tailwindcss";
@import "tailwindcss" source(none);
@import "@heroui/styles";
@import "@heroui-pro/react/css";
@import "./design-system/theme.css";
......@@ -9,6 +6,20 @@
@import "./design-system/shell.css";
@import "./design-system/data-grid.css";
@source "./*.{js,jsx,ts,tsx}";
@source "./components/**/*.{js,jsx,ts,tsx}";
@source "./pages/**/*.{js,jsx,ts,tsx}";
@source "./wrapper/**/*.{js,jsx,ts,tsx}";
/* Keep HeroUI primary button pressed feedback on the Xinyuan brand-red scale. */
.button--primary {
--button-bg: var(--accent);
--button-bg-hover: var(--primary-hover);
--button-bg-pressed: var(--primary-pressed);
--button-fg: var(--accent-foreground);
--button-bg-disabled: var(--rc-brand-disabled);
}
@theme {
--font-inter:
"Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
......@@ -18,19 +29,12 @@
--font-inter:
"Inter", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color-scheme: light !important;
--pm-brand: var(--rc-brand-primary);
--pm-brand-strong: var(--rc-brand-dark);
}
html {
color-scheme: light !important;
}
html.dark,
html[data-theme="dark"] {
color-scheme: dark !important;
}
body {
margin: 0;
font-family: var(--rc-font-sans);
......@@ -39,6 +43,43 @@ body {
color-scheme: inherit;
}
:is(.checkbox[data-selected="true"], .checkbox[data-indeterminate="true"])
.checkbox__control,
:is(
.checkbox__content[data-selected="true"],
.checkbox__content[data-indeterminate="true"]
)
.checkbox__control {
border-color: var(--rc-brand-primary) !important;
background: var(--rc-brand-primary) !important;
box-shadow: none !important;
filter: none !important;
}
:is(.checkbox[data-selected="true"], .checkbox[data-indeterminate="true"])
.checkbox__control::before,
:is(
.checkbox__content[data-selected="true"],
.checkbox__content[data-indeterminate="true"]
)
.checkbox__control::before {
border-color: var(--rc-brand-primary) !important;
background: var(--rc-brand-primary) !important;
box-shadow: none !important;
filter: none !important;
}
:is(.checkbox[data-selected="true"], .checkbox[data-indeterminate="true"])
.checkbox__indicator,
:is(
.checkbox__content[data-selected="true"],
.checkbox__content[data-indeterminate="true"]
)
.checkbox__indicator {
color: var(--accent-foreground) !important;
filter: none !important;
}
body .login-page {
--rc-login-brand-logo-size: 2.25rem;
--rc-login-card-brand-gap: 0.875rem;
......@@ -51,8 +92,8 @@ body .login-page {
--rc-login-field-filled-background: #fbf1f1;
--rc-login-field-filled-background-hover: #faeaea;
--rc-login-field-autofill-background: #fbf1f1;
--rc-login-field-border-hover: rgb(158 27 27 / 24%);
--rc-login-field-focus-ring: rgb(158 27 27 / 16%);
--rc-login-field-border-hover: color-mix(in oklab, var(--accent) 24%, transparent);
--rc-login-field-focus-ring: color-mix(in oklab, var(--accent) 16%, transparent);
}
body .login-brand img {
......
// Chart series colors resolve from the shared Xinyuan CSS tokens at render time.
export const XINYUAN_CHART_TOKENS = Object.freeze(
Array.from({ length: 16 }, (_, index) => `--rc-chart-${index + 1}`),
);
export function getChartColorToken(index = 0) {
const normalizedIndex = Math.max(0, Number(index) || 0);
if (normalizedIndex >= XINYUAN_CHART_TOKENS.length) {
throw new RangeError('Xinyuan charts support at most 16 distinct series colors.');
}
return `var(${XINYUAN_CHART_TOKENS[normalizedIndex]})`;
}
function hashChartKey(key) {
let hash = 2166136261;
for (let index = 0; index < key.length; index += 1) {
hash ^= key.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return hash >>> 0;
}
export function createStableChartColorMap(keys = []) {
const uniqueKeys = [...new Set(keys.map((key) => String(key ?? '')).filter(Boolean))]
.sort((left, right) => left.localeCompare(right, 'zh-Hans'));
if (uniqueKeys.length > XINYUAN_CHART_TOKENS.length) {
throw new RangeError('Xinyuan charts support at most 16 distinct series colors.');
}
// Hash each business key into the shared palette so filtering and sorting do
// not change a series' color. Linear probing resolves deterministic collisions.
const usedIndexes = new Set();
return new Map(
uniqueKeys.map((key) => {
let paletteIndex = hashChartKey(key) % XINYUAN_CHART_TOKENS.length;
while (usedIndexes.has(paletteIndex)) {
paletteIndex = (paletteIndex + 1) % XINYUAN_CHART_TOKENS.length;
}
usedIndexes.add(paletteIndex);
return [key, getChartColorToken(paletteIndex)];
}),
);
}
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