修复: 08-07-business-routing-regression-repair 关闭资产创建跳转回归
这个提交包含在:
@@ -1185,6 +1185,9 @@ validation prevent repeated re-entry and reduce support/debugging cost.
|
||||
- Reload/close guard: `beforeunload` while `isDirty === true`.
|
||||
- Shared UI contract: one unsaved-change dialog with continue-editing and
|
||||
discard actions.
|
||||
- Successful-save navigation: `usePostSaveNavigation()` opens one synchronous
|
||||
allow-next window around the canonical navigation action; its signature is
|
||||
`(action: () => void) => void`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
@@ -1194,6 +1197,12 @@ validation prevent repeated re-entry and reduce support/debugging cost.
|
||||
is called only after explicit discard confirmation.
|
||||
- Record switching and panel closing use the same pending-action contract as
|
||||
route navigation. Successful save or explicit discard clears dirty state.
|
||||
- When a successful server save immediately replaces a create route with its
|
||||
canonical detail, use the dedicated post-save navigation entry after a valid
|
||||
entity ID is available. Do not depend on React state cleanup completing
|
||||
before `navigate(...)`, and do not expose a persistent guard-disable switch.
|
||||
- Failed, timed-out or invalid save responses never use the post-save bypass;
|
||||
they retain the form values and normal dirty protection.
|
||||
- Register `beforeunload` only while dirty and remove it during cleanup.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
@@ -1202,21 +1211,31 @@ validation prevent repeated re-entry and reduce support/debugging cost.
|
||||
- Dirty in-app navigation or browser back -> block and show one dialog.
|
||||
- Continue editing -> URL, record selection and values remain unchanged.
|
||||
- Discard -> execute exactly one pending navigation/selection and clear dirty.
|
||||
- Successful create -> replace to canonical detail without an unsaved dialog,
|
||||
even when the clean-state update has not committed yet.
|
||||
- Save failure or missing entity ID -> remain on the form with dirty protection;
|
||||
never bypass navigation protection to hide the failure.
|
||||
- Reload/close while dirty -> browser-level unload warning remains active.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: reimbursement, procurement, contract and asset forms reuse one guard
|
||||
while keeping their own save and dirty-field logic.
|
||||
- Good: an asset POST succeeds, then the page wraps one `replace` navigation in
|
||||
`usePostSaveNavigation()` before refreshing non-critical list caches.
|
||||
- Base: a short non-editable filter form does not register the guard.
|
||||
- Bad: listen to history after navigation, then push the previous URL back; the
|
||||
user can see state flicker or lose form data before confirmation.
|
||||
- Bad: call `setSavedSignature(...)` and immediately navigate while relying on
|
||||
React to commit the clean state before `useBlocker` evaluates.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Playwright covers link navigation, browser back, reload and record switch.
|
||||
- Assert cancel preserves URL and values; discard changes target once; save
|
||||
removes the warning.
|
||||
- A create-route regression covers successful canonical `replace`, absence of
|
||||
the unsaved dialog and browser history that does not reopen the submitted form.
|
||||
- Frontend typecheck and build after router or shared guard changes.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
@@ -1227,6 +1246,11 @@ validation prevent repeated re-entry and reduce support/debugging cost.
|
||||
window.addEventListener("popstate", () => navigate(previousUrl));
|
||||
```
|
||||
|
||||
```ts
|
||||
setSavedSignature(nextSignature);
|
||||
navigate(canonicalDetailPath, { replace: true }); // dirty ref may still be true
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```ts
|
||||
@@ -1234,6 +1258,13 @@ const blocker = useBlocker(isDirty);
|
||||
// reset to keep editing; proceed only after explicit discard.
|
||||
```
|
||||
|
||||
```ts
|
||||
const runAfterSuccessfulSave = usePostSaveNavigation();
|
||||
// Call only after the API returned a valid entity ID.
|
||||
runAfterSuccessfulSave(() => navigate(canonicalDetailPath, { replace: true }));
|
||||
void refreshNonCriticalCaches();
|
||||
```
|
||||
|
||||
## Scenario: Business Ledger List-To-Detail Route Migration
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户