Commit 01cf462f authored by Andy-bubu's avatar Andy-bubu

fix: refine sidebar defaults and list scrolling

parent db38d98a
......@@ -18,6 +18,8 @@ Hover/Focus/Selected/Disabled。`src/pages` 下没有页面私有 CSS/Less/SCSS
- `AppShell`:固定浅色根主题、Pro Sidebar Provider、Navbar 和 base-aware 页面滚动容器。
- `DashboardSidebar` / `DashboardNavbar`:固定深色导航、offcanvas、应用切换、用户菜单与通知动作;
Navbar 不再提供主题切换。
Sidebar 业务分类首次渲染默认展开,分类仍由 HeroUI `Disclosure` 管理,用户可手动收起/展开且路由切换不
强制重置状态。
标题层级固定为 Navbar 20px/600、页面分区 18px/700、卡片/列表模块 16px/600,模块标题
不得复用页面级标题字号或字重。
移动 Sidebar 直接组合 Pro Sheet,并在真实 `Sheet.Dialog` 上提供可访问名称,继续复用
......@@ -33,8 +35,12 @@ Hover/Focus/Selected/Disabled。`src/pages` 下没有页面私有 CSS/Less/SCSS
原顺序,不拥有接口、权限、会话或跳转逻辑。
- `DataTable`:Pro DataGrid 原生选择、排序、固定列、页面纵向滚动下的 sticky 表头、EmptyState、ActionBar;共享
`DataTablePagination` 将摘要与 HeroUI Autocomplete 页容量选择器组成左侧同组,选择器紧接摘要
右侧;Pagination 页码控制靠右。项目业务列表使用 20/50/100 条,选择器不显示搜索框,变更时重置到第一页。
该结构是跨应用固定基线,业务页面不得改变其层级、顺序、对齐和尺寸。
右侧;Pagination 页码控制靠右。项目业务列表使用 20/50/100 条,页容量选择器固定 128px 且不显示搜索框,变更时重置到第一页。
该结构是跨应用固定基线,业务页面不得改变其层级、顺序、对齐和尺寸。长列表允许 DataGrid
使用一个真实的纵向滚动容器,但必须保持滚动链:滚动到最后一条后继续向下、滚动到第一条后继续向上,
边界滚动自动传递给 `.app-shell-content-scroll` 页面容器;不得使用 `overscroll-behavior: contain`
阻断传递,也不得在 DataGrid 与页面之间增加第二个纵向滚动层。Pagination 始终是数据列表底部内容,
不固定在浏览器底部;横向列宽溢出仍只由 DataGrid 横向容器处理。
- `XinyuanAutocomplete` / `FilterSelect` / `ReferenceSearchSelect`:当前 HeroUI
`selectedKey(s)`、`onSelectionChange`、`inputValue` 合同;下拉选项统一单行展示业务标题,
内部 ID、账号和文件辅助描述只用于搜索/匹配,不渲染为第二行。
......@@ -45,9 +51,11 @@ Hover/Focus/Selected/Disabled。`src/pages` 下没有页面私有 CSS/Less/SCSS
- `XinyuanEChart`:`echarts-for-react` 的共享主题边界,统一解析 `--xinyuan-chart-*`,注入
ARIA、Tooltip、图例、坐标轴和响应式;业务页面只拥有 series/data 等业务配置。
- `FilterSurface`(共享布局合同):三列以上字段使用 HeroUI `Surface default` 承载,字段使用
`secondary`,首行最多三个高频字段,剩余字段通过官方 `Disclosure` 展开。查询按钮与展开触发器沿字段
底部右对齐;触发器是 `sm ghost`,文案“展开/收起”,指示器由 `Disclosure.Indicator` 负责旋转。折叠
只改变显示,不触发查询或重置筛选,内容保持挂载并以布局 `hidden` 收起;移动端退化为单列。
`secondary`,首行最多三个高频字段,剩余字段通过官方 `Disclosure` 展开。操作按字段底部右对齐,顺序
固定为 36px 圆形重置图标、品牌红查询按钮、`sm ghost` 展开/收起触发器;重置通过共享回调同步清空待提交/已提交
筛选并回到第一页,使用 Ant `ReloadOutlined` 和“重置筛选”无障碍名称。触发器文案为“展开/收起”,指示器由
`Disclosure.Indicator` 负责旋转。折叠只改变显示,不触发查询或重置筛选,内容保持挂载并以布局 `hidden` 收起;
移动端退化为单列。
- `DynamicFormValueEditor`:将 Subject `form` 的 `{ form, data, settings }` 协议映射为纵向
HeroUI Tabs 分组导航、Disclosure、Pro DataGrid、Autocomplete、Modal 和 AlertDialog;组件只通过 `onChange` 回传
草稿,不拥有记录更新接口或提交时机。
......
......@@ -23,6 +23,8 @@ Inter 与系统回退字体之间切换。
- 登录按“问题整改”保留真实验证码:首次加载 `/auth/captcha`,提交 `captcha/token`,失败后
清空并刷新;账号、密码和验证码控件等高,验证码输入与图片使用批准的两列网格。
- HeroUI Pro Sidebar 为桌面 240px,`collapsible="offcanvas"`;移动端使用官方 80vw。
- Sidebar 内的业务分类使用 HeroUI `Disclosure`,首次进入页面默认全部展开;用户可通过官方触发器
自由收起或展开,路由切换不强制重置用户当前状态。
- HeroUI Pro Navbar 高 64px,直接使用 `Sidebar.Trigger`;Navbar 与页面共享桌面/平板/
手机 48/32/16px 对称 gutter。
- 标题层级固定区分:Navbar 页面标题为 20px/600;页面分区标题使用 18px/700;卡片或列表
......@@ -117,8 +119,10 @@ Inter 与系统回退字体之间切换。
同行字段间距 12px。宽屏首行使用等宽三列,字段标签和控件左边界严格对齐,右侧保留操作轨道;不使用
Card、额外描边、阴影或页面私有字段背景。
- 筛选首行只放最常用的前三个字段,其余字段继续挂载在同一个 HeroUI `Disclosure` 中。首行操作按字段
底部对齐:手动提交页面显示品牌红 `primary` 的“查询”,随后显示 `sm` `ghost` 的“展开/收起”;实时
筛选页面不显示重复的查询或刷新按钮。展开/收起只改变字段可见性,不提交查询、不清空已选值、不改变分页。
底部对齐:先显示 36px 圆形 secondary HeroUI 重置图标,再显示品牌红 `primary` 的“查询”,随后显示
`sm` `ghost` 的“展开/收起”;重置图标使用 Ant Design `ReloadOutlined`、aria-label 为“重置筛选”,
清空待提交和已提交筛选、回到第一页并清除列表选择。实时筛选页面不显示重复的查询或刷新按钮;仅在页面
提供真实重置回调时显示重置图标。展开/收起只改变字段可见性,不提交查询、不清空已选值、不改变分页。
- “展开/收起”必须使用官方 `Disclosure.Trigger + Disclosure.Indicator`,文字始终为“展开”或“收起”,
指示器使用共享 Ant Design `DownOutlined`,收起时向下、展开时由 HeroUI 状态旋转向上。触发器默认透明,
Hover、focus-visible、pressed 和 expanded 状态由 HeroUI Token 提供;禁止自绘箭头、固定灰色胶囊、分隔线或
......@@ -130,8 +134,10 @@ Inter 与系统回退字体之间切换。
字段之后并保持同一行可换行,不产生横向滚动。所有字段填满自己的网格轨道,禁止半列短控件和不均匀孤列。
- 列表区域不重复渲染与页面/Navbar 相同层级的模块标题;页面标题或当前 Tab 已能表达业务语境时,
列表直接从工具栏开始,避免重复文案占用垂直空间。
- 列表采用页面整体纵向滚动,不创建固定高度的表格纵向滚动容器,也不产生“页面滚动 + 表格滚动”的
双重体验。DataGrid 内容随页面自然向下滚动,Pagination 位于数据列表底部并随列表一起滚动。
- 列表保留页面与 DataGrid 的连续纵向滚动关系:DataGrid 内容较多时在列表区域滚动,Pagination 位于
数据列表底部并随列表一起滚动;当列表滚动到第一条或最后一条后继续向同方向滚动,滚动链必须自动传递给
页面,让用户能够继续看到筛选、分页和后续内容。不得通过 `overscroll-behavior: contain` 阻断边界传递,
也不得再增加第三层纵向滚动容器。横向列宽溢出仍由 DataGrid 的横向容器独立处理。
- DataGrid 表头进入页面顶部时使用 CSS `position: sticky` 吸顶,`top` 必须避开固定 Navbar 的实际高度;
表头保持原有 `surface-secondary` 背景、底部边界和层级阴影。不得用 JS 监听滚动计算位置。
- 吸顶表头沿用 DataGrid 实际列宽;固定首列与操作列的 sticky 偏移和层级不得改变。首列背景必须不透明,
......@@ -163,7 +169,7 @@ Inter 与系统回退字体之间切换。
批量 ActionBar 和详情配置化操作区必须统一执行该排序,页面不得用私有定位样式修正按钮顺序。
- DataGrid 使用原生 `selectedKeys`、`onSelectionChange`、排序、固定列、Pagination、
ActionBar 和 EmptyState。业务数据列表默认每页 20 条,并在 Pagination 左侧使用 HeroUI
Autocomplete 提供 20/50/100 条切换;页容量控件与左侧分页摘要(“共…条,第…页”)同组同一行,
Autocomplete 提供 20/50/100 条切换,固定宽度 128px;页容量控件与左侧分页摘要(“共…条,第…页”)同组同一行,
紧接摘要右侧;该三项选择器关闭搜索框,页码控制继续靠右。切换回到第一页并保留筛选、排序和权限范围。
- 上述列表分页组合是跨应用固定基线:列表模块、分页摘要、页容量选择器和页码控制的层级、顺序、
对齐方式与尺寸不得由业务页面重新设计;后续应用只接入自己的数据和回调。
......
......@@ -88,12 +88,11 @@ function SidebarContents({
);
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 defaultExpanded>
<Disclosure.Heading>
<Sidebar.GroupLabel>
<Disclosure.Trigger className="flex w-full min-w-0 items-center gap-2 text-left">
......
......@@ -596,7 +596,7 @@ export function DataTablePagination({
{hasPageSizePicker ? (
<XinyuanAutocomplete
aria-label="每页条数"
className="w-24 shrink-0"
className="w-32 shrink-0"
isDisabled={isDisabled}
options={pageSizeOptionsForPicker}
placeholder="每页条数"
......
......@@ -389,6 +389,12 @@ export default function SubjectRecordWorkbench({
setPage(0);
setSelectedIds([]);
}}
onResetFilters={() => {
setPendingFilters({});
setFilters({});
setPage(0);
setSelectedIds([]);
}}
title={title}
description={description}
columns={visibleColumns}
......
......@@ -83,6 +83,7 @@ export default function UserSearchSelect({
aria-label={ariaLabel}
inputValue={keyword}
isDisabled={disabled}
isClearable
isLoading={loading}
options={options}
placeholder={placeholder}
......@@ -93,6 +94,7 @@ export default function UserSearchSelect({
valueText={valueText || placeholder}
variant={variant}
onInputChange={setKeyword}
onClear={() => onChange?.(multiple ? [] : null)}
onSelectionChange={(selection) => {
if (multiple) {
onChange?.(selection === 'all' ? options.map((option) => option.id) : [...selection]);
......
......@@ -16,6 +16,7 @@ import {
Close,
Delete,
Eye,
ArrowPathIcon,
ChartBarSquareIcon,
ChevronDownIcon,
ChevronRightIcon,
......@@ -531,6 +532,7 @@ export default function RecordWorkbench({
filterSchema = [],
onFiltersChange,
onQuery,
onResetFilters,
title,
columns = EMPTY_ARRAY,
columnGroups = EMPTY_ARRAY,
......@@ -1088,6 +1090,22 @@ export default function RecordWorkbench({
{primaryFilterFields.map(renderFilterField)}
</div>
<div className="flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap">
{onResetFilters ? (
<Tooltip delay={0}>
<Button
aria-label="重置筛选"
className="rc-filter-reset-button"
isDisabled={loading}
isIconOnly
size="sm"
variant="secondary"
onPress={onResetFilters}
>
<ArrowPathIcon aria-hidden="true" size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip>
) : null}
{onQuery ? (
<Button
isDisabled={loading}
......
......@@ -552,6 +552,27 @@
border-radius: var(--rc-radius-overlay);
}
.rc-filter-reset-button.button {
width: 2.25rem;
min-width: 2.25rem;
max-width: 2.25rem;
height: 2.25rem;
flex: 0 0 2.25rem;
padding: 0;
border-radius: 999px;
color: var(--rc-brand-primary);
}
.rc-filter-reset-button.button > span,
.rc-filter-reset-button.button .anticon,
.rc-filter-reset-button.button svg {
display: inline-grid;
width: 1rem;
height: 1rem;
place-items: center;
margin: 0;
}
.rc-dashboard-card {
border-radius: 1.5rem;
}
......@@ -723,7 +744,10 @@
max-height: min(60dvh, 42rem);
overflow-x: auto;
overflow-y: auto;
overscroll-behavior: contain;
/* Continue page scrolling after the table reaches its top or bottom edge. */
overscroll-behavior: auto;
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
}
/* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the
......
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