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

fix: polish integrity party interactions

parent e31e7c18
......@@ -49,7 +49,8 @@
Native HeroUI 静态架构门禁已通过:`src/xinyuan-portable.css` 已移除,业务图表统一保留并使用 ECharts 适配器,页面目录无私有 CSS/Less/SCSS;仅保留经确认的共享样式层。HeroUI 与 HeroUI Pro 的样式分别通过 `@heroui/styles/css` 和 `@heroui-pro/react/css` 显式加载;包根 `@heroui/styles` 是 JavaScript 样式工具入口,不能替代 CSS 入口。当前品牌主色固定为 `#DA241A`,HeroUI `accent/focus` 和 `primary-*` Token 不随浅深主题改变色相;图表 Token 按品牌红深浅、暖土色、中性灰紫和灰蓝分组,禁止绿色和荧光色。2026-08-26 已完成四视口、深浅主题、权限、通知和文件事务的运行态浏览器门禁。
# 共享 UI 架构补充
`XinyuanEChart` 是图表唯一渲染适配器,页面仅提供业务数据和 ECharts option;适配器统一主题、Tooltip、坐标轴和 Pie 扇区样式。相同分类维度的多个数据项使用 `createResolvedXinyuanChartColorMap` 和 `XINYUAN_CHART_CATEGORY_PALETTE` 按稳定顺序分配颜色,顺序为品牌红深浅 → 暖土色 → 中性灰紫 → 灰蓝,禁止绿色、荧光色和彩虹式跳色;单一指标或多指标系列使用 `XINYUAN_CHART_SEMANTIC` 语义色,单系列固定使用 `#DA241A`,页面不得写入硬编码颜色。业务源码禁止直接导入 Recharts 或 HeroUI Pro 图表组件;`recharts` 依赖仅因当前 HeroUI Pro `KPI` 包的静态运行时解析而保留,不作为应用图表渲染器。`XinyuanModal` 统一使用 `bg-background` Dialog Surface。列表分页由 `DataTablePagination` 统一承载容量选择器与页码状态:容量使用 HeroUI Autocomplete 受控单选,标准选项为 `20 / 50 / 100 条/页`、默认值必须属于选项集合,固定少量选项时关闭 `showSearch` 与 `isClearable`,切换容量回到第 1 页。
`XinyuanEChart` 是图表唯一渲染适配器,页面仅提供业务数据和 ECharts option;适配器统一主题、Tooltip、坐标轴和 Pie 扇区样式。相同分类维度的多个数据项使用 `createResolvedXinyuanChartColorMap` 和 `XINYUAN_CHART_CATEGORY_PALETTE` 按稳定顺序分配颜色,顺序为品牌红深浅 → 暖土色 → 中性灰紫 → 灰蓝,禁止绿色、荧光色和彩虹式跳色;单一指标或多指标系列使用 `XINYUAN_CHART_SEMANTIC` 语义色,单系列固定使用 `#DA241A`,页面不得写入硬编码颜色。业务源码禁止直接导入 Recharts 或 HeroUI Pro 图表组件;`recharts` 依赖仅因当前 HeroUI Pro `KPI` 包的静态运行时解析而保留,不作为应用图表渲染器。`XinyuanModal` 统一使用 `bg-background` Dialog Surface。党风廉政台账筛选由 HeroUI `form` 手动提交,字段值先保留在页面草稿,右侧操作固定为重置图标、`查询` 主按钮和 `展开/收起` Disclosure;查询时复用原有 `reload` 请求参数,不改变接口协议。列表分页由 `DataTablePagination` 统一承载容量选择器与页码状态:容量使用 HeroUI Autocomplete 受控单选,标准选项为 `20 / 50 / 100 条/页`、默认值必须属于选项集合,固定少量选项时关闭 `showSearch` 与 `isClearable`,切换容量回到第 1 页。
长列表 DataGrid 的 `.data-table-scroll` 只隔离横向滚动;纵向在首行/末行继续滚动时必须允许滚动链传递到 `.app-shell-content-scroll`,保证筛选、列表和 Pagination 可以连续浏览,不增加页面级滚轮监听或改变数据请求时机。
驾驶舱模块标题沿用“审计项目管理”基准的 HeroUI `Card.Header` 左对齐横向结构;`Card.Title` 使用共享 `.rc-module-title`(16px/600、24px 行高、主题前景色)与 `min-w-0 flex-1 text-left`,操作或描述信息放在标题右侧,不通过页面私有 CSS 改写字号、颜色或字重。
......@@ -58,3 +59,4 @@ Native HeroUI 静态架构门禁已通过:`src/xinyuan-portable.css` 已移除
台账详情预览使用固定功能标题,记录标题作为正文首项展示;编辑态在同一位置使用 HeroUI `Input` 修改顶层 `record.title`,不改变 `metadata`。标题展示尊重当前 View 的 `shortTitle` 配置。
详情弹窗结构固定为 HeroUI `Modal.Root > Modal.Backdrop > Modal.Container > Modal.Dialog`。字段控件直接来自 `@heroui/react`(`Input`、`TextArea`、`DatePicker`、`Autocomplete`)或 `@heroui-pro/react`(`DropZone`);预览态使用静态只读值,编辑态使用 `secondary` 控件,权限禁用字段不显示灰色伪控件。不得引入 Catalyst、Headless UI、原生表单控件或自制 Modal/字段外观。
预览态底部仅显示 `primary` 编辑按钮作为主操作;点击后通过同一 Modal 内的状态切换进入编辑态,不重建记录或材料会话。编辑态底部显示 `secondary` 取消与 `primary` 保存,取消关闭弹窗且不触发保存。
......@@ -17,10 +17,12 @@
- [x] Shell:迁移品牌、offcanvas、用户菜单、主题和 base-aware 导航
- [x] 应用切换:同步 `model_analysis` HeroUI Modal、`site.group` 动态分组、真实数量、“其他应用”/“全部应用”回退和 36px identity well
- [x] 驾驶舱:KPI 使用 HeroUI Pro KPI compound API,所有业务图表统一通过共享 `XinyuanEChart`(ECharts)适配器渲染
- [x] 党风廉政 Tabs、结构化筛选与透明工具栏 SearchField;无刷新/查询按钮并保持自动筛选
- [x] 党风廉政 Tabs、结构化筛选与透明工具栏 SearchField;结构化筛选按基准使用重置图标、查询按钮和 Disclosure 手动提交,关键词搜索保持原有延迟
- [x] 党风廉政 DataGrid、选择、ActionBar、20/50/100 页容量 Pagination;默认 20,切换后回到第 1 页且无清除图标/搜索框
- [x] 长列表滚动边界:DataGrid 到达首/末行后继续纵向滚动可自然传递至页面,底部 Pagination 可达;横向滚动仍限制在列表内
- [x] 新建/编辑、导入、批量修改、删除确认 Modal
- [x] 详情预览/编辑遵循 HeroUI Modal:预览使用固定功能标题 + 正文记录标题静态值,编辑使用 HeroUI Input 修改顶层 `record.title`;权限禁用字段保持只读值,不以灰色控件冒充预览
- [x] 详情预览态通过同一 Modal 的 `primary` 编辑按钮平滑切换编辑态;编辑态显示取消/保存,保留字段与材料草稿,取消不保存
- [x] DropZone 上传、附件维护、Pro DataGrid Excel 预览
## 业务回归
......
......@@ -30,8 +30,9 @@
- Shell:240px offcanvas Sidebar、64px Navbar、桌面/平板/手机分别使用 48/32/16px 对称 gutter。
- 列表顺序:页面 Tabs、结构化筛选 Surface、透明数据集标题与工具栏、DataGrid、Pagination。
- 筛选 Surface 最多首显三个结构化字段,溢出字段通过 HeroUI Disclosure 展开;不显示刷新或查询按钮,字段变化自动筛选,关键词 SearchField 约 300ms 延迟。
- 筛选 Surface 最多首显三个结构化字段,溢出字段通过 HeroUI Disclosure 展开。党风廉政台账使用手动提交模式:右侧依次提供 HeroUI 重置图标按钮、`查询` 主按钮和 `展开/收起` Disclosure 触发器;字段变化只更新筛选草稿,点击查询才复用原有请求回调。列表工具栏关键词 SearchField 保持原有约 300ms 延迟。
- 业务列表使用 HeroUI Pagination,并在列表总数信息旁提供页容量选择器;当前标准选项为 `20 / 50 / 100 条/页`,默认值必须是选项中的一个(本应用默认 `20`)。切换页容量时回到第 1 页,并复用原有查询回调传递新的 `pageSize`。
- 长列表的数据区域使用独立纵向滚动和 sticky 表头;纵向滚动到首行/末行后继续滚动时,滚动链自然交给页面容器,使底部 Pagination 或顶部筛选可达。横向滚动仍限制在 DataGrid 内,不影响页面横向布局。
- 普通业务单选使用 HeroUI Autocomplete 的受控单选结构,已有值时显示官方 `Autocomplete.ClearButton`;页容量选择器属于例外,选项数量较少且固定时必须传 `showSearch={false}` 和 `isClearable={false}`,不渲染“输入关键词搜索”框,也不显示清除/删除图标。选择器只能切换有效页容量,不能提交空值。
- 页容量选择器保留加载禁用状态、当前值回显和 `20 / 50 / 100` 的顺序;页面不得把不存在的值(例如 `10`)作为初始 `pageSize` 或展示文本。
- Dashboard/KPI 外层圆角 24px,DataGrid 20px,字段 12px,Modal 24px。
......@@ -40,6 +41,7 @@
- 台账详情预览弹窗的 Modal Heading 使用固定功能标题(如“预览台账记录”),正文首项按当前 View 的 title 配置展示“记录标题”;编辑态该项使用 HeroUI `Input` 编辑并仅写入顶层 `record.title`,不得写入 `metadata`。标题展示沿用 `displayTitle` 去除自动生成的日期后缀;`shortTitle` 隐藏时同步隐藏该正文项。
- 预览态普通标量字段使用无边框静态值:标签为 `text-muted`,值为 `text-foreground`,空值显示“暂无内容”;不得用灰色 disabled Input/Autocomplete 冒充预览。编辑态才显示 HeroUI `secondary` 控件;权限禁用、锁定或只读字段在同一网格中继续显示静态只读值。
- 预览与编辑共用两列字段网格和字段顺序;长文本、附件等内容跨两列。编辑态使用 Footer 的 HeroUI `secondary` 取消 + `primary` 保存,保存只提交一次,取消丢弃草稿,不改变原有校验、权限和请求时机。
- 预览态 Footer 使用 HeroUI `primary` 编辑按钮作为主操作;点击后在同一个 Modal 和编辑会话内切换为编辑态,保留当前记录标题、字段草稿和材料草稿,不关闭后重新打开;进入编辑态后显示 `secondary` 取消 + `primary` 保存,取消关闭并丢弃未保存草稿。
- 例行成功反馈使用顶部居中的自动消失 Toast;需要处理的错误保留上下文 Alert。
- 应用切换严格继承 `model_analysis`:从 `/info -> site.group` 动态生成分组,保持配置顺序并显示真实数量;未归组应用进入“其他应用”,无配置时使用“全部应用”。Modal 固定 46rem/42rem、28px inset、手机一列、`sm` 起两列,间距 12px;加载骨架使用相同网格,桌面不得切成三列,长列表仅在 Modal Body 内滚动。
......
......@@ -723,7 +723,10 @@
max-height: min(60dvh, 42rem);
overflow-x: auto;
overflow-y: auto;
overscroll-behavior: contain;
/* Keep horizontal gestures inside the grid, but hand vertical wheel/touch
momentum to the page once the first or last row has been reached. */
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
}
/* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the
......
import {
ArrowPathIcon,
Close,
ChevronDownIcon,
Delete,
......@@ -26,6 +27,7 @@ import {
SearchField,
Surface,
Tabs,
Tooltip,
TextArea,
Toast,
} from '@heroui/react';
......@@ -479,13 +481,10 @@ export default function IntegrityPartyPage() {
}
useEffect(() => {
const timer = window.setTimeout(() => {
reload(0, filters[activeKey]);
}, 300);
return () => window.clearTimeout(timer);
// 筛选字段是待提交草稿,只有 activeKey/view/权限变化时自动加载。
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeKey, activeViewName, canAccessLedger, filters]);
}, [activeKey, activeViewName, canAccessLedger]);
function updateFilter(name, value) {
setFilters((current) => ({
......@@ -498,6 +497,15 @@ export default function IntegrityPartyPage() {
setPage(0);
}
function clearFilters() {
setFilters((current) => ({
...current,
[activeKey]: createEmptyFilters(activeConfig),
}));
setPage(0);
setSelectedIds([]);
}
function openCreateDialog() {
pendingLocalMaterialsRef.current = {};
setDetailSessionId((value) => value + 1);
......@@ -888,6 +896,12 @@ export default function IntegrityPartyPage() {
<Disclosure
isExpanded={filtersExpanded}
onExpandedChange={setFiltersExpanded}
>
<form
onSubmit={(event) => {
event.preventDefault();
reload(0, filters[activeKey]);
}}
>
<div className="grid min-w-0 gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
<div className="grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
......@@ -897,21 +911,43 @@ export default function IntegrityPartyPage() {
</FilterField>
))}
</div>
{expandedFilterItems.length > 0 ? (
<div className="flex min-w-0 flex-wrap items-end gap-2 md:justify-end xl:flex-nowrap">
<Tooltip delay={0}>
<Button
aria-label="重置筛选"
isDisabled={loading}
isIconOnly
size="sm"
variant="secondary"
type="button"
onPress={clearFilters}
>
<ArrowPathIcon size={16} />
</Button>
<Tooltip.Content>重置筛选</Tooltip.Content>
</Tooltip>
<Button
isDisabled={loading}
size="sm"
variant="primary"
type="submit"
>
查询
</Button>
{expandedFilterItems.length > 0 ? (
<Disclosure.Trigger className="button button--sm button--ghost">
<span>{filtersExpanded ? '收起' : '展开'}</span>
<Disclosure.Indicator>
<ChevronDownIcon size={14} />
<ChevronDownIcon aria-hidden="true" size={14} />
</Disclosure.Indicator>
</Disclosure.Trigger>
</div>
) : null}
</div>
{expandedFilterItems.length > 0 ? (
<Disclosure.Content
hidden={!filtersExpanded}
className={clsx(
'min-h-0 min-w-0 xl:col-start-1 xl:col-end-2',
'min-w-0 xl:col-start-1 xl:col-end-2',
!filtersExpanded && 'hidden',
)}
>
......@@ -925,6 +961,7 @@ export default function IntegrityPartyPage() {
</Disclosure.Content>
) : null}
</div>
</form>
</Disclosure>
</XinyuanFilterSurface>
......@@ -1028,16 +1065,20 @@ export default function IntegrityPartyPage() {
size="4xl"
title={detailMode === 'create' ? `新建${activeConfig.tabLabel}记录` : detailMode === 'view' ? '预览台账记录' : '编辑台账记录'}
footer={
detailMode === 'view' ? (
<Button type="button" size="sm" variant="primary" onPress={() => setDetailMode('edit')}>
编辑
</Button>
) : (
<>
<Button type="button" size="sm" variant="secondary" onPress={() => handleDetailOpenChange(false)}>
取消
</Button>
{detailMode !== 'view' ? (
<Button type="button" isPending={saving} size="sm" variant="primary" onPress={saveDetail}>
{saving ? '保存中...' : '保存'}
</Button>
) : null}
</>
)
}
>
{detailViews.length > 1 ? (
......
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