Commit bfc58622 authored by drigle's avatar drigle

fix: refine toast close button and report detail save success

parent 513706c5
......@@ -35,7 +35,7 @@
接口成功、失败及降级警告统一使用根部 `AppFeedback` 的 HeroUI `Toast.Provider` 和官方 compound 组件;登录、独立流程页和 AppShell 共用一个 Provider,业务页面不再渲染接口反馈横条。成功默认 4 秒、错误/警告默认 8 秒后消失,关闭不改变请求结果或表单草稿。
`src/design-system/feedback.css` 是操作反馈的窄范围交互约定,沿用模型分析应用 `f167d24`:仅调整 Toast 层级、安全区位置、长文布局及关闭按钮的可见性/点击范围。浮层层级 300,高于业务 Modal 和移动侧栏;关闭按钮无需悬停即可操作。颜色、边框、圆角、阴影与 Hover 继续使用 HeroUI 默认主题,不在页面 CSS 或 `tokens.css` 中覆盖。
`src/design-system/feedback.css` 仅调整 Toast 层级、安全区位置及后排提示关闭按钮的可见性,浮层层级 300,高于业务 Modal 和移动侧栏。关闭按钮直接组合 HeroUI `Button`(`ghost`、`sm`、`isIconOnly`),通过官方 `slot="close"` 接入 Toast 关闭行为,使用 `AppIcons.Close`,位于内容右侧并随正文参与布局。前排关闭图标始终可见,使用官方透明按钮及默认 Hover/焦点样式;不再放大带边框的 Toast 默认关闭按钮,不设置额外的绝对位置和正文右侧占位。颜色、边框、圆角、阴影与 Hover 继续使用 HeroUI 默认主题,不在页面 CSS 或 `tokens.css` 中覆盖。
## 2. 主题权威
......
import { Toast } from '@heroui/react';
import { Button, Toast } from '@heroui/react';
import { Close } from '@/components/AppIcons';
export default function AppFeedback({ children }) {
return (
......@@ -12,7 +13,7 @@ export default function AppFeedback({ children }) {
width="min(30rem, calc(100vw - 2rem))"
>
{({ toast }) => (
<Toast className="app-feedback-toast" toast={toast} variant={toast.content.variant}>
<Toast className="app-feedback-toast gap-2" toast={toast} variant={toast.content.variant}>
<Toast.Indicator variant={toast.content.variant} />
<Toast.Content className="min-w-0">
<Toast.Title>{toast.content.title}</Toast.Title>
......@@ -23,7 +24,16 @@ export default function AppFeedback({ children }) {
) : null}
{toast.content.actionProps ? <Toast.ActionButton {...toast.content.actionProps} /> : null}
</Toast.Content>
<Toast.CloseButton aria-label="关闭提示" className="app-feedback-close" />
<Button
aria-label="关闭提示"
className="app-feedback-close shrink-0 self-start"
isIconOnly
size="sm"
slot="close"
variant="ghost"
>
<Close size={16} />
</Button>
</Toast>
)}
</Toast.Provider>
......
......@@ -4,17 +4,8 @@
top: max(1rem, env(safe-area-inset-top));
}
.app-feedback-toast.toast {
gap: 0.5rem;
padding-inline-end: 3.5rem;
}
/* HeroUI hides this button until hover by default; touch and keyboard users need it visible. */
.app-feedback-toast[data-frontmost="true"] .app-feedback-close {
top: 0.25rem;
right: 0.25rem;
width: 2.75rem;
height: 2.75rem;
opacity: 1;
pointer-events: auto;
/* Only the front toast is interactive; its ghost close button stays visible without hover. */
.app-feedback-toast:not([data-frontmost="true"]) .app-feedback-close {
visibility: hidden;
pointer-events: none;
}
......@@ -75,6 +75,7 @@ const RecordDetailDrawer = (props, ref) => {
const [dirtyFields, setDirtyFields] = useState({});
const [validationErrors, setValidationErrors] = useState({});
const [, setSaveError] = useFeedbackState('error');
const [, setSaveSuccess] = useFeedbackState('success');
const editSessionRef = useRef(0);
const pendingLocalMaterialsRef = useRef({});
......@@ -107,6 +108,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields({});
setValidationErrors({});
setSaveError('');
setSaveSuccess('');
pendingLocalMaterialsRef.current = {};
editSessionRef.current += 1;
}, [open, recordName]);
......@@ -212,6 +214,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields({});
setValidationErrors({});
setSaveError('');
setSaveSuccess('');
pendingLocalMaterialsRef.current = {};
};
......@@ -228,6 +231,7 @@ const RecordDetailDrawer = (props, ref) => {
setDirtyFields({});
setValidationErrors({});
setSaveError('');
setSaveSuccess('');
pendingLocalMaterialsRef.current = {};
setIsEditing(true);
};
......@@ -312,6 +316,7 @@ const RecordDetailDrawer = (props, ref) => {
setIsSaving(true);
setSaveError('');
setSaveSuccess('');
try {
const pendingMaterials = Object.entries(pendingLocalMaterialsRef.current)
......@@ -343,6 +348,7 @@ const RecordDetailDrawer = (props, ref) => {
setValidationErrors({});
pendingLocalMaterialsRef.current = {};
onDataChange?.(true);
setSaveSuccess('详情已保存。');
} catch (saveFailure) {
setSaveError(saveFailure?.message || '保存失败,请稍后重试。');
} finally {
......
......@@ -155,8 +155,9 @@
- 功能入口:登录、站点配置、应用切换、问题清单及归档批次、驾驶舱/整改分析、详情编辑、证明材料上传与搜索、整改/延期流程表格、旧外链流程页和通知中心。
- 查询、保存、复制、删除、锁定/解锁、推送、归档、上传及审批的接口成功/失败反馈统一显示 HeroUI Toast,应用配置/应用列表降级使用 warning。根部统一挂载,Portal 在视口顶部呈现,层级高于业务 Modal、确认框和移动侧栏;弹窗打开或页面滚动时也可看到并关闭,不抢走表单焦点。
- 成功提示默认 4 秒、错误和警告默认 8 秒后消失,悬停或键盘聚焦暂停计时;关闭按钮始终可见。相同错误再次发生时重新弹出,同一来源的新提示替换旧提示;清空反馈或离开对应页面时清理该来源提示。
- 成功提示默认 4 秒、错误和警告默认 8 秒后消失,悬停或键盘聚焦暂停计时;当前提示右侧的关闭图标始终可见,使用 HeroUI 默认小号透明按钮,点击或键盘激活均通过 Toast 的 `close` slot 关闭。后排堆叠提示不显示关闭按钮,避免误关闭。相同错误再次发生时重新弹出,同一来源的新提示替换旧提示;清空反馈或离开对应页面时清理该来源提示。
- 提示关闭仅影响展示,不把失败改为成功,不清空草稿、选中记录或附件,不发起保存、推送、审批或归档。表单必填/JSON 校验、锁定说明和删除/归档确认说明继续保留在对应控件或确认框中。
- 问题详情弹窗点击“保存”后,只有本次待建档材料和 `updateRecord` 全部成功,才退出编辑并弹出“详情已保存”成功提示,弹窗继续展示已保存内容;任一接口失败仅显示错误并保留草稿。重新开始编辑、再次保存或切换记录时清理上一条成功提示;无字段修改、校验未通过或正在保存时不提交、不弹出成功提示。
- 问题清单、归档批次、驾驶舱/整改分析、详情、通知和流程页加载失败时保留失败空状态及“重新加载/重试”,详细原因由浮层展示,避免误显示“暂无数据”。重试只读取当前页面资源,不重新执行写入操作;通知继续按当前分类、已读状态、关键词和页码重试。
- 人员、关联记录和材料查询失败时弹出提示,选择器保留字段级加载错误;关联明细和材料保留读取重试入口。详情保存或附件上传失败时保留当前编辑内容,可修正后再次提交;批量附件部分失败保留已成功的附件并提示失败项。
- 归档快照字段读取失败时保留明细弹窗和重试入口,历史 JSON 损坏时保留“查看异常数据”及异常空状态,详细原因由操作提示展示。关闭提示不回写 `metadata`、不更改 `is_locked`、审批状态、分页筛选或归档快照。
......
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