feat: 增强数据导入功能与用户界面优化

- 更新 README.md,强调 Area 路径为必填项,并增加加载 Area 列表的功能,确保用户在导入时不会遗漏必要信息。
- 修改 Config.cs 和 config.json,调整 SkipExistingTitles 默认值为 false,以便用户首次导入时能看到真实冲突和报错。
- 在 MainForm.cs 中添加加载 Area 列表的按钮,优化用户交互体验。
- 更新 ExcelService.cs,增强模板导出逻辑,确保用户能够更方便地使用自定义模板。
- 调整界面布局,确保 Area 路径与迭代路径的选择更加直观,提升整体用户体验。
This commit is contained in:
chenshoulin
2026-11-02 10:40:52 +08:00
parent ffc210f827
commit 14f5e32262
7 changed files with 409 additions and 91 deletions
+51 -16
View File
@@ -21,10 +21,11 @@ namespace TfsExcelImporter
private TextBox _txtPwd = new TextBox { UseSystemPasswordChar = true };
private ComboBox _cmbIteration = new ComboBox();
private ComboBox _cmbArea = new ComboBox();
private TextBox _txtExcel = new TextBox();
private TextBox _txtParentPbi = new TextBox();
private TextBox _txtAreaPath = new TextBox();
private Button _btnLoadIterations = new Button();
private Button _btnLoadAreas = new Button();
private Button _btnTemplate = new Button();
private CheckBox _chkSkip = new CheckBox();
@@ -337,34 +338,47 @@ namespace TfsExcelImporter
private void BuildCardData()
{
_cardData = MakeCard(2, "选择迭代与数据源", true, Theme.Px(44));
_cardData.SummaryDots = true; // 头部摘要按「圆点 + 短语」分段,四项状态一眼可辨
_cardData.SummaryDots = true; // 头部摘要按「圆点 + 短语」分段,五项状态一眼可辨
_cmbIteration.DropDownStyle = ComboBoxStyle.DropDown; // 允许手填完整路径
_cmbArea.DropDownStyle = ComboBoxStyle.DropDown; // 同上:列表拿不到时仍能手填
_fIteration = NewField(_cmbIteration, 500, false);
_fArea = NewField(_cmbArea, 500, true);
_fExcel = NewField(_txtExcel, 500, true);
_fParent = NewField(_txtParentPbi, 150, true);
_fIteration.SetCue("选择或手填完整迭代路径,如 Tools_Dev\\Sprint 1");
// Area 现在是必填:留空会导致团队视图看不到新导入的工作项,所以提示里直接给写法
_fArea.SetCue("选择或手填完整 Area 路径,如 Tools_Dev\\DataTechnologies");
_fExcel.SetCue("选择 .xlsx 文件,或点右侧文件夹图标浏览");
_fParent.SetCue("如 219975");
_btnLoadIterations = NewButton("加载迭代列表", BtnKind.Secondary, IconKind.Fetch, 130);
_btnLoadAreas = NewButton("加载 Area 列表", BtnKind.Secondary, IconKind.Fetch, 130);
_btnTemplate = NewButton("导出 Excel 模板", BtnKind.Secondary, IconKind.Doc, 142);
// 行尾两个按钮宽度取齐(取两者较宽的一个):右缘本来就都由 AnchorRight 贴在 M 上,
// 宽度一致后左缘也落在同一条竖线上,两个按钮才成为一列;同时两行 Track 给右侧留的
// 宽度变成同一个值,上面两个输入框的右边框才对得齐。
int wBtn = Math.Max(_btnLoadIterations.Width, _btnTemplate.Width);
_btnLoadIterations.Width = wBtn;
_btnLoadAreas.Width = wBtn;
_btnTemplate.Width = wBtn;
_chkSkip = NewCheck("跳过已存在标题", "");
// 三行字段对齐同一条左缘:标签列宽取最大值
int wLbl = MaxLabelWidth("目标迭代", "Excel 文件", "父 PBI Id");
// 四行字段对齐同一条左缘:标签列宽取最大值;必填项的红色 * 也留进列宽里,
// 否则带 * 的行会把输入框往右推,四行左缘就不再是一条竖线。
int wLbl = MaxLabelWidth("目标迭代", "Area 路径", "Excel 文件", "父 PBI Id")
+ Theme.Px(2) + Theme.TextWidth("*", Theme.Body);
int y = _cardData.HeaderH + Theme.S12;
y = Row(_cardData, y, 52, new Lbl("目标迭代", wLbl), _fIteration);
y = Row(_cardData, y, 52, new Lbl("目标迭代", wLbl, true), _fIteration);
AnchorRight(_btnLoadIterations, _cardData, M, _fIteration.Top);
Track(_fIteration, _cardData, wBtn + G + M, 0);
y = Row(_cardData, y, 52, new Lbl("Excel 文件", wLbl), _fExcel);
// Area 与迭代同属「写到哪儿」:紧贴迭代下方,值直接落进 System.AreaPath。
// 行尾同样挂一个列表按钮:Area 树往往比迭代树深得多,手打路径最容易写错。
y = Row(_cardData, y, 52, new Lbl("Area 路径", wLbl, true), _fArea);
AnchorRight(_btnLoadAreas, _cardData, M, _fArea.Top);
Track(_fArea, _cardData, wBtn + G + M, 0);
y = Row(_cardData, y, 52, new Lbl("Excel 文件", wLbl, true), _fExcel);
// 设计方案:行内操作(浏览)并入输入框尾部图标,行尾只留右对齐成一列的操作按钮
_fExcel.AddSuffix(IconKind.Folder, BrowseExcel);
AnchorRight(_btnTemplate, _cardData, M, _fExcel.Top);
@@ -377,6 +391,7 @@ namespace TfsExcelImporter
y = Row(_cardData, y, 32, _fParent.Left - M, _chkSkip);
_errIteration = AddErrorUnder(_cardData, _fIteration);
_errArea = AddErrorUnder(_cardData, _fArea);
_errExcel = AddErrorUnder(_cardData, _fExcel);
_errParent = AddErrorUnder(_cardData, _fParent);
_cardData.BodyH = y - _cardData.HeaderH + Theme.S8;
@@ -554,17 +569,16 @@ namespace TfsExcelImporter
{
_cardAdv = MakeCard(0, "高级工具", true, Theme.Px(36));
_cardAdv.ShowBadge = false;
_fArea = NewField(_txtAreaPath, 420, true);
_fArea.SetCue("如 Tools_Dev\\导入工具,留空则沿用工作项现有 Area");
// Area 输入框已上移到第 2 步(必填),这里只剩两个批量修正动作,
// 两者都按第 2 步填的「目标迭代 / Area 路径」筛选已有工作项。
_btnFixArea = NewButton("按迭代修 Area", BtnKind.Secondary, IconKind.Wrench, 158);
_btnLinkParent = NewButton("补挂父项", BtnKind.Secondary, IconKind.Tree, 120);
int y = _cardAdv.HeaderH + Theme.S12;
y = Row(_cardAdv, y, 52, "Area 路径", _fArea,
new Hint("改 Area 与补挂父项都按上面的「目标迭代」筛选已有工作项"));
y = Row(_cardAdv, y, 34, _btnFixArea, G, _btnLinkParent, G, "已关闭的工作项会被临时重新打开,完成后自动还原状态");
_errArea = AddErrorUnder(_cardAdv, _fArea);
y = Row(_cardAdv, y, 34, _btnFixArea, G, _btnLinkParent, G,
new Hint("Area 取第 2 步的「Area 路径」,按「目标迭代」筛选;已关闭的工作项会临时重新打开,完成后自动还原状态"));
_cardAdv.BodyH = y - _cardAdv.HeaderH + Theme.S8;
_cardAdv.SetSummary("按迭代批量修 Area / 补挂父项");
}
// ------------------------------------------------------------- 摆放助手
@@ -573,8 +587,15 @@ namespace TfsExcelImporter
{
public readonly string Text;
public readonly int MinWidth; // 运行时像素(构建时已含缩放)
/// <summary>必填项:标签后补一个红色 *,且列宽里预留它的位置。</summary>
public readonly bool Required;
public Lbl(string text, int minWidth) { Text = text; MinWidth = minWidth; }
public Lbl(string text, int minWidth, bool required = false)
{
Text = text;
MinWidth = minWidth;
Required = required;
}
}
/// <summary>紧跟在字段后面的灰色说明(Caption 字号 + Ink3):非标签、不参与标签列宽计算。</summary>
@@ -616,6 +637,21 @@ namespace TfsExcelImporter
};
lbl.SetBounds(x, y + (Theme.FieldH - lbl.PreferredHeight) / 2, lbl.PreferredWidth, lbl.PreferredHeight);
card.Controls.Add(lbl);
if (spec != null && spec.Required)
{
// 红色 * 紧跟标签右缘:颜色是这套界面里唯一的「必填」色板(与错误提示同色)
var star = new Label
{
Text = "*",
Font = Theme.Body,
ForeColor = Theme.Danger,
AutoSize = true,
BackColor = Theme.Card
};
star.SetBounds(x + lbl.PreferredWidth + Theme.Px(1),
y + (Theme.FieldH - star.PreferredHeight) / 2, star.PreferredWidth, star.PreferredHeight);
card.Controls.Add(star);
}
x += Math.Max(lbl.PreferredWidth, spec != null ? spec.MinWidth : 0) + Theme.Px(6);
lastLabel = label;
continue;
@@ -922,6 +958,7 @@ namespace TfsExcelImporter
_cardData.SetSummary(string.Join(" · ", new[]
{
string.IsNullOrEmpty(iter) ? "迭代未选" : iter,
string.IsNullOrWhiteSpace(_cmbArea.Text) ? "Area 未填" : "Area " + _cmbArea.Text.Trim(),
string.IsNullOrEmpty(fileName) ? "未选 Excel" : fileName,
_rows.Count > 0 ? _rows.Count + " 行" : "未预览",
string.IsNullOrWhiteSpace(_txtParentPbi.Text) ? "父 PBI 未填" : "父 PBI #" + _txtParentPbi.Text.Trim()
@@ -929,9 +966,6 @@ namespace TfsExcelImporter
_cardPreview.SetSummary(_rows.Count > 0
? "共 " + _rows.Count + " 行,可直接开始导入"
: "选择 Excel 后在这里核对将要写入的内容");
_cardAdv.SetSummary(string.IsNullOrWhiteSpace(_txtAreaPath.Text)
? "Area 未设置"
: "Area " + _txtAreaPath.Text.Trim());
}
/// <summary>第 4 步徽标:待办 / 进行中 / 完成 / 有失败。</summary>
@@ -992,6 +1026,7 @@ namespace TfsExcelImporter
_btnPreview.Enabled = !busy;
_btnTest.Enabled = !busy;
_btnLoadIterations.Enabled = !busy;
_btnLoadAreas.Enabled = !busy;
_btnFixArea.Enabled = !busy;
_btnLinkParent.Enabled = !busy;
_fExcel.SetSuffixEnabled(!busy); // 「浏览」已并入输入框尾部,随忙等一起禁用