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

feat: open process instance details as page

parent c8685ec9
......@@ -58,5 +58,5 @@ export default function ProcessInstanceDetailContent({ data, loading = false, wi
{details.map((field, index) => <DetailField key={`${field.label}-${index}`} label={field.label || `字段 ${index + 1}`} fullWidth={field?.type === 'file' || String(field?.value || '').length > 80}>{resolveDetailValue(field, fieldMap, lookups)}</DetailField>)}
</div>;
return withSurface ? <Surface className="px-5 py-5 sm:px-6 sm:py-6" variant="secondary">{fieldGrid}</Surface> : fieldGrid;
return withSurface ? <Surface className="rounded-2xl px-5 py-5 sm:px-6 sm:py-6" variant="secondary">{fieldGrid}</Surface> : fieldGrid;
}
......@@ -2,7 +2,6 @@ import {
loadProcessInstanceDetail,
} from '@/pages/process_monitor/processMonitorService';
import ProcessInstanceDetailContent from '@/components/ProcessInstanceDetailContent';
import { TitleInfo } from '@/components/XinyuanUi';
import { Alert } from '@heroui/react';
import { useEffect, useMemo, useState } from 'react';
import { useParams } from 'umi';
......@@ -27,7 +26,7 @@ export default function ProcessDetailPage() {
return (
<div className="rc-page-stack">
{errorMessage ? <Alert status="danger"><Alert.Indicator /><Alert.Content><Alert.Title>详情加载失败</Alert.Title><Alert.Description>{errorMessage}</Alert.Description></Alert.Content></Alert> : null}
<div className="rc-list-action-bar"><div className="flex items-center gap-1.5"><h2>流程实例信息</h2><TitleInfo label="流程实例信息" content="展示该实例的流程信息和动态表单字段,附件保留独立下载入口。" /></div></div>
<div className="rc-list-action-bar"><h2>流程实例信息</h2></div>
<ProcessInstanceDetailContent data={detailData} loading={loading} withSurface />
</div>
......
import { Close, DataStore, Delete, Eye, FieldTime, FileText, Filter, FolderOpen, Search } from '@/components/AppIcons';
import ProcessInstanceDetailContent from '@/components/ProcessInstanceDetailContent';
import { TableActionButton, TableActionGroup } from '@/components/TableActionButton';
import { TitleInfo } from '@/components/XinyuanUi';
import XinyuanAutocomplete from '@/components/XinyuanAutocomplete';
......@@ -15,13 +14,12 @@ import {
findDetailField,
formatDateTime,
loadTemplateMonitorPage,
loadProcessInstanceDetail,
normalizeDisplayFieldDefinitions,
queryProcessInstances,
updateTemplateDisplayFields,
} from '@/pages/process_monitor/processMonitorService';
import { useEffect, useMemo, useState } from 'react';
import { useParams, useSearchParams } from 'umi';
import { useNavigate, useParams, useSearchParams } from 'umi';
const PAGE_SIZE = 20;
const PAGE_SIZE_OPTIONS = [20, 50, 100];
......@@ -96,6 +94,7 @@ function PagePagination({ page, pageSize, total, onPageChange, onPageSizeChange
export default function ProcessTemplateListPage() {
const { processId } = useParams();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const tabName = searchParams.get('tab') || '';
const [loading, setLoading] = useState(true);
......@@ -114,10 +113,6 @@ export default function ProcessTemplateListPage() {
const [fieldDialogOpen, setFieldDialogOpen] = useState(false);
const [fieldDrafts, setFieldDrafts] = useState([]);
const [fieldSaving, setFieldSaving] = useState(false);
const [detailDialogOpen, setDetailDialogOpen] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [detailError, setDetailError] = useState('');
const [detailRecord, setDetailRecord] = useState(null);
const loadTemplateMeta = async () => {
const result = await loadTemplateMonitorPage(processId);
......@@ -139,24 +134,12 @@ export default function ProcessTemplateListPage() {
const instanceColumns = useMemo(() => [
{ id: 'title', header: '表单名称', isRowHeader: true, width: '1fr', minWidth: 220, allowsResizing: false, pinned: 'start', cell: (record) => <span className="pm-data-grid-primary">{record.title}</span> },
...fieldsDefinition.map((field) => ({ id: `${field.display_name}-${field.type}`, header: field.display_name, width: 220, minWidth: 180, allowsResizing: false, cell: (record) => <span className="pm-data-grid-text">{renderFieldValue(field, record, lookups)}</span> })),
{ id: 'actions', header: '操作', width: 80, minWidth: 80, maxWidth: 80, allowsResizing: false, align: 'start', pinned: 'end', cell: (record) => <TableActionGroup><TableActionButton icon={Eye} label="查看" onPress={() => openDetailDialog(record)} /></TableActionGroup> },
], [fieldsDefinition, lookups]);
const openDetailDialog = async (record) => {
{ id: 'actions', header: '操作', width: 80, minWidth: 80, maxWidth: 80, allowsResizing: false, align: 'start', pinned: 'end', cell: (record) => <TableActionGroup><TableActionButton icon={Eye} label="查看" onPress={() => {
if (!record?._id) return;
setDetailDialogOpen(true);
setDetailLoading(true);
setDetailError('');
setDetailRecord(null);
try {
const detail = await loadProcessInstanceDetail(record._id);
setDetailRecord(detail);
} catch (error) {
setDetailError(error?.message || '加载流程详情失败');
} finally {
setDetailLoading(false);
}
};
const tabQuery = tabName ? `?tab=${encodeURIComponent(tabName)}` : '';
navigate(`/process_monitor/${processId}/${record._id}${tabQuery}`);
}} /></TableActionGroup> },
], [fieldsDefinition, lookups, navigate, processId, tabName]);
const openFieldDialog = () => {
const normalizedFields = normalizeDisplayFieldDefinitions(fieldsDefinition);
......@@ -197,7 +180,6 @@ export default function ProcessTemplateListPage() {
<div className="grid gap-3">{fieldDrafts.map((field, index) => <div key={field._key} className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_2rem] sm:items-end"><div className="grid gap-1.5"><Label>字段名</Label><Input aria-label={`字段 ${index + 1} 名称`} fullWidth variant="secondary" value={field.display_name} onChange={(event) => setFieldDrafts((current) => current.map((item, itemIndex) => itemIndex === index ? { ...item, display_name: event.target.value } : item))} placeholder="请输入字段名" /></div><div className="grid gap-1.5"><Label>字段类型</Label><XinyuanAutocomplete aria-label={`字段 ${index + 1} 类型`} options={FIELD_TYPE_OPTIONS} placeholder="请选择" selectedKey={field.type} onSelectionChange={(key) => setFieldDrafts((current) => current.map((item, itemIndex) => itemIndex === index ? { ...item, type: String(key || '') } : item))} /></div><Tooltip delay={0}><Button isIconOnly aria-label={`删除字段 ${index + 1}`} size="sm" variant="danger-soft" onPress={() => setFieldDrafts((current) => current.filter((_, itemIndex) => itemIndex !== index))}><Delete size={16} /></Button><Tooltip.Content>删除字段</Tooltip.Content></Tooltip></div>)}{!fieldDrafts.length ? <div className="py-6 text-center text-sm text-muted">暂未添加显示字段</div> : null}<Button fullWidth size="sm" variant="secondary" onPress={() => setFieldDrafts((current) => [...current, createFieldDraft()])}>添加字段</Button></div>
</Modal.Body><Modal.Footer><Button isDisabled={fieldSaving} size="sm" variant="secondary" onPress={() => setFieldDialogOpen(false)}>取消</Button><Button isPending={fieldSaving} size="sm" variant="primary" onPress={saveFieldDefinitions}>保存字段</Button></Modal.Footer></Modal.Dialog></Modal.Container></Modal.Backdrop></Modal.Root>
<Modal.Root><Modal.Trigger aria-hidden="true" className="sr-only" tabIndex={-1} /><Modal.Backdrop isOpen={detailDialogOpen} onOpenChange={setDetailDialogOpen} variant="blur"><Modal.Container placement="center" scroll="inside" size="lg"><Modal.Dialog aria-label="流程实例详情" className="p-7 sm:max-w-[68rem]"><Modal.CloseTrigger aria-label="关闭"><Close size={16} /></Modal.CloseTrigger><Modal.Header><Modal.Heading>流程实例详情</Modal.Heading></Modal.Header><Modal.Body>{detailError ? <Alert status="danger"><Alert.Indicator /><Alert.Content><Alert.Title>详情加载失败</Alert.Title><Alert.Description>{detailError}</Alert.Description></Alert.Content></Alert> : null}<ProcessInstanceDetailContent data={detailRecord} loading={detailLoading} /></Modal.Body><Modal.Footer><Button size="sm" variant="secondary" onPress={() => setDetailDialogOpen(false)}>关闭</Button></Modal.Footer></Modal.Dialog></Modal.Container></Modal.Backdrop></Modal.Root>
</div>
);
}
......@@ -133,7 +133,7 @@ Shell 中的应用切换入口继续读取当前登录用户可访问应用,
列表交互:
- 使用 HeroUI Pro DataGrid 生成动态列并承载选择状态
- 表单名称列仅展示文本,通过操作列的“查看”图标在当前列表页打开单条流程实例详情 Modal;详情路由仍保留用于直接访问和兼容既有链接
- 表单名称列仅展示文本,通过操作列的“查看”图标进入 `/process_monitor/:processId/:application` 独立流程实例详情页;顶部 Navbar 面包屑显示“流程监控 / 实例列表 / 流程详情”,前级节点可点击返回
- Pagination 保留原有页码语义和 `page` / `page_size` 请求参数,条数选择器支持 20/50/100 条,宽度为 128px
- 有选中记录时由 HeroUI Pro ActionBar 承载批量附件下载;空数据使用 HeroUI Pro EmptyState
......
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