Commit 48dae1c1 authored by David Yang's avatar David Yang

feat: 完善延期申请状态展示与流程标识

parent 5a67a51e
import { Button } from '@/catalyst-ui-kit/javascript/button'; import { Button } from '@/catalyst-ui-kit/javascript/button';
import { Badge } from '@/catalyst-ui-kit/javascript/badge';
import { Input } from '@/catalyst-ui-kit/javascript/input'; import { Input } from '@/catalyst-ui-kit/javascript/input';
import { Select } from '@/catalyst-ui-kit/javascript/select'; import { Select } from '@/catalyst-ui-kit/javascript/select';
import { import {
...@@ -23,7 +24,12 @@ import { ...@@ -23,7 +24,12 @@ import {
import { normalizeText, toArray } from '@/utils/subjectView'; import { normalizeText, toArray } from '@/utils/subjectView';
import clsx from 'clsx'; import clsx from 'clsx';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { LockClosedIcon } from '@heroicons/react/24/outline'; import {
ArrowPathIcon,
CalendarDaysIcon,
ClipboardDocumentCheckIcon,
LockClosedIcon,
} from '@heroicons/react/24/outline';
import { useContext, useEffect, useMemo, useState } from 'react'; import { useContext, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'umi'; import { useSearchParams } from 'umi';
import { UserContext } from '@/wrapper/Auth'; import { UserContext } from '@/wrapper/Auth';
...@@ -1231,24 +1237,83 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1231,24 +1237,83 @@ export default function ProblemRectificationFlowTablePage() {
rows.every((row) => !row.isLocked && row.auditStatus === AUDIT_STATUS_APPROVED) && rows.every((row) => !row.isLocked && row.auditStatus === AUDIT_STATUS_APPROVED) &&
senderId === currentUserId && senderId === currentUserId &&
rows.some((row) => row.finalAuditStatus !== AUDIT_STATUS_APPROVED); rows.some((row) => row.finalAuditStatus !== AUDIT_STATUS_APPROVED);
const flowModeLabel = isDelayFlow
? flowMode === FLOW_MODE_DELAY_DEPARTMENT
? '按责任部门'
: '问题级申请'
: flowMode === FLOW_MODE_DEPARTMENT
? '按责任部门'
: '批量处理';
const flowPresentation = isDelayFlow
? {
badge: '延期申请流程',
title: '问题延期申请',
detailTitle: '延期申请明细',
description: '申请新的整改完成时间,并记录延期事由',
accentClass: 'bg-amber-500',
iconClass: 'bg-amber-100 text-amber-700 ring-amber-200',
sectionClass: 'border-amber-200/80 bg-amber-50/50',
badgeColor: 'amber',
icon: CalendarDaysIcon,
}
: {
badge: '问题整改流程',
title: '问题整改处理',
detailTitle: '问题整改明细',
description: '更新整改进度、预计完成时间、整改情况与证明材料',
accentClass: 'bg-sky-600',
iconClass: 'bg-sky-100 text-sky-700 ring-sky-200',
sectionClass: 'border-sky-200/80 bg-sky-50/50',
badgeColor: 'sky',
icon: ClipboardDocumentCheckIcon,
};
const FlowIcon = flowPresentation.icon;
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6">
<section className="rounded-[30px] border border-white/80 bg-white/95 px-6 py-5 shadow-sm"> <section
<div className="flex flex-wrap items-start justify-between gap-4"> className={clsx(
<div> 'overflow-hidden rounded-[30px] border bg-white/95 shadow-sm',
<div className="text-xs font-semibold uppercase tracking-[0.24em] text-rose-600"> isDelayFlow ? 'border-amber-200/80' : 'border-sky-200/80',
Problem Rectification Flow )}
</div> >
<div className={clsx('h-1.5 w-full', flowPresentation.accentClass)} />
<div className="flex flex-wrap items-start justify-between gap-4 px-6 py-5">
<div className="flex min-w-0 items-start gap-4">
<span
className={clsx(
'inline-flex size-11 shrink-0 items-center justify-center rounded-lg ring-1',
flowPresentation.iconClass,
)}
>
<FlowIcon className="size-6" aria-hidden="true" />
</span>
<div className="min-w-0">
<Badge color={flowPresentation.badgeColor}>
{flowPresentation.badge}
</Badge>
<h1 className="mt-2 text-2xl font-semibold tracking-tight text-slate-950"> <h1 className="mt-2 text-2xl font-semibold tracking-tight text-slate-950">
{isDelayFlow ? '延期申请处理' : '问题整改表格处理'} {flowPresentation.title}
</h1> </h1>
<p className="mt-2 text-sm text-slate-500"> <p className="mt-1 text-sm font-medium text-slate-600">
当前流程:{messageId || '-'};流程模式: {flowPresentation.description}
{isDelayFlow ? '问题延期申请' : flowMode === FLOW_MODE_DEPARTMENT ? '按责任部门' : '批量简单推送'}
</p> </p>
<dl className="mt-3 flex flex-wrap gap-x-6 gap-y-2 text-xs text-slate-500">
<div className="flex min-w-0 items-center gap-2">
<dt>流程编号</dt>
<dd className="min-w-0 break-all font-mono text-slate-700">
{messageId || '-'}
</dd>
</div>
<div className="flex items-center gap-2">
<dt>处理方式</dt>
<dd className="font-medium text-slate-700">{flowModeLabel}</dd>
</div>
</dl>
</div>
</div> </div>
<Button outline onClick={loadFlow} disabled={loading || Boolean(submittingKey)}> <Button outline onClick={loadFlow} disabled={loading || Boolean(submittingKey)}>
<ArrowPathIcon data-slot="icon" />
刷新 刷新
</Button> </Button>
</div> </div>
...@@ -1262,6 +1327,29 @@ export default function ProblemRectificationFlowTablePage() { ...@@ -1262,6 +1327,29 @@ export default function ProblemRectificationFlowTablePage() {
) : null} ) : null}
<section className="overflow-hidden rounded-[30px] border border-white/80 bg-white/96 shadow-sm"> <section className="overflow-hidden rounded-[30px] border border-white/80 bg-white/96 shadow-sm">
<div
className={clsx(
'flex flex-wrap items-center justify-between gap-3 border-b px-5 py-4',
flowPresentation.sectionClass,
)}
>
<div className="flex min-w-0 items-center gap-3">
<FlowIcon className="size-5 shrink-0" aria-hidden="true" />
<div className="min-w-0">
<h2 className="text-sm font-semibold text-slate-900">
{flowPresentation.detailTitle}
</h2>
<p className="mt-0.5 text-xs text-slate-600">
{flowPresentation.description}
</p>
</div>
</div>
<Badge color={flowPresentation.badgeColor}>
{loading
? '加载中'
: `${rows.length} 条${isDelayFlow ? '延期申请' : '整改明细'}`}
</Badge>
</div>
{loading ? ( {loading ? (
<div className="px-6 py-14 text-center text-sm text-slate-500">正在加载流程表格...</div> <div className="px-6 py-14 text-center text-sm text-slate-500">正在加载流程表格...</div>
) : rows.length === 0 ? ( ) : rows.length === 0 ? (
......
import { Button } from '@/catalyst-ui-kit/javascript/button'; import { Button } from '@/catalyst-ui-kit/javascript/button';
import { BadgeButton } from '@/catalyst-ui-kit/javascript/badge';
import { import {
Dialog, Dialog,
DialogActions, DialogActions,
...@@ -34,7 +35,12 @@ import { ...@@ -34,7 +35,12 @@ import {
} from '@/services/DataService'; } from '@/services/DataService';
import * as Headless from '@headlessui/react'; import * as Headless from '@headlessui/react';
import { ChevronRightIcon } from '@heroicons/react/20/solid'; import { ChevronRightIcon } from '@heroicons/react/20/solid';
import { LockClosedIcon, LockOpenIcon, TrashIcon } from '@heroicons/react/24/outline'; import {
CalendarDaysIcon,
LockClosedIcon,
LockOpenIcon,
TrashIcon,
} from '@heroicons/react/24/outline';
import clsx from 'clsx'; import clsx from 'clsx';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
...@@ -48,10 +54,13 @@ import { ...@@ -48,10 +54,13 @@ import {
import { import {
DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE, DEFAULT_PROBLEM_RECTIFICATION_PAGE_SIZE,
PROBLEM_RECTIFICATION_SUBJECT, PROBLEM_RECTIFICATION_SUBJECT,
AUDIT_STATUS_APPROVED,
AUDIT_STATUS_PENDING, AUDIT_STATUS_PENDING,
AUDIT_STATUS_REJECTED,
DELAY_REQUEST_FIELD, DELAY_REQUEST_FIELD,
DELAY_REQUEST_ITEMS_FIELD, DELAY_REQUEST_ITEMS_FIELD,
DEPARTMENT_RECTIFICATION_FIELD, DEPARTMENT_RECTIFICATION_FIELD,
EDITOR_STATUS_COMPLETED,
EDITOR_STATUS_PENDING, EDITOR_STATUS_PENDING,
FLOW_MODE_BATCH, FLOW_MODE_BATCH,
FLOW_MODE_DELAY_DEPARTMENT, FLOW_MODE_DELAY_DEPARTMENT,
...@@ -127,7 +136,7 @@ const CREATE_FIELD_OPTION_OVERRIDES = { ...@@ -127,7 +136,7 @@ const CREATE_FIELD_OPTION_OVERRIDES = {
is_repeated_problem: ['是', '否'], is_repeated_problem: ['是', '否'],
}; };
function StatusPill({ text, tone = 'default' }) { function StatusPill({ text, tone = 'default', icon: Icon = null, title }) {
const toneClass = const toneClass =
tone === 'rose' tone === 'rose'
? 'bg-rose-50 text-rose-700 ring-rose-100' ? 'bg-rose-50 text-rose-700 ring-rose-100'
...@@ -138,12 +147,89 @@ function StatusPill({ text, tone = 'default' }) { ...@@ -138,12 +147,89 @@ function StatusPill({ text, tone = 'default' }) {
: 'bg-slate-100 text-slate-600 ring-slate-200'; : 'bg-slate-100 text-slate-600 ring-slate-200';
return ( return (
<span className={clsx('inline-flex rounded-full px-3 py-1 text-xs font-medium ring-1', toneClass)}> <span
className={clsx(
'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ring-1',
toneClass,
)}
title={title}
>
{Icon ? <Icon className="size-3.5 shrink-0" aria-hidden="true" /> : null}
{text || '-'} {text || '-'}
</span> </span>
); );
} }
function getRecordDelayRequests(record) {
return [record?.delayRequest, ...(record?.delayRequestItems || [])].filter(Boolean);
}
function resolveDelayRequestDisplay(record) {
const requests = getRecordDelayRequests(record);
if (requests.length === 0) {
return null;
}
const everyRequestMatches = (field, value) =>
requests.every((request) => request?.[field] === value);
const anyRequestMatches = (field, value) =>
requests.some((request) => request?.[field] === value);
const title = `共 ${requests.length} 条延期申请`;
if (
anyRequestMatches('auditor_status', AUDIT_STATUS_REJECTED) ||
anyRequestMatches('sender_status', AUDIT_STATUS_REJECTED)
) {
return { text: '延期已退回', tone: 'rose', title };
}
if (everyRequestMatches('sender_status', AUDIT_STATUS_APPROVED)) {
return { text: '延期已生效', tone: 'emerald', title };
}
if (everyRequestMatches('auditor_status', AUDIT_STATUS_APPROVED)) {
return { text: '延期待终审', tone: 'amber', title };
}
if (everyRequestMatches('editor_status', EDITOR_STATUS_COMPLETED)) {
return { text: '延期待审批', tone: 'amber', title };
}
const hasProgressedRequest = requests.some(
(request) =>
request?.editor_status === EDITOR_STATUS_COMPLETED ||
request?.auditor_status === AUDIT_STATUS_APPROVED ||
request?.sender_status === AUDIT_STATUS_APPROVED,
);
return {
text: hasProgressedRequest ? '延期处理中' : '延期已发起',
tone: 'amber',
title,
};
}
function resolveDelayStatusTone(status = '') {
if (status === EDITOR_STATUS_COMPLETED || status === AUDIT_STATUS_APPROVED) {
return 'emerald';
}
if (status === AUDIT_STATUS_REJECTED) {
return 'rose';
}
return 'default';
}
function formatDelayDate(value) {
if (!value) {
return '待填写';
}
const parsedDate = dayjs(value);
return parsedDate.isValid() ? parsedDate.format('YYYY-MM-DD') : String(value);
}
function resolveProgressTone(progress = '') { function resolveProgressTone(progress = '') {
if (progress === '已整改' || progress === '已优化') { if (progress === '已整改' || progress === '已优化') {
return 'emerald'; return 'emerald';
...@@ -656,6 +742,7 @@ export default function ProblemRectificationListPage() { ...@@ -656,6 +742,7 @@ export default function ProblemRectificationListPage() {
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [showPushModal, setShowPushModal] = useState(false); const [showPushModal, setShowPushModal] = useState(false);
const [showDelayPushModal, setShowDelayPushModal] = useState(false); const [showDelayPushModal, setShowDelayPushModal] = useState(false);
const [delayDetailRecord, setDelayDetailRecord] = useState(null);
const [cloneTargetRecord, setCloneTargetRecord] = useState(null); const [cloneTargetRecord, setCloneTargetRecord] = useState(null);
const [formValues, setFormValues] = useState({}); const [formValues, setFormValues] = useState({});
const [fillerUsers, setFillerUsers] = useState([]); const [fillerUsers, setFillerUsers] = useState([]);
...@@ -672,7 +759,7 @@ export default function ProblemRectificationListPage() { ...@@ -672,7 +759,7 @@ export default function ProblemRectificationListPage() {
totalPages: 1, totalPages: 1,
}); });
const [gridViewCounts, setGridViewCounts] = useState({}); const [gridViewCounts, setGridViewCounts] = useState({});
const [departmentUserLabels, setDepartmentUserLabels] = useState({}); const [workflowUserLabels, setWorkflowUserLabels] = useState({});
const reloadResources = useCallback(async () => { const reloadResources = useCallback(async () => {
setLoading(true); setLoading(true);
...@@ -700,14 +787,21 @@ export default function ProblemRectificationListPage() { ...@@ -700,14 +787,21 @@ export default function ProblemRectificationListPage() {
useEffect(() => { useEffect(() => {
const userIds = Array.from( const userIds = Array.from(
new Set( new Set(
records.flatMap((record) => records
(record.departmentItems || []).flatMap((item) => [item.editor, item.auditor]), .flatMap((record) => [
).filter(Boolean), ...(record.departmentItems || []).flatMap((item) => [item.editor, item.auditor]),
...getRecordDelayRequests(record).flatMap((item) => [
item.editor,
item.auditor,
item.sender,
]),
])
.filter(Boolean),
), ),
); );
if (userIds.length === 0) { if (userIds.length === 0) {
setDepartmentUserLabels({}); setWorkflowUserLabels({});
return; return;
} }
...@@ -717,7 +811,7 @@ export default function ProblemRectificationListPage() { ...@@ -717,7 +811,7 @@ export default function ProblemRectificationListPage() {
if (cancelled) { if (cancelled) {
return; return;
} }
setDepartmentUserLabels( setWorkflowUserLabels(
Object.fromEntries( Object.fromEntries(
(result?.users || []).map((user) => [ (result?.users || []).map((user) => [
user._id, user._id,
...@@ -728,7 +822,7 @@ export default function ProblemRectificationListPage() { ...@@ -728,7 +822,7 @@ export default function ProblemRectificationListPage() {
}) })
.catch(() => { .catch(() => {
if (!cancelled) { if (!cancelled) {
setDepartmentUserLabels({}); setWorkflowUserLabels({});
} }
}); });
...@@ -854,6 +948,8 @@ export default function ProblemRectificationListPage() { ...@@ -854,6 +948,8 @@ export default function ProblemRectificationListPage() {
[filteredRecords, selectedIds], [filteredRecords, selectedIds],
); );
const hasLockedSelection = selectedRecords.some((record) => record.isLocked); const hasLockedSelection = selectedRecords.some((record) => record.isLocked);
const selectedDelayRequestDisplay =
selectedRecords.length === 1 ? resolveDelayRequestDisplay(selectedRecords[0]) : null;
useEffect(() => { useEffect(() => {
const visibleIds = new Set(filteredRecords.map((item) => item.id)); const visibleIds = new Set(filteredRecords.map((item) => item.id));
...@@ -1594,7 +1690,7 @@ export default function ProblemRectificationListPage() { ...@@ -1594,7 +1690,7 @@ export default function ProblemRectificationListPage() {
isRowExpandable={(record) => record.departmentItems?.length > 0} isRowExpandable={(record) => record.departmentItems?.length > 0}
getExpandedRecordRows={({ record }) => ( getExpandedRecordRows={({ record }) => (
(record.departmentItems || []).map((item) => (record.departmentItems || []).map((item) =>
buildDepartmentDetailRecord(record, item, departmentUserLabels), buildDepartmentDetailRecord(record, item, workflowUserLabels),
) )
)} )}
toolbarActions={[ toolbarActions={[
...@@ -1614,9 +1710,13 @@ export default function ProblemRectificationListPage() { ...@@ -1614,9 +1710,13 @@ export default function ProblemRectificationListPage() {
}, },
{ {
key: 'push-delay-request', key: 'push-delay-request',
label: '推送延期申请', label: selectedDelayRequestDisplay?.text || '推送延期申请',
variant: 'outline', variant: 'outline',
disabled: () => selectedIds.length !== 1 || hasLockedSelection || actionLoading, disabled: () =>
selectedIds.length !== 1 ||
hasLockedSelection ||
Boolean(selectedDelayRequestDisplay) ||
actionLoading,
onClick: openDelayPushModal, onClick: openDelayPushModal,
}, },
{ {
...@@ -1649,6 +1749,14 @@ export default function ProblemRectificationListPage() { ...@@ -1649,6 +1749,14 @@ export default function ProblemRectificationListPage() {
variant: 'plain', variant: 'plain',
onClick: ({ openDetail, record }) => openDetail(record), onClick: ({ openDetail, record }) => openDetail(record),
}, },
{
key: 'view-delay-request',
label: '查看延期申请',
icon: CalendarDaysIcon,
variant: 'outline',
hidden: ({ record }) => !resolveDelayRequestDisplay(record),
onClick: ({ record }) => setDelayDetailRecord(record),
},
{ {
key: 'lock', key: 'lock',
label: '锁定', label: '锁定',
...@@ -1748,9 +1856,34 @@ export default function ProblemRectificationListPage() { ...@@ -1748,9 +1856,34 @@ export default function ProblemRectificationListPage() {
return fieldPreview; return fieldPreview;
} }
const delayRequestDisplay = record.isDepartmentDetailRow
? null
: resolveDelayRequestDisplay(record);
return ( return (
<div className="flex flex-wrap items-center gap-x-2 gap-y-1"> <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
{fieldPreview} {fieldPreview}
{delayRequestDisplay ? (
<BadgeButton
color={
delayRequestDisplay.tone === 'rose'
? 'rose'
: delayRequestDisplay.tone === 'emerald'
? 'emerald'
: 'amber'
}
title={delayRequestDisplay.title}
aria-label={`查看${record.title || record.projectName || '当前问题'}的延期申请详情`}
onClick={(event) => {
event.stopPropagation();
setDelayDetailRecord(record);
}}
>
<CalendarDaysIcon className="size-3.5 shrink-0" aria-hidden="true" />
{delayRequestDisplay.text}
<span className="sr-only">,点击查看填写数据</span>
</BadgeButton>
) : null}
{!record.isDepartmentDetailRow && record.isLocked ? ( {!record.isDepartmentDetailRow && record.isLocked ? (
<StatusPill text="已锁定" tone="amber" /> <StatusPill text="已锁定" tone="amber" />
) : null} ) : null}
...@@ -1773,6 +1906,90 @@ export default function ProblemRectificationListPage() { ...@@ -1773,6 +1906,90 @@ export default function ProblemRectificationListPage() {
}} }}
/> />
{delayDetailRecord ? (
<Dialog
open={Boolean(delayDetailRecord)}
onClose={() => setDelayDetailRecord(null)}
size="5xl"
>
<DialogTitle>延期申请详情</DialogTitle>
<DialogDescription>
{delayDetailRecord.title || delayDetailRecord.projectName || '当前问题'},共{' '}
{getRecordDelayRequests(delayDetailRecord).length} 条延期申请。尚未提交的申请完成时间和延期事由显示为“待填写”。
</DialogDescription>
<DialogBody>
<div className="overflow-hidden rounded-lg border border-zinc-200 bg-white">
<Table bleed dense className="[--gutter:--spacing(4)]">
<TableHead>
<TableRow className="bg-zinc-50">
<TableHeader>责任部门</TableHeader>
<TableHeader>原预计完成时间</TableHeader>
<TableHeader>申请完成时间</TableHeader>
<TableHeader>延期事由</TableHeader>
<TableHeader>填写人</TableHeader>
<TableHeader>一级审批人</TableHeader>
<TableHeader>最终审批人</TableHeader>
<TableHeader>填写状态</TableHeader>
<TableHeader>一级审批状态</TableHeader>
<TableHeader>最终审批状态</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{getRecordDelayRequests(delayDetailRecord).map((request, index) => (
<TableRow key={request.item_id || `delay-request-${index}`}>
<TableCell className="min-w-40 align-top font-medium text-slate-900">
{request.responsible_department || '整条问题'}
</TableCell>
<TableCell className="align-top tabular-nums">
{formatDelayDate(request.original_due_date)}
</TableCell>
<TableCell className="align-top tabular-nums font-medium text-slate-900">
{formatDelayDate(request.requested_due_date)}
</TableCell>
<TableCell className="min-w-72 max-w-md whitespace-normal align-top text-slate-700">
{request.delay_reason || '待填写'}
</TableCell>
<TableCell className="align-top">
{workflowUserLabels[request.editor] || request.editor || '待填写'}
</TableCell>
<TableCell className="align-top">
{workflowUserLabels[request.auditor] || request.auditor || '待填写'}
</TableCell>
<TableCell className="align-top">
{workflowUserLabels[request.sender] || request.sender || '待填写'}
</TableCell>
<TableCell className="align-top">
<StatusPill
text={request.editor_status || EDITOR_STATUS_PENDING}
tone={resolveDelayStatusTone(request.editor_status)}
/>
</TableCell>
<TableCell className="align-top">
<StatusPill
text={request.auditor_status || AUDIT_STATUS_PENDING}
tone={resolveDelayStatusTone(request.auditor_status)}
/>
</TableCell>
<TableCell className="align-top">
<StatusPill
text={request.sender_status || AUDIT_STATUS_PENDING}
tone={resolveDelayStatusTone(request.sender_status)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</DialogBody>
<DialogActions>
<Button outline onClick={() => setDelayDetailRecord(null)}>
关闭
</Button>
</DialogActions>
</Dialog>
) : null}
<Dialog open={showCreateModal} onClose={handleCloseCreateModal} size="5xl"> <Dialog open={showCreateModal} onClose={handleCloseCreateModal} size="5xl">
<DialogTitle>新增问题整改记录</DialogTitle> <DialogTitle>新增问题整改记录</DialogTitle>
<DialogDescription> <DialogDescription>
......
...@@ -79,6 +79,9 @@ ...@@ -79,6 +79,9 @@
- 主列表提供“检查报告出具月份”筛选,用户选择 `YYYY-MM` 后,仅查询 `metadata.inspection_report_date` 位于该月份的记录;该筛选不使用其它检查或整改日期兜底 - 主列表提供“检查报告出具月份”筛选,用户选择 `YYYY-MM` 后,仅查询 `metadata.inspection_report_date` 位于该月份的记录;该筛选不使用其它检查或整改日期兜底
- 主列表支持每页 `10 / 20 / 50 / 100` 条,切换视图、修改筛选或清空筛选时回到第 1 页;表头勾选只选择当前页记录,跨页批量处理需用户逐页确认选择范围 - 主列表支持每页 `10 / 20 / 50 / 100` 条,切换视图、修改筛选或清空筛选时回到第 1 页;表头勾选只选择当前页记录,跨页批量处理需用户逐页确认选择范围
- 主列表视图页签数量通过 `/record/count` 按各视图限制和当前筛选条件计算,表格标题区展示服务端筛选后的总数与当前页记录数 - 主列表视图页签数量通过 `/record/count` 按各视图限制和当前筛选条件计算,表格标题区展示服务端筛选后的总数与当前页记录数
- 主列表标题列直接展示可点击的延期申请状态:存在历史问题级延期或部门级延期明细时,按当前阶段显示“延期已发起 / 延期待审批 / 延期待终审 / 延期处理中 / 延期已生效 / 延期已退回”日历徽标,不能等用户再次点击推送后才提示已发起;状态同时使用图标和文字,不只依赖颜色
- 点击延期状态后打开“延期申请详情”弹窗,按责任部门展示原预计完成时间、申请完成时间、延期事由、填写人、一级审批人、最终审批人以及三级流程状态;申请完成时间或延期事由尚未提交时明确显示“待填写”,历史问题级延期按“整条问题”展示;行操作区同时提供“查看延期申请”日历按钮,保证窄屏下标题列被固定列压缩时仍可进入详情
- 用户单选已有延期申请的记录时,顶部“推送延期申请”按钮改为显示当前延期状态并禁用,避免形成看似可以重复推送的入口
- 主列表滚动到表格区域后,表格工作台吸顶展示;标题栏、操作区、列分组和分页固定在工作台内,数据表格区域独立滚动,表头列名在数据区域内继续吸顶,折叠 / 展开列分组时保留当前表格滚动位置 - 主列表滚动到表格区域后,表格工作台吸顶展示;标题栏、操作区、列分组和分页固定在工作台内,数据表格区域独立滚动,表头列名在数据区域内继续吸顶,折叠 / 展开列分组时保留当前表格滚动位置
- 详情侧栏统一复用 `RecordDetailDrawer`,通过 `recordName`、`subjectName`、`preferredViewName` 驱动,不再在各页面重复实现抽屉骨架 - 详情侧栏统一复用 `RecordDetailDrawer`,通过 `recordName`、`subjectName`、`preferredViewName` 驱动,不再在各页面重复实现抽屉骨架
- 详情侧栏顶部不展示记录标题、状态和摘要卡片,只保留详情视图切换与全屏 / 关闭操作,字段主体作为主要阅读区域 - 详情侧栏顶部不展示记录标题、状态和摘要卡片,只保留详情视图切换与全屏 / 关闭操作,字段主体作为主要阅读区域
...@@ -350,7 +353,8 @@ ...@@ -350,7 +353,8 @@
推荐新交互: 推荐新交互:
- 页面顶部显示本次流程步骤和当前身份 - 页面顶部必须以图标、文字标签和独立色调明确区分“问题整改流程”与“延期申请流程”,不能只在流程模式说明中体现;问题整改页提示其处理整改进度、预计完成时间、整改情况和证明材料,延期页提示其处理新的整改完成时间和延期事由
- 明细表顶部再次显示“问题整改明细”或“延期申请明细”及当前行数,用户从 OA 待办直接进入页面或横向浏览表格时仍能识别当前处理类型;流程类型不能只靠颜色表达
- 主体统一使用表格,不再使用旧填写页的详情式编辑 - 主体统一使用表格,不再使用旧填写页的详情式编辑
- 长文本、附件、预计完成时间和整改进度均在表格行内维护 - 长文本、附件、预计完成时间和整改进度均在表格行内维护
- 部门流程的“送审”和各阶段审批作为行级操作展示;多选简单推送的送审、复核和最终审批按钮统一放在表格上方,行内只保留“保存” - 部门流程的“送审”和各阶段审批作为行级操作展示;多选简单推送的送审、复核和最终审批按钮统一放在表格上方,行内只保留“保存”
......
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