Commit 70f36d82 authored by Andy-bubu's avatar Andy-bubu

feat: refine xinyuan colors and list interactions

parent bb0fb715
......@@ -27,7 +27,7 @@
## 本轮迁移基线
- 主品牌色统一为 `#9E1B1B`,禁止将 sky/teal/cyan/rose 作为可见品牌色。
- 主品牌色统一为 `#DA241A`,禁止将 sky/teal/cyan/green/rose 作为可见品牌色。
- 使用本地完整 HeroUI Pro 镜像及 HeroUI 样式层。
- 同步已验收鑫元 Token、Shell 和组件 CSS。
- 登录页使用鑫元红 Logo、紧凑品牌区、统一输入高度和不透明浅红 filled/autofill 状态。
......
......@@ -35,10 +35,18 @@
- Surface 层级为 page background -> surface/default -> surface-secondary;overlay 是独立平面。
- 不在页面覆盖 HeroUI slot、BEM、伪元素、颜色、边框、圆角、阴影或交互状态。
## 品牌色与数据可视化
- `src/design-system/theme.css` 中 `--accent`、`--focus`、`--primary` 固定为 `#DA241A`,
由 HeroUI 官方状态变量承接按钮、选中、Hover、Pressed 和 Focus;页面不再声明另一套 Primary 色。
- 图表通过 `--rc-chart-*` 使用统一的品牌红、暖色、中性色和灰蓝色低冲突序列,禁止绿色系和随机彩虹色。
- KPI compound API 的图标 tone 仅引用共享 `--rc-metric-tone-*`,不在页面写入 hex/OKLCH 颜色。
## 列表与反馈
- 流程列表默认每页 10 条,并可通过 HeroUI Autocomplete 切换 10/20/50/100 条,使用 Pro DataGrid 与 HeroUI compound Pagination。
- 流程列表默认每页 20 条,并可通过宽度 128px 的 HeroUI Autocomplete 切换 20/50/100 条,使用 Pro DataGrid 与 HeroUI compound Pagination。
- 列表模块使用基准的透明三层结构:独立数据集标题行、`rc-page-toolbar` 命令行、`rc-list-data-stack` 数据与分页;不使用 Card 或页面外观把三层框在一起。
- DataGrid 列表外层使用 `rc-data-table-sticky`,滚动容器使用 `data-table-scroll`;横向 overscroll 在表格内消化,纵向 overscroll 允许向 `app-shell-content-scroll` 传递,滚到数据首尾后可继续浏览页面分页区域。
- 搜索位于左侧,业务操作在右,主操作最右;移动端搜索独占行。
- 成功反馈使用 HeroUI toast;需要用户处理的错误保留上下文 Alert。
- 查看详情使用单一 secondary Surface,编辑/确认使用 Modal 或 AlertDialog。
......
......@@ -6,6 +6,19 @@
- 视觉和交互唯一运行基准为已确认完成的“审计项目管理”应用。
- 页面不得自定义 HeroUI/HeroUI Pro 的颜色、背景、描边、圆角、阴影或交互状态。
## 品牌与图表配色(2026-09)
- 鑫元唯一 Primary Brand Color 为 `#DA241A`。HeroUI 的 `accent`、`focus`、`primary`
及 `rc-brand-primary` 均固定到该值;Hover 使用 `#C91F17`,Pressed 使用 `#B51B14`,
Disabled 使用低透明度品牌红。浅色和深色模式不得由 HeroUI 默认主题改写品牌色。
- 图表只使用 `--xinyuan-chart-1` 到 `--xinyuan-chart-16` / `--rc-chart-*` Token:
`#DA241A, #B83A32, #C95A52, #D47A70, #A65A45, #B87958, #C99772,
#6F6674, #807783, #938B96, #4F6380, #61758F, #74879A, #8A6F63,
#9A8174, #5F5A61`。
- 色板按品牌红深浅、暖色、中性色、灰蓝色排序,禁止绿色、青色、荧光色和彩虹式高冲突相邻配色。
单系列图表使用 `--rc-chart-primary`;同一指标使用同一语义 Token,分类数量增加时按稳定业务 key 分配颜色。
- KPI 图标色也从同一克制色板取值,Card 主体保持中性,不以页面私有颜色覆盖共享 Token。
## 字体与样式入口
- `.umirc.ts` 通过常驻 `<link>` 加载 `public/fonts/inter.css`,字体文件固定为 Inter 400/500/600 拉丁字符子集。
......@@ -37,6 +50,8 @@
- 三个以上筛选字段使用 `Surface variant="default"`;内部字段统一 `secondary`;一到两个字段不额外套 Surface。
- 一到两个结构化筛选字段与列表标题、工具栏、DataGrid 和 Pagination 归入同一个列表堆栈;少量高频字段使用紧凑行内筛选,单个低频字段可由列表工具栏的 HeroUI Popover 按需展开,不单独占用 Surface。
- 列表标题、工具栏与 Pagination 透明无框;DataGrid 自己拥有 20px 数据 Surface。
- 长列表使用 DataGrid 的 `data-table-scroll` 滚动容器:横向滚动保留在表格内,纵向使用
`overscroll-behavior-y: auto`。滚到首行或末行后继续滚动会自然交给页面容器,确保顶部工具栏和底部分页始终可达。
- 页面一级模块间距 32px,普通模块 24px,紧密列表/KPI/工具栏 16px;移动端分别收敛为 24px/16px。
## 控件
......@@ -46,7 +61,7 @@
- 普通业务文字 Button 不带装饰图标;主操作在最右。
- 详情/表单使用 HeroUI Modal;查看态整组字段放一个 secondary Surface,编辑态移除该 Surface。
- DataGrid 使用原生 selection、排序、固定列、横向滚动、ActionBar、EmptyState 和 compound Pagination。
- 流程业务列表默认每页 10 条,分页条使用 HeroUI Autocomplete 提供 10/20/50/100 条选择;通知中心仍固定每页 10 条。
- 流程业务列表默认每页 20 条,分页条使用宽度 128px 的 HeroUI Autocomplete 提供 20/50/100 条选择;通知中心仍固定每页 10 条。
## 主题与资产
......
......@@ -37,8 +37,10 @@
- 筛选使用 HeroUI SearchField/Autocomplete,弹窗使用 Modal/AlertDialog,分页固定 10 条并使用 compound Pagination。
- 详情查看态只保留一个 `Surface secondary`;页面不再定义组件背景、颜色、描边、圆角、阴影或交互状态。
- 删除不可达的 Catalyst、RecordDetailDrawer、record-workbench、FilterSelect 和旧组件样式文件,旧组件引用扫描为零。
- `theme.css` 与技能权威副本逐字节一致;`tokens.css`、`shell.css`、`tailwind.css` 与审计项目管理基准一致。
- `theme.css` 沿用技能权威结构,并按本次鑫元品牌专项将 HeroUI 主色固定为 `#DA241A`;`tokens.css`、`shell.css`、`tailwind.css` 与审计项目管理基准一致。
- 本应用无业务图表页面,图表迁移和 Tooltip 验收标记为不适用,不新增虚构图表。
- 共享图表 Token 已专项收敛为鑫元红及低饱和暖色、中性色、灰蓝色序列;不存在绿色或彩虹式默认系列。
- HeroUI `accent/focus/primary` 和共享 `rc-brand-primary` 已固定为 `#DA241A`,浅色/深色模式均保持一致。
## 本次共享同步
......@@ -50,7 +52,7 @@
- 未改变路由、Subject、API URL、请求方法、payload、metadata 字段或 Reference ID-only 协议。
- 未改变模板同步、分组回写、取消监控、动态列生成、附件链接获取、权限判定、保存回调或请求时机。
- 流程列表默认每页 10 条,并新增 HeroUI Autocomplete 条数选择器(10/20/50/100);查询仍使用原有 `page` / `page_size` 协议。
- 流程列表默认每页 20 条,并新增 128px HeroUI Autocomplete 条数选择器(20/50/100);查询仍使用原有 `page` / `page_size` 协议。
- 401 仍在相同响应时机清理登录态并跳转登录页,只修正为带 Umi base 的应用内地址。
## 通知中心审计
......
......@@ -744,7 +744,12 @@
max-height: min(60dvh, 42rem);
overflow-x: auto;
overflow-y: auto;
overscroll-behavior: contain;
/* Keep horizontal overflow inside the grid, but let vertical wheel/touch
scrolling continue into the page once the last row is reached. This
keeps the pagination area reachable without requiring a second gesture
surface or a nested scroll lock. */
overscroll-behavior-x: contain;
overscroll-behavior-y: auto;
}
/* DataGrid adds a non-scrolling resize wrapper. Let sticky headers use the
......
......@@ -11,7 +11,24 @@
[data-theme="light"],
[data-theme="default"] {
/* Theme Colors (Light Mode) */
--accent: oklch(57.14% 0.2156 29.20);
/* Xinyuan brand primary; HeroUI accent remains stable in every mode. */
--accent: #DA241A;
--accent-hover: #C91F17;
--accent-pressed: #B51B14;
--accent-disabled: color-mix(in srgb, #DA241A 42%, transparent);
--primary: #DA241A;
--primary-foreground: #ffffff;
--primary-50: #FFF4F3;
--primary-100: #FFE3E0;
--primary-200: #FFC7C2;
--primary-300: #F89A92;
--primary-400: #EB675D;
--primary-500: #DA241A;
--primary-600: #C91F17;
--primary-700: #B51B14;
--primary-800: #8F1712;
--primary-900: #6F120F;
--primary-950: #480B09;
--accent-foreground: oklch(99.11% 0 0);
--background: oklch(97.02% 0.0000 29.20);
--border: oklch(90.00% 0.0000 29.20);
......@@ -23,7 +40,7 @@
--field-border: transparent;
--field-foreground: oklch(21.03% 0.0000 29.20);
--field-placeholder: oklch(55.17% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20);
--focus: #DA241A;
--foreground: oklch(21.03% 0.0000 29.20);
--muted: oklch(55.17% 0.0000 29.20);
--overlay: oklch(100.00% 0.0000 29.20);
......@@ -32,8 +49,8 @@
--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: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.85);
--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);
......@@ -70,22 +87,23 @@
* Ant Design remains the icon source. Resolve these CSS variables to
* concrete colors before assigning them to an ECharts option object.
*/
--xinyuan-chart-1: #d92d20;
--xinyuan-chart-2: #ef4444;
--xinyuan-chart-3: #f97316;
--xinyuan-chart-4: #f59e0b;
--xinyuan-chart-5: #22c55e;
--xinyuan-chart-6: #14b87a;
--xinyuan-chart-7: #06b6d4;
--xinyuan-chart-8: #0ea5e9;
--xinyuan-chart-9: #3b82f6;
--xinyuan-chart-10: #6366f1;
--xinyuan-chart-11: #8b5cf6;
--xinyuan-chart-12: #7c3aed;
--xinyuan-chart-13: #b42318;
--xinyuan-chart-14: #84cc16;
--xinyuan-chart-15: #a855f7;
--xinyuan-chart-16: #64748b;
/* Ordered low-conflict palette: brand red, warm neutrals, gray-blue. */
--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: #5F5A61;
--xinyuan-chart-grid: #e8e8e8;
--xinyuan-chart-axis: #737373;
--xinyuan-chart-label: #737373;
......@@ -101,7 +119,24 @@
[data-theme="dark"] {
color-scheme: dark;
/* Theme Colors (Dark Mode) */
--accent: oklch(57.14% 0.2156 29.20);
/* Keep brand identity fixed in dark mode; surfaces provide contrast. */
--accent: #DA241A;
--accent-hover: #C91F17;
--accent-pressed: #B51B14;
--accent-disabled: color-mix(in srgb, #DA241A 42%, transparent);
--primary: #DA241A;
--primary-foreground: #ffffff;
--primary-50: #FFF4F3;
--primary-100: #FFE3E0;
--primary-200: #FFC7C2;
--primary-300: #F89A92;
--primary-400: #EB675D;
--primary-500: #DA241A;
--primary-600: #C91F17;
--primary-700: #B51B14;
--primary-800: #8F1712;
--primary-900: #6F120F;
--primary-950: #480B09;
--accent-foreground: oklch(99.11% 0 0);
--background: oklch(12.00% 0.0000 29.20);
--border: oklch(28.00% 0.0000 29.20);
......@@ -113,7 +148,7 @@
--field-border: transparent;
--field-foreground: oklch(99.11% 0.0000 29.20);
--field-placeholder: oklch(70.50% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20);
--focus: #DA241A;
--foreground: oklch(99.11% 0.0000 29.20);
--muted: oklch(70.50% 0.0000 29.20);
--overlay: oklch(21.03% 0.0000 29.20);
......@@ -122,8 +157,8 @@
--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: oklch(73.29% 0.1935 123.85);
--success-foreground: oklch(21.03% 0.0059 123.85);
--success: #74879A;
--success-foreground: #ffffff;
--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);
......@@ -132,4 +167,31 @@
--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);
/* Chart tokens stay identical across light and dark modes. */
--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: #5F5A61;
--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;
}
......@@ -2,9 +2,12 @@
.light,
.default,
[data-theme='light'],
[data-theme='default'] {
[data-theme='default'],
.dark,
[data-theme='dark'] {
/* Xinyuan aliases and extension tokens. HeroUI core tokens are owned by theme.css. */
--rc-brand-primary: var(--accent);
--rc-brand-primary: #DA241A;
--rc-brand-on-primary: #ffffff;
--rc-brand-light: color-mix(in oklab, var(--accent) 84%, white);
--rc-brand-dark: color-mix(in oklab, var(--accent) 82%, black);
......@@ -92,14 +95,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: var(--rc-chart-11);
--rc-metric-tone-success: var(--rc-chart-2);
--rc-metric-tone-teal: var(--rc-chart-9);
--rc-metric-tone-danger: var(--rc-chart-1);
--rc-metric-tone-warning: var(--rc-chart-6);
--rc-metric-tone-violet: var(--rc-chart-8);
--rc-metric-tone-indigo: var(--rc-chart-12);
--rc-metric-tone-coral: var(--rc-chart-3);
--rc-metric-tone-neutral: var(--muted);
--rc-metric-tone-info-soft: color-mix(
in oklab,
......@@ -167,9 +170,9 @@
--rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: var(--rc-chart-1);
--rc-chart-warning: var(--rc-chart-3);
--rc-chart-positive: var(--rc-chart-7);
--rc-chart-comparison: var(--rc-chart-5);
--rc-chart-neutral: var(--rc-chart-8);
--rc-chart-positive: var(--rc-chart-2);
--rc-chart-comparison: var(--rc-chart-11);
--rc-chart-neutral: var(--rc-chart-9);
--rc-chart-legend-font-size: var(--xinyuan-chart-legend-font-size);
--rc-chart-legend-line-height: var(--xinyuan-chart-legend-line-height);
}
......@@ -10,8 +10,8 @@ import { addTemplatesToGroup, createProcessGroup, loadProcessGroup, loadProcessM
import { useEffect, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'umi';
const PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
const PAGE_SIZE = 20;
const PAGE_SIZE_OPTIONS = [20, 50, 100];
const EMPTY_DEPARTMENT_KEY = '__all_departments__';
function Metric({ label, value, icon: Icon, tone }) {
......@@ -44,7 +44,7 @@ function PagePagination({ page, pageSize, total, onPageChange, onPageSizeChange
<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} 条,第 {total ? page + 1 : 0} / {total ? totalPages : 0} 页</span>
<XinyuanAutocomplete aria-label="每页条数" className="w-24 shrink-0" options={PAGE_SIZE_OPTIONS.map((value) => ({ id: String(value), label: `${value} 条/页` }))} placeholder="每页条数" selectedKey={String(pageSize)} showSearch={false} valueText={`${pageSize} 条/页`} variant="secondary" onSelectionChange={(key) => onPageSizeChange(Number(key))} />
<XinyuanAutocomplete aria-label="每页条数" className="w-32 shrink-0" options={PAGE_SIZE_OPTIONS.map((value) => ({ id: String(value), label: `${value} 条/页` }))} placeholder="每页条数" selectedKey={String(pageSize)} showSearch={false} valueText={`${pageSize} 条/页`} variant="secondary" onSelectionChange={(key) => onPageSizeChange(Number(key))} />
</div>
<Pagination className="ml-auto w-auto shrink-0" size="sm">
<Pagination.Content>
......@@ -240,15 +240,15 @@ export default function ProcessMonitorPage() {
</div>
<div className="rc-list-data-stack">
<section className="rc-list-data-grid"><DataGrid allowsColumnResize aria-label="流程监控模板列表" className="pm-data-grid" columns={listColumns} contentClassName="min-w-[1074px]" data={loading ? [] : pagedTemplates} getRowId={(template) => template._id || template.name} renderEmptyState={() => loading ? <div className="py-8 text-sm text-muted">正在加载流程监控列表...</div> : <DataEmptyState title={activeGroup ? '当前分组还没有监控模板' : '暂无可展示模板'} action={canManage && activeGroup ? <Button size="sm" variant="secondary" onPress={() => setAddTemplateOpen(true)}>添加监控模板</Button> : null} />} scrollContainerClassName="pm-data-grid-scroll" verticalAlign="middle" /></section>
<section className="rc-list-data-grid rc-data-table-sticky"><DataGrid allowsColumnResize aria-label="流程监控模板列表" className="pm-data-grid" columns={listColumns} contentClassName="min-w-[1074px]" data={loading ? [] : pagedTemplates} getRowId={(template) => template._id || template.name} renderEmptyState={() => loading ? <div className="py-8 text-sm text-muted">正在加载流程监控列表...</div> : <DataEmptyState title={activeGroup ? '当前分组还没有监控模板' : '暂无可展示模板'} action={canManage && activeGroup ? <Button size="sm" variant="secondary" onPress={() => setAddTemplateOpen(true)}>添加监控模板</Button> : null} />} scrollContainerClassName="data-table-scroll" verticalAlign="middle" /></section>
{!loading ? <div className="pm-pagination-bar"><PagePagination page={listPage} pageSize={pageSize} total={filteredTemplates.length} onPageChange={setListPage} onPageSizeChange={(nextPageSize) => { if (PAGE_SIZE_OPTIONS.includes(nextPageSize)) setPageSize(nextPageSize); }} /></div> : null}
</div>
</section>
<FormModal isOpen={createGroupOpen} onOpenChange={setCreateGroupOpen} title="新建流程监控分组" footer={<><Button isDisabled={submitting} size="sm" variant="secondary" onPress={() => setCreateGroupOpen(false)}>取消</Button><Button isPending={submitting} size="sm" variant="primary" onPress={handleCreateGroup}>创建分组</Button></>}><div className="grid gap-1.5"><Label>分组名称</Label><Input fullWidth variant="secondary" value={createGroupTitle} onChange={(event) => setCreateGroupTitle(event.target.value)} placeholder="例如:投资业务流程" /></div></FormModal>
<FormModal isOpen={addTemplateOpen} onOpenChange={(open) => { setAddTemplateOpen(open); if (!open) { setSelectedTemplateIds([]); setTemplatePickerKeyword(''); } }} size="4xl" title="添加流程监控模板" footer={<><Button isDisabled={submitting} size="sm" variant="secondary" onPress={() => setAddTemplateOpen(false)}>取消</Button><Button isDisabled={!activeGroup} isPending={submitting} size="sm" variant="primary" onPress={handleAddTemplates}>确认添加</Button></>}>
<div className="grid gap-4"><SearchField fullWidth value={templatePickerKeyword} variant="secondary" onChange={setTemplatePickerKeyword}><SearchField.Group><SearchField.SearchIcon><Search size={16} /></SearchField.SearchIcon><SearchField.Input placeholder="搜索模板标题" /><SearchField.ClearButton aria-label="清空搜索"><Close size={14} /></SearchField.ClearButton></SearchField.Group></SearchField><Surface className="max-h-80 overflow-y-auto p-3" variant="secondary"><div className="grid gap-2">{pickerTemplates.map((template) => { const isDisabled = currentGroupExternalIds.has(template._id); const isSelected = selectedTemplateIds.includes(template._id); return <Checkbox key={template._id} isDisabled={isDisabled} isSelected={isSelected} onChange={(selected) => setSelectedTemplateIds((current) => selected ? [...current, template._id] : current.filter((id) => id !== template._id))}><Checkbox.Content><Checkbox.Control><Checkbox.Indicator><Check size={12} /></Checkbox.Indicator></Checkbox.Control><span className="min-w-0"><span className="block truncate text-sm">{template.title}</span><span className="block truncate text-xs text-muted">模板 ID:{template._id}{isDisabled ? ' · 当前分组已添加' : ''}</span></span></Checkbox.Content></Checkbox>; })}{!pickerTemplates.length ? <div className="py-5 text-center text-sm text-muted">没有找到匹配的模板</div> : null}</div></Surface></div>
<FormModal isOpen={addTemplateOpen} onOpenChange={(open) => { setAddTemplateOpen(open); if (!open) { setSelectedTemplateIds([]); setTemplatePickerKeyword(''); } }} size="2xl" title="添加流程监控模板" footer={<><Button isDisabled={submitting} size="sm" variant="secondary" onPress={() => setAddTemplateOpen(false)}>取消</Button><Button isDisabled={!activeGroup} isPending={submitting} size="sm" variant="primary" onPress={handleAddTemplates}>确认添加</Button></>}>
<div className="grid gap-4"><SearchField fullWidth value={templatePickerKeyword} variant="secondary" onChange={setTemplatePickerKeyword}><SearchField.Group><SearchField.SearchIcon><Search size={16} /></SearchField.SearchIcon><SearchField.Input placeholder="搜索模板标题" /><SearchField.ClearButton aria-label="清空搜索"><Close size={14} /></SearchField.ClearButton></SearchField.Group></SearchField><Surface className="max-h-80 overflow-y-auto rounded-2xl p-3" variant="default"><div className="grid gap-2">{pickerTemplates.map((template) => { const isDisabled = currentGroupExternalIds.has(template._id); const isSelected = selectedTemplateIds.includes(template._id); return <Checkbox key={template._id} isDisabled={isDisabled} isSelected={isSelected} onChange={(selected) => setSelectedTemplateIds((current) => selected ? [...current, template._id] : current.filter((id) => id !== template._id))}><Checkbox.Content><Checkbox.Control><Checkbox.Indicator><Check size={12} /></Checkbox.Indicator></Checkbox.Control><span className="min-w-0"><span className="block truncate text-sm">{template.title}</span><span className="block truncate text-xs text-muted">模板 ID:{template._id}{isDisabled ? ' · 当前分组已添加' : ''}</span></span></Checkbox.Content></Checkbox>; })}{!pickerTemplates.length ? <div className="py-5 text-center text-sm text-muted">没有找到匹配的模板</div> : null}</div></Surface></div>
</FormModal>
<FormModal isOpen={editTemplateOpen} onOpenChange={setEditTemplateOpen} size="2xl" title="编辑模板监控配置" footer={<><Button isDisabled={submitting} size="sm" variant="secondary" onPress={() => setEditTemplateOpen(false)}>取消</Button><Button isPending={submitting} size="sm" variant="primary" onPress={handleUpdateTemplate}>保存配置</Button></>}>
......
......@@ -23,8 +23,8 @@ import {
import { useEffect, useMemo, useState } from 'react';
import { useParams, useSearchParams } from 'umi';
const PAGE_SIZE = 10;
const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
const PAGE_SIZE = 20;
const PAGE_SIZE_OPTIONS = [20, 50, 100];
const FIELD_TYPE_OPTIONS = [
{ id: 'text', label: '文本' },
{ id: 'member', label: '人员' },
......@@ -81,7 +81,7 @@ function PagePagination({ page, pageSize, total, onPageChange, onPageSizeChange
<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} 条,第 {total ? page : 0} / {total ? totalPages : 0} 页</span>
<XinyuanAutocomplete aria-label="每页条数" className="w-24 shrink-0" options={PAGE_SIZE_OPTIONS.map((value) => ({ id: String(value), label: `${value} 条/页` }))} placeholder="每页条数" selectedKey={String(pageSize)} showSearch={false} valueText={`${pageSize} 条/页`} variant="secondary" onSelectionChange={(key) => onPageSizeChange(Number(key))} />
<XinyuanAutocomplete aria-label="每页条数" className="w-32 shrink-0" options={PAGE_SIZE_OPTIONS.map((value) => ({ id: String(value), label: `${value} 条/页` }))} placeholder="每页条数" selectedKey={String(pageSize)} showSearch={false} valueText={`${pageSize} 条/页`} variant="secondary" onSelectionChange={(key) => onPageSizeChange(Number(key))} />
</div>
<Pagination className="ml-auto w-auto shrink-0" size="sm">
<Pagination.Content>
......@@ -188,7 +188,7 @@ export default function ProcessTemplateListPage() {
<div className="rc-list-action-bar"><div className="flex items-center gap-1.5"><h2>实例列表</h2><TitleInfo label="实例列表" content="按模板展示实际提交的流程实例,可通过操作列查看单条详情。" /></div></div>
<div className="flex flex-wrap items-center justify-between gap-3"><SearchField aria-label="搜索表单名称" className="w-full sm:w-96" value={keyword} onChange={(value) => { setPage(1); setKeyword(value); }}><SearchField.Group><SearchField.SearchIcon><Search size={16} /></SearchField.SearchIcon><SearchField.Input placeholder="搜索表单名称" /><SearchField.ClearButton aria-label="清空关键词"><Close size={14} /></SearchField.ClearButton></SearchField.Group></SearchField></div>
<div className="rc-list-data-stack">
<section className="rc-list-data-grid"><DataGrid allowsColumnResize aria-label="流程实例列表" className="pm-data-grid" columns={instanceColumns} contentClassName="min-w-[760px]" data={loading ? [] : instances} getRowId={(record) => record._id || record.name} renderEmptyState={() => loading ? <div className="py-8 text-sm text-muted">正在加载模板实例...</div> : <DataEmptyState />} scrollContainerClassName="pm-data-grid-scroll" verticalAlign="middle" /></section>
<section className="rc-list-data-grid rc-data-table-sticky"><DataGrid allowsColumnResize aria-label="流程实例列表" className="pm-data-grid" columns={instanceColumns} contentClassName="min-w-[760px]" data={loading ? [] : instances} getRowId={(record) => record._id || record.name} renderEmptyState={() => loading ? <div className="py-8 text-sm text-muted">正在加载模板实例...</div> : <DataEmptyState />} scrollContainerClassName="data-table-scroll" verticalAlign="middle" /></section>
{!loading ? <div className="pm-pagination-bar"><PagePagination page={page} pageSize={pageSize} total={total} onPageChange={setPage} onPageSizeChange={(nextPageSize) => { if (PAGE_SIZE_OPTIONS.includes(nextPageSize)) { setPageSize(nextPageSize); setPage(1); } }} /></div> : null}
</div>
</section>
......
......@@ -118,7 +118,7 @@ Shell 中的应用切换入口继续读取当前登录用户可访问应用,
- 读取当前监控模板记录 `audit_related_progress`
- 根据模板的 `metadata.fields_definition` 动态生成列表列
- 查询 `audit_progress_info` 并按 `metadata.related_progress` 过滤
- 支持关键字搜索和每页 10/20/50/100 条的分页切换,默认每页 10 条
- 支持关键字搜索和每页 20/50/100 条的分页切换,默认每页 20 条
- 支持批量触发附件下载
- 支持维护实例列表的显示字段定义
- 打开“定义显示字段”时,已有字段按配置回显;无字段配置时默认展示一行空字段,字段名和字段类型分别使用“请输入字段名”“请选择”提示,不额外展示重复的字段表头
......@@ -134,7 +134,7 @@ Shell 中的应用切换入口继续读取当前登录用户可访问应用,
- 使用 HeroUI Pro DataGrid 生成动态列并承载选择状态
- 表单名称列仅展示文本,通过操作列的“查看”图标在当前列表页打开单条流程实例详情 Modal;详情路由仍保留用于直接访问和兼容既有链接
- Pagination 保留原有页码语义和 `page` / `page_size` 请求参数,条数选择器支持 10/20/50/100 条
- Pagination 保留原有页码语义和 `page` / `page_size` 请求参数,条数选择器支持 20/50/100 条,宽度为 128px
- 有选中记录时由 HeroUI Pro ActionBar 承载批量附件下载;空数据使用 HeroUI Pro EmptyState
### 5.3 流程实例详情页
......@@ -172,7 +172,7 @@ Shell 中的应用切换入口继续读取当前登录用户可访问应用,
- 旧版实例列表批量下载会打包 zip;当前迁移版使用逐个触发浏览器下载,保持“批量下载附件”能力但不强依赖额外前端压缩库
- 旧版详情页曾预留字段类型即时修改能力,但实际入口已注释;当前迁移版不开放详情页内字段类型编辑,统一回到模板实例列表页的“定义显示字段”
- 列表按统一设计合同默认每页 10 条,并通过 HeroUI 条数选择器支持 10/20/50/100 条;后端分页协议没有变化
- 列表按统一设计合同默认每页 20 条,并通过 HeroUI 条数选择器支持 20/50/100 条;后端分页协议没有变化
- 401 响应仍在原时机清理登录态,只将登录跳转修正为包含 `/oa_process_monitor_new/` base 的应用内地址
---
......
......@@ -82,8 +82,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 srgb, var(--rc-brand-primary) 24%, transparent);
--rc-login-field-focus-ring: color-mix(in srgb, var(--rc-brand-primary) 16%, transparent);
}
body .login-brand img {
......
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