refactor: 移除不再使用的配置文件与代码,优化用户界面
- 删除 config.json 和 requirements.txt 文件,移除 Python 版相关代码,简化项目结构。 - 更新 README.md,调整工具说明,强调仅保留 C# WinForms 版本。 - 在 MainForm.cs 中添加输入框的占位提示,提升用户体验。 - 更新 .gitignore,添加 debug.log 文件以记录运行时日志。 - 移除与 Python 版相关的示例 CSV 文件,确保数据源一致性。
This commit is contained in:
@@ -23,31 +23,18 @@ namespace TfsExcelImporter
|
||||
|
||||
public TfsClient(AppConfig cfg)
|
||||
{
|
||||
ICredentials cred;
|
||||
var mode = (cfg.AuthMode ?? "ntlm").ToLowerInvariant();
|
||||
if (mode == "default")
|
||||
{
|
||||
cred = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = string.IsNullOrEmpty(cfg.Domain) ? cfg.Username : cfg.Domain + "\\" + cfg.Username;
|
||||
cred = new NetworkCredential(user, cfg.Password ?? "");
|
||||
}
|
||||
// 认证固定 NTLM:用户名可写 user(用当前登录域),也可写 DOMAIN\user 显式指定域。
|
||||
var user = (cfg.Username ?? "").Trim();
|
||||
var pwd = cfg.Password ?? "";
|
||||
var sep = user.IndexOf('\\');
|
||||
ICredentials cred = sep > 0
|
||||
? new NetworkCredential(user.Substring(sep + 1), pwd, user.Substring(0, sep))
|
||||
: new NetworkCredential(user, pwd);
|
||||
_tpc = new TfsTeamProjectCollection(new Uri(cfg.TfsUrl), cred);
|
||||
|
||||
// TFS OM 内部用 HttpWebRequest 传输,默认跟随 IE 系统代理——实测代理会拦内网 TFS 返回 502。
|
||||
// 默认直连;勾选"使用系统代理"时保留系统代理并带 Windows 凭据过代理认证。
|
||||
if (!cfg.UseSystemProxy)
|
||||
{
|
||||
WebRequest.DefaultWebProxy = new WebProxy(); // 空 WebProxy = 不走代理,直连
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = WebRequest.DefaultWebProxy ?? new WebProxy();
|
||||
p.Credentials = CredentialCache.DefaultCredentials;
|
||||
WebRequest.DefaultWebProxy = p;
|
||||
}
|
||||
// 因此固定直连:把 DefaultWebProxy 置空,不读取本机系统代理设置。
|
||||
WebRequest.DefaultWebProxy = new WebProxy();
|
||||
}
|
||||
|
||||
/// <summary>WorkItemStore 惰性初始化(首次调用会下载字段/类型元数据,耗时数秒属正常)。</summary>
|
||||
@@ -187,14 +174,16 @@ namespace TfsExcelImporter
|
||||
|
||||
/// <summary>
|
||||
/// 创建工作项(OM Save)。返回新工作项 ID。
|
||||
/// 说明:OM 14.x 的 SaveFlags 无 BypassRules(15.x 才加入,且 15.x 不兼容 TFS 2013)——
|
||||
/// 说明:字段校验不可绕过——OM 14.x 的 SaveFlags 无 BypassRules(15.x 才加入,且 15.x 不兼容 TFS 2013),
|
||||
/// 因此校验失败时直接把「哪个字段 / 什么原因 / 怎么改」写进异常(见 CheckInvalid),
|
||||
/// 不做静默降级,避免产出违反模板规则的脏数据。
|
||||
/// System.CreatedDate 为服务器管理的只读字段,无法写入(取导入时间);
|
||||
/// Microsoft.VSTS.Common.ClosedDate 在 State=Done 时按普通规则通常可写:尝试写入,
|
||||
/// 校验或保存失败则清空该字段降级为普通保存(保证行导入成功)。
|
||||
/// parentPbiId 非空时,通过 System.LinkTypes.Hierarchy(Reverse=Parent) 挂接到该 PBI。
|
||||
/// </summary>
|
||||
public async Task<int> CreateWorkItemAsync(string project, string workItemType,
|
||||
List<KeyValuePair<string, object>> fields, bool bypassRules, int? parentPbiId = null)
|
||||
List<KeyValuePair<string, object>> fields, int? parentPbiId = null)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
@@ -269,14 +258,77 @@ namespace TfsExcelImporter
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把 wi.Validate() 的结果整理成「字段(原因) → 怎么改」的单行提示。
|
||||
/// 单行是硬要求:这段文字会写进 import_report.csv,换行会破坏 CSV 的行结构。
|
||||
/// </summary>
|
||||
private static string CheckInvalid(WorkItem wi)
|
||||
{
|
||||
var invalid = wi.Validate();
|
||||
if (invalid.Count == 0) return null;
|
||||
var names = new StringBuilder();
|
||||
var sb = new StringBuilder();
|
||||
foreach (Field f in invalid)
|
||||
names.Append(f.Name).Append("(").Append(FieldStatusText(f)).Append("); ");
|
||||
return names.ToString();
|
||||
{
|
||||
if (sb.Length > 0) sb.Append(";");
|
||||
sb.Append(f.Name).Append('(').Append(FieldStatusText(f)).Append(") → ")
|
||||
.Append(FixHint(f));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按字段状态给出修复动作:导入失败时用户拿到的应该是「补哪一列、改成什么」,
|
||||
/// 而不是只有一句「字段校验失败」。注意校验无法绕过(OM 14.x 无 BypassRules)。
|
||||
/// </summary>
|
||||
private static string FixHint(Field f)
|
||||
{
|
||||
switch (f.Status.ToString())
|
||||
{
|
||||
case "InvalidEmpty":
|
||||
case "InvalidNull":
|
||||
return "必填但没值:Excel 补上这一列,并确认字段映射是否对上" + AllowedHint(f);
|
||||
case "InvalidTooLong":
|
||||
return "值超长:截断到上限内(TFS 字符串字段默认上限 255 字符)";
|
||||
case "InvalidType":
|
||||
return "类型不匹配,该字段是 " + FieldTypeText(f) + ":检查单元格里是文本还是数字/日期";
|
||||
case "InvalidValue":
|
||||
case "InvalidList":
|
||||
return "值不在允许范围" + AllowedHint(f);
|
||||
case "InvalidFormat":
|
||||
return "格式不对:日期写 yyyy-MM-dd,数字不要带单位或千分位";
|
||||
case "InvalidComputed":
|
||||
return "计算字段不可写:把这一列从 Excel 去掉";
|
||||
case "InvalidNotNew":
|
||||
return "只在新建时可设置:不要对已存在工作项改这一列";
|
||||
default:
|
||||
return "按服务器返回的规则修正该字段的值";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>列出字段的允许值(列表字段 InvalidList 的主因),最多展开 8 个。</summary>
|
||||
private static string AllowedHint(Field f)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vals = new List<string>();
|
||||
int total = 0;
|
||||
foreach (object v in f.AllowedValues)
|
||||
{
|
||||
total++;
|
||||
if (vals.Count < 8) vals.Add(Convert.ToString(v));
|
||||
}
|
||||
if (vals.Count == 0) return "";
|
||||
return "(允许值: " + string.Join(" / ", vals.ToArray())
|
||||
+ (total > vals.Count ? " 等 " + total + " 项" : "") + ")";
|
||||
}
|
||||
catch { return ""; }
|
||||
}
|
||||
|
||||
/// <summary>字段类型(String / Integer / DateTime ...),用于提示类型不匹配。</summary>
|
||||
private static string FieldTypeText(Field f)
|
||||
{
|
||||
try { return f.FieldDefinition.FieldType.ToString(); }
|
||||
catch { return "未知类型"; }
|
||||
}
|
||||
|
||||
/// <summary>把 FieldStatus 翻译为可读原因(用字符串匹配,避免不同 OM 版本枚举成员差异)。</summary>
|
||||
|
||||
Reference in New Issue
Block a user