Day 5 - 详情与编辑
建议用时:180-210 分钟
你将学会什么
- 详情和编辑有什么区别
- 为什么编辑表单不要直接改原对象
- 什么是编辑草稿
- 保存时如何校验名称、价格、库存
- 取消时如何恢复选中商品数据
- 保存后如何刷新列表和选中项
今天重点是“选中一条商品后编辑它”。不要让输入框直接改列表里的原对象。更稳的做法是:选中商品时把数据复制到编辑字段,点击保存后再写回列表。
本页固定顺序
- 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
- 再学第二部分:把刚学的知识组合成一个完整例子。
- 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
- 最后做第四部分:先独立完成作业,再用完整答案检查。
学习衔接
上一页学习的是“主列表页面”,今天继续学习“详情与编辑”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。
今天的最低通过线
第一次学习不要求背完整页。完成下面 3 项,就可以继续:
- 能用自己的话说明“详情与编辑”解决什么问题。
- 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
- 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。
第一部分:先学原理和最小知识
这一部分从最小知识开始。先读解释,再把紧跟着的短例子敲一遍。编辑页面最重要的问题是:输入框里的内容是否应该立刻改列表里的原数据。
详情和编辑的区别
详情是“看”。
编辑是“改”。
详情可以直接显示选中商品:
SelectedProduct.Name
SelectedProduct.Price
SelectedProduct.Stock编辑最好使用单独字段:
EditName
EditPriceText
EditStockText原因是:用户输入过程中可能还没保存,也可能点取消。
什么是编辑草稿
编辑草稿就是临时放在输入框里的数据。
本页的草稿字段是:
EditName
EditPriceText
EditStockText当用户选中商品时,把商品复制到草稿:
EditName = SelectedProduct.Name;
EditPriceText = SelectedProduct.Price.ToString();
EditStockText = SelectedProduct.Stock.ToString();当用户点击保存时,再把草稿写回商品列表。
为什么不直接改原对象
如果输入框直接绑定 SelectedProduct.Name,用户每输入一个字,原对象就变了。
这样会导致:
- 用户还没保存,列表已经变化。
- 用户点取消时,需要额外记录原始值。
- 输入了错误价格时,原数据可能已经被污染。
用编辑草稿可以避免这些问题。
保存时为什么替换整条商品
本页保存时不是直接修改原对象,而是创建一个 updated:
var updated = new ProductItem
{
Id = SelectedProduct.Id,
Name = name,
Price = price,
Stock = stock,
CreatedAt = SelectedProduct.CreatedAt,
UpdatedAt = DateTime.Now
};然后替换列表里的那一条:
Products[index] = updated;
SelectedProduct = updated;这样列表能明确刷新,选中项也会指向新对象。
保存时要校验什么
根据 Day 1 的需求,保存至少校验:
| 字段 | 规则 |
|---|---|
| 名称 | 不能为空,不能超过 50 个字符 |
| 价格 | 必须是数字,并且大于 0 |
| 库存 | 必须是整数,并且大于或等于 0 |
这些校验失败时,应该显示 Message,并且不保存。
取消到底做什么
取消不是删除商品。
取消只是放弃当前输入框里的草稿,把草稿恢复成选中商品的原数据。
对应代码:
LoadSelectedProductIntoEditor();
Message = $"已取消编辑:{SelectedProduct.Name}";详情与编辑常用写法速查
| 需求 | 常用写法 |
|---|---|
| 选中后显示详情 | SelectedProduct |
| 编辑草稿字段 | EditName、EditPriceText、EditStockText |
| 进入编辑 | 把选中项复制到草稿 |
| 保存编辑 | 校验草稿后写回选中项 |
| 取消编辑 | 清空草稿或重新复制选中项 |
| 禁用保存 | CanSave |
| 错误提示 | ErrorMessage |
为什么要用草稿字段:
用户正在输入时,原商品不应该马上被改坏
点击保存后,校验通过才写回 SelectedProduct
点击取消时,原商品保持不变第二部分:把知识组合成完整例子
今天做一个“列表 + 编辑表单”页面:
- 左侧选择商品。
- 右侧显示编辑表单。
- 修改名称、价格、库存。
- 点击保存后,列表里的商品更新。
- 点击取消后,编辑表单恢复为选中商品原数据。
- 输入错误时显示提示,不保存。
ViewModel
文件位置:
ViewModels/MainWindowViewModel.csusing System;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace ProductApp.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ProductItem> Products { get; } = new();
[ObservableProperty]
private ProductItem? selectedProduct;
[ObservableProperty]
private string editName = "";
[ObservableProperty]
private string editPriceText = "";
[ObservableProperty]
private string editStockText = "";
[ObservableProperty]
private string message = "请选择商品后编辑。";
public MainWindowViewModel()
{
Products.Add(CreateProduct("Keyboard", 199m, 10));
Products.Add(CreateProduct("Mouse", 59m, 30));
Products.Add(CreateProduct("Monitor", 899m, 5));
SelectedProduct = Products.FirstOrDefault();
}
partial void OnSelectedProductChanged(ProductItem? value)
{
LoadSelectedProductIntoEditor();
if (value is not null)
{
Message = $"正在编辑:{value.Name}";
}
}
[RelayCommand]
private void SaveEdit()
{
if (SelectedProduct is null)
{
Message = "请先选择商品。";
return;
}
string name = EditName.Trim();
if (string.IsNullOrWhiteSpace(name))
{
Message = "商品名称不能为空。";
return;
}
if (name.Length > 50)
{
Message = "商品名称不能超过 50 个字符。";
return;
}
if (!decimal.TryParse(EditPriceText, out decimal price) || price <= 0)
{
Message = "价格必须是大于 0 的数字。";
return;
}
if (!int.TryParse(EditStockText, out int stock) || stock < 0)
{
Message = "库存必须是大于或等于 0 的整数。";
return;
}
var updated = new ProductItem
{
Id = SelectedProduct.Id,
Name = name,
Price = price,
Stock = stock,
CreatedAt = SelectedProduct.CreatedAt,
UpdatedAt = DateTime.Now
};
int index = Products.IndexOf(SelectedProduct);
if (index < 0)
{
Message = "要保存的商品不存在。";
return;
}
Products[index] = updated;
SelectedProduct = updated;
Message = $"已保存:{updated.Name}";
}
[RelayCommand]
private void CancelEdit()
{
if (SelectedProduct is null)
{
Message = "没有可取消的编辑。";
return;
}
LoadSelectedProductIntoEditor();
Message = $"已取消编辑:{SelectedProduct.Name}";
}
private void LoadSelectedProductIntoEditor()
{
if (SelectedProduct is null)
{
EditName = "";
EditPriceText = "";
EditStockText = "";
return;
}
EditName = SelectedProduct.Name;
EditPriceText = SelectedProduct.Price.ToString();
EditStockText = SelectedProduct.Stock.ToString();
}
private static ProductItem CreateProduct(string name, decimal price, int stock)
{
DateTime now = DateTime.Now;
return new ProductItem
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
Stock = stock,
CreatedAt = now,
UpdatedAt = now
};
}
}
public sealed class ProductItem
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}窗口
文件位置:
Views/MainWindow.axaml<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ProductApp.ViewModels"
x:Class="ProductApp.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Width="860"
Height="560"
Title="Product Edit">
<Grid RowDefinitions="Auto,Auto,*" Margin="20" RowSpacing="12">
<TextBlock Text="商品详情与编辑"
FontSize="24"
FontWeight="Bold" />
<TextBlock Grid.Row="1"
Text="{Binding Message}" />
<Grid Grid.Row="2" ColumnDefinitions="320,*" ColumnSpacing="16">
<ListBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ProductItem">
<Grid ColumnDefinitions="160,80,60" Margin="4">
<TextBlock Text="{Binding Name}" />
<TextBlock Grid.Column="1"
Text="{Binding Price}" />
<TextBlock Grid.Column="2"
Text="{Binding Stock}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Spacing="8">
<TextBlock Text="编辑商品"
FontSize="20"
FontWeight="Bold" />
<TextBlock Text="名称" />
<TextBox Text="{Binding EditName, Mode=TwoWay}" />
<TextBlock Text="价格" />
<TextBox Text="{Binding EditPriceText, Mode=TwoWay}" />
<TextBlock Text="库存" />
<TextBox Text="{Binding EditStockText, Mode=TwoWay}" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="保存"
Command="{Binding SaveEditCommand}" />
<Button Content="取消"
Command="{Binding CancelEditCommand}" />
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Window>第三部分:跟着敲代码
从这里开始动手。打开 Avalonia 项目。
第 1 步:准备商品模型
public sealed class ProductItem
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}解释:
- 这里加入了创建时间和更新时间。
- 编辑保存时只改变
UpdatedAt。
第 2 步:写列表和选中项
public ObservableCollection<ProductItem> Products { get; } = new();
[ObservableProperty]
private ProductItem? selectedProduct;解释:
Products给列表显示。SelectedProduct代表当前正在编辑哪一条。
第 3 步:写编辑草稿字段
[ObservableProperty]
private string editName = "";
[ObservableProperty]
private string editPriceText = "";
[ObservableProperty]
private string editStockText = "";
[ObservableProperty]
private string message = "请选择商品后编辑。";解释:
- 输入框绑定这些草稿字段。
- 不是直接绑定
SelectedProduct。
第 4 步:选中商品时加载到草稿
partial void OnSelectedProductChanged(ProductItem? value)
{
LoadSelectedProductIntoEditor();
if (value is not null)
{
Message = $"正在编辑:{value.Name}";
}
}解释:
- 每次选中项变化,就刷新编辑表单。
第 5 步:写加载草稿方法
private void LoadSelectedProductIntoEditor()
{
if (SelectedProduct is null)
{
EditName = "";
EditPriceText = "";
EditStockText = "";
return;
}
EditName = SelectedProduct.Name;
EditPriceText = SelectedProduct.Price.ToString();
EditStockText = SelectedProduct.Stock.ToString();
}解释:
- 有选中商品时,把商品数据复制到输入框。
- 没有选中商品时,清空输入框。
第 6 步:写保存校验
if (SelectedProduct is null)
{
Message = "请先选择商品。";
return;
}
string name = EditName.Trim();
if (string.IsNullOrWhiteSpace(name))
{
Message = "商品名称不能为空。";
return;
}
if (name.Length > 50)
{
Message = "商品名称不能超过 50 个字符。";
return;
}解释:
- 没选中商品不能保存。
- 名称先去掉前后空格。
- 名称为空或太长都不能保存。
第 7 步:继续校验价格和库存
if (!decimal.TryParse(EditPriceText, out decimal price) || price <= 0)
{
Message = "价格必须是大于 0 的数字。";
return;
}
if (!int.TryParse(EditStockText, out int stock) || stock < 0)
{
Message = "库存必须是大于或等于 0 的整数。";
return;
}解释:
TryParse失败说明不是合法数字。- 价格要大于 0。
- 库存不能小于 0。
第 8 步:保存时替换列表项
var updated = new ProductItem
{
Id = SelectedProduct.Id,
Name = name,
Price = price,
Stock = stock,
CreatedAt = SelectedProduct.CreatedAt,
UpdatedAt = DateTime.Now
};
int index = Products.IndexOf(SelectedProduct);
if (index < 0)
{
Message = "要保存的商品不存在。";
return;
}
Products[index] = updated;
SelectedProduct = updated;
Message = $"已保存:{updated.Name}";解释:
- 保留原来的 Id 和 CreatedAt。
- 更新时间改成当前时间。
- 替换列表里的旧商品。
第 9 步:写取消命令
[RelayCommand]
private void CancelEdit()
{
if (SelectedProduct is null)
{
Message = "没有可取消的编辑。";
return;
}
LoadSelectedProductIntoEditor();
Message = $"已取消编辑:{SelectedProduct.Name}";
}解释:
- 取消只是恢复输入框。
- 不改变列表里的商品。
第 10 步:写编辑表单 XAML
<StackPanel Grid.Column="1" Spacing="8">
<TextBlock Text="编辑商品"
FontSize="20"
FontWeight="Bold" />
<TextBlock Text="名称" />
<TextBox Text="{Binding EditName, Mode=TwoWay}" />
<TextBlock Text="价格" />
<TextBox Text="{Binding EditPriceText, Mode=TwoWay}" />
<TextBlock Text="库存" />
<TextBox Text="{Binding EditStockText, Mode=TwoWay}" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="保存"
Command="{Binding SaveEditCommand}" />
<Button Content="取消"
Command="{Binding CancelEditCommand}" />
</StackPanel>
</StackPanel>解释:
- 输入框绑定草稿字段。
- 保存按钮调用
SaveEditCommand。 - 取消按钮调用
CancelEditCommand。
第 11 步:运行后检查
运行后按顺序检查:
- 选中
Keyboard。 - 把名称改成
Keyboard Pro,点击保存。 - 左侧列表变成
Keyboard Pro。 - 把价格改成
abc,点击保存。 - 状态提示显示价格错误,列表不变化。
- 修改名称后点击取消,输入框恢复选中商品原数据。
- 不选商品时保存,提示先选择商品。
常见错误和修法
| 错误 | 为什么错 | 修法 |
|---|---|---|
| 编辑时直接改列表对象 | 取消编辑也会污染原数据 | 用编辑副本,保存时再覆盖 |
价格输入直接用 decimal | 输入过程可能临时非法 | 输入框用 string,保存时 TryParse |
| 保存后不刷新列表 | 页面显示旧数据 | 保存成功后重新加载或更新集合 |
| 删除前不确认 | 容易误删 | 至少在逻辑上保留确认入口 |
| 没有处理未选中状态 | 点击编辑或删除会空引用 | 未选中时禁用按钮或提示 |
小白重复敲写训练
编辑功能先练草稿,不直接修改原对象。
训练 1:把选中商品复制到草稿
private void LoadDraft(Product product)
{
DraftName = product.Name;
DraftPrice = product.Price.ToString();
DraftStock = product.Stock.ToString();
}选中两条不同商品,确认草稿跟着切换。
训练 2:取消时恢复草稿
[RelayCommand]
private void CancelEdit()
{
if (SelectedProduct is not null)
{
LoadDraft(SelectedProduct);
}
}改动草稿后点击取消,确认原对象没有变化。
训练 3:校验后保存
if (string.IsNullOrWhiteSpace(DraftName))
{
ErrorMessage = "名称不能为空";
return;
}
if (!decimal.TryParse(DraftPrice, out decimal price) || price < 0)
{
ErrorMessage = "价格格式不正确";
return;
}第三遍补库存校验和成功保存逻辑。
每日小测
做完本页后,用这 5 题检查是否真的掌握。
1. 判断题
本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。
答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。
2. 填空题
本页主题是:详情与编辑。今天至少要掌握的 3 个点是:
1. 详情和编辑有什么区别
2. 为什么编辑表单不要直接改原对象
3. 什么是编辑草稿答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。
3. 流程题
遇到本页相关功能时,先按什么顺序处理?
答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。
4. 找错误题
如果本页代码运行失败,第一步应该做什么?
答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。
5. 改需求题
在本页完整例子跑通后,至少改一个小需求。
可选改法:
- 改一个字段名称。
- 多加一个校验条件。
- 多输出一行结果。
- 把固定数据改成用户输入。
- 把一次处理改成多条数据处理。
答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:详情和编辑有什么区别。
上位机专项练习
详情页要回答设备是谁、是否在线、有哪些点位、最近值是多少、能执行哪些操作。
下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。
专项例子 1:设备详情头部
<StackPanel>
<TextBlock Text="{Binding SelectedDevice.Name}" FontSize="30" />
<TextBlock Text="{Binding SelectedDevice.ConnectionText}" />
<TextBlock Text="{Binding SelectedDevice.LastSeenText}" />
</StackPanel>运行结果或界面效果:
设备名、连接状态和最后通信时间清楚改动任务: 增加 IP 地址。
专项例子 2:点位实时表
<DataGrid ItemsSource="{Binding SelectedDevice.Tags}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="点位" Binding="{Binding Name}" />
<DataGridTextColumn Header="实时值" Binding="{Binding ValueText}" />
<DataGridTextColumn Header="质量" Binding="{Binding Quality}" />
</DataGrid.Columns>
</DataGrid>运行结果或界面效果:
每个点位显示值和质量改动任务: 增加更新时间列。
专项例子 3:编辑报警阈值
[RelayCommand]
private void SaveLimit()
{
if (SelectedTag is null) return;
tagRepository.UpdateLimit(SelectedTag.Id, SelectedTag.AlarmLimit);
Message = "阈值已保存";
}运行结果或界面效果:
选中点位后可保存阈值改动任务: 保存前检查上下限关系。
第四部分:作业完整答案
作业要求:完成商品详情与编辑页面,必须包含编辑草稿、保存校验、取消恢复、列表刷新。
答案文件 1:ViewModels/MainWindowViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace ProductApp.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public ObservableCollection<ProductItem> Products { get; } = new();
[ObservableProperty]
private ProductItem? selectedProduct;
[ObservableProperty]
private string editName = "";
[ObservableProperty]
private string editPriceText = "";
[ObservableProperty]
private string editStockText = "";
[ObservableProperty]
private string message = "请选择商品后编辑。";
public MainWindowViewModel()
{
Products.Add(CreateProduct("Keyboard", 199m, 10));
Products.Add(CreateProduct("Mouse", 59m, 30));
Products.Add(CreateProduct("Monitor", 899m, 5));
SelectedProduct = Products.FirstOrDefault();
}
partial void OnSelectedProductChanged(ProductItem? value)
{
LoadSelectedProductIntoEditor();
if (value is not null)
{
Message = $"正在编辑:{value.Name}";
}
}
[RelayCommand]
private void SaveEdit()
{
if (SelectedProduct is null)
{
Message = "请先选择商品。";
return;
}
string name = EditName.Trim();
if (string.IsNullOrWhiteSpace(name))
{
Message = "商品名称不能为空。";
return;
}
if (name.Length > 50)
{
Message = "商品名称不能超过 50 个字符。";
return;
}
if (!decimal.TryParse(EditPriceText, out decimal price) || price <= 0)
{
Message = "价格必须是大于 0 的数字。";
return;
}
if (!int.TryParse(EditStockText, out int stock) || stock < 0)
{
Message = "库存必须是大于或等于 0 的整数。";
return;
}
var updated = new ProductItem
{
Id = SelectedProduct.Id,
Name = name,
Price = price,
Stock = stock,
CreatedAt = SelectedProduct.CreatedAt,
UpdatedAt = DateTime.Now
};
int index = Products.IndexOf(SelectedProduct);
if (index < 0)
{
Message = "要保存的商品不存在。";
return;
}
Products[index] = updated;
SelectedProduct = updated;
Message = $"已保存:{updated.Name}";
}
[RelayCommand]
private void CancelEdit()
{
if (SelectedProduct is null)
{
Message = "没有可取消的编辑。";
return;
}
LoadSelectedProductIntoEditor();
Message = $"已取消编辑:{SelectedProduct.Name}";
}
private void LoadSelectedProductIntoEditor()
{
if (SelectedProduct is null)
{
EditName = "";
EditPriceText = "";
EditStockText = "";
return;
}
EditName = SelectedProduct.Name;
EditPriceText = SelectedProduct.Price.ToString();
EditStockText = SelectedProduct.Stock.ToString();
}
private static ProductItem CreateProduct(string name, decimal price, int stock)
{
DateTime now = DateTime.Now;
return new ProductItem
{
Id = Guid.NewGuid(),
Name = name,
Price = price,
Stock = stock,
CreatedAt = now,
UpdatedAt = now
};
}
}
public sealed class ProductItem
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}答案文件 2:Views/MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ProductApp.ViewModels"
x:Class="ProductApp.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Width="860"
Height="560"
Title="Product Edit">
<Grid RowDefinitions="Auto,Auto,*" Margin="20" RowSpacing="12">
<TextBlock Text="商品详情与编辑"
FontSize="24"
FontWeight="Bold" />
<TextBlock Grid.Row="1"
Text="{Binding Message}" />
<Grid Grid.Row="2" ColumnDefinitions="320,*" ColumnSpacing="16">
<ListBox ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ProductItem">
<Grid ColumnDefinitions="160,80,60" Margin="4">
<TextBlock Text="{Binding Name}" />
<TextBlock Grid.Column="1"
Text="{Binding Price}" />
<TextBlock Grid.Column="2"
Text="{Binding Stock}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Spacing="8">
<TextBlock Text="编辑商品"
FontSize="20"
FontWeight="Bold" />
<TextBlock Text="名称" />
<TextBox Text="{Binding EditName, Mode=TwoWay}" />
<TextBlock Text="价格" />
<TextBox Text="{Binding EditPriceText, Mode=TwoWay}" />
<TextBlock Text="库存" />
<TextBox Text="{Binding EditStockText, Mode=TwoWay}" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="保存"
Command="{Binding SaveEditCommand}" />
<Button Content="取消"
Command="{Binding CancelEditCommand}" />
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Window>验收结果
运行后必须看到:
- 选中商品后,右侧输入框显示对应数据。
- 修改名称并保存后,左侧列表同步更新。
- 价格输入
abc时不能保存。 - 库存输入
-1时不能保存。 - 名称清空时不能保存。
- 点击取消后,输入框恢复选中商品原数据。
- 保存成功后
UpdatedAt被更新。
为什么这个答案是对的
这个答案把编辑流程拆清楚了:
| 动作 | 代码 | 作用 |
|---|---|---|
| 选择商品 | SelectedProduct | 确定当前编辑对象 |
| 加载草稿 | LoadSelectedProductIntoEditor | 把原数据复制到输入框 |
| 校验输入 | SaveEdit | 拦住错误名称、价格、库存 |
| 保存修改 | Products[index] = updated | 替换列表中的商品 |
| 取消修改 | CancelEdit | 恢复输入框,不改原数据 |
编辑页面的关键是:输入框里的内容是草稿,点击保存才真正写回商品。