refactor(endpoints): implement deferred submission and fix clear-all bug
Implement Solution A (complete deferred submission) for custom endpoint
management, replacing the dual-mode system with unified local staging.
Changes:
- Remove immediate backend saves from EndpointSpeedTest
* handleAddEndpoint: local state update only
* handleRemoveEndpoint: local state update only
* handleSelect: remove lastUsed timestamp update
- Add explicit clear detection in ProviderForm
* Distinguish "user cleared endpoints" from "user didn't modify"
* Pass empty object {} as clear signal vs null for no-change
- Fix mergeProviderMeta to handle three distinct cases:
* null/undefined: don't modify endpoints (no meta sent)
* empty object {}: explicitly clear endpoints (send empty meta)
* with data: add/update endpoints (overwrite)
Fixed Critical Bug:
When users deleted all custom endpoints, changes were not saved because:
- draftCustomEndpoints=[] resulted in customEndpointsToSave=null
- mergeProviderMeta(meta, null) returned undefined
- Backend interpreted missing meta as "don't modify", preserving old values
Solution:
Detect when user had endpoints and cleared them (hadEndpoints && length===0),
then pass empty object to mergeProviderMeta as explicit clear signal.
Architecture Improvements:
- Transaction atomicity: all fields submitted together on form save
- UX consistency: add/edit modes behave identically
- Cancel button: true rollback with no immediate saves
- Code simplification: removed ~40 lines of immediate save error handling
Testing:
- TypeScript type check: passed
- Rust backend tests: 10/10 passed
- Build: successful
This commit is contained in:
@@ -440,21 +440,16 @@ impl ProviderService {
|
||||
let merged = if let Some(existing) = manager.providers.get(&provider_id) {
|
||||
let mut updated = provider_clone.clone();
|
||||
match (existing.meta.as_ref(), updated.meta.take()) {
|
||||
// 前端未提供 meta,表示不修改,沿用旧值
|
||||
(Some(old_meta), None) => {
|
||||
updated.meta = Some(old_meta.clone());
|
||||
}
|
||||
(Some(old_meta), Some(mut new_meta)) => {
|
||||
let mut merged_map = old_meta.custom_endpoints.clone();
|
||||
for (url, ep) in new_meta.custom_endpoints.drain() {
|
||||
merged_map.entry(url).or_insert(ep);
|
||||
}
|
||||
updated.meta = Some(ProviderMeta {
|
||||
custom_endpoints: merged_map,
|
||||
usage_script: new_meta.usage_script.clone(),
|
||||
});
|
||||
(None, None) => {
|
||||
updated.meta = None;
|
||||
}
|
||||
(None, maybe_new) => {
|
||||
updated.meta = maybe_new;
|
||||
// 前端提供的 meta 视为权威,直接覆盖(其中 custom_endpoints 允许是空,表示删除所有自定义端点)
|
||||
(_old, Some(new_meta)) => {
|
||||
updated.meta = Some(new_meta);
|
||||
}
|
||||
}
|
||||
updated
|
||||
|
||||
Reference in New Issue
Block a user