- 更新 README.md,明确父 PBI Id 的选择方式,增加加载 PBI 列表的功能,提升用户在导入时的便利性。 - 在 MainForm.cs 中添加加载 PBI 列表的按钮,优化父项选择逻辑,确保用户能够方便地选择合适的父项。 - 修改 MainFormUi.cs,将父 PBI Id 输入框替换为下拉框,支持从列表中选择,提升用户体验。 - 在 TfsClient.cs 中实现获取父项候选的逻辑,确保用户能够根据项目和迭代路径获取相关的工作项信息。 - 调整界面布局,确保父 PBI 选择与其他输入项一致,提升整体视觉效果。
158 lines
13 KiB
Markdown
158 lines
13 KiB
Markdown
# TFSImport
|
||
|
||
TFS 2013 工作项批量导入工具集,用于把 Excel/CSV 里的工时台账(标题、状态、指派人、完成量、日期等)批量写成团队项目里的工作项,并挂到指定的父项(Product Backlog Item / 占位 Backlog Item)下,使其在「积压工作」页和任务板可见。
|
||
|
||
目标环境:
|
||
|
||
| 项 | 值 |
|
||
| --- | --- |
|
||
| 服务器 | `http://pdtfs01.dev.goldenstand.cn:8080/tfs/DefaultCollection` |
|
||
| 团队项目 | `Tools_Dev` |
|
||
| 团队 | `DataTechnologies` |
|
||
| 迭代 | `2026年Sprint\2026年Sprint10` |
|
||
| 认证 | 域账号 NTLM(内网直连,不走代理) |
|
||
|
||
## 工具一览
|
||
|
||
仓库只有一套工具:[TfsExcelImporter](TfsExcelImporter)(C# WinForms 桌面版),数据源为 `.xlsx`(默认模板随程序发布),图形界面预览后导入。
|
||
|
||
通信走 TFS Client OM(VS/Team Explorer 同款内部 SOAP):该 TFS 2013 实例只有 `/_apis/projects` 通,`wit/fields`、`wit/classificationnodes`、`wit/wiql` 全部 404,REST 路线走不通。原先另有一套 Python CLI(REST `/_apis/wit/*`)实现,因上述限制无法在本环境工作,已移除。
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
TFSImport/
|
||
├── data/
|
||
│ └── work_items.csv # 本地工时台账(含人员/项目,不入库)
|
||
├── templates/
|
||
│ └── TFS导入模板.xlsx # 默认模板:构建时嵌入 exe,「导出Excel模板」原样导出
|
||
├── tools/
|
||
│ ├── make_icon.py # 生成 app.ico(9 个尺寸帧,纯标准库 SDF 栅格化 + 手写 PNG/ICO)
|
||
│ ├── pe_icon.py # 解析 exe 的 PE 图标资源,导出某帧 PNG(换图标后核对用)
|
||
│ └── win_screenshot.py # 冒烟截图:拉起 exe、PrintWindow 抓窗、导出标题栏图标(ctypes)
|
||
└── TfsExcelImporter/ # C# WinForms 桌面版
|
||
├── TfsExcelImporter.csproj # net48 + ClosedXML + TFS ExtendedClient 14.*
|
||
├── FodyWeavers.xml # Costura 单文件打包配置(便携版)
|
||
├── NativeLibraryLoader.cs # 释放并预加载内嵌的 TFS native DLL
|
||
├── AppIcon.cs # 从内嵌 app.ico 取 Form.Icon(窗口/任务栏)
|
||
├── app.ico # 应用图标(16~256 共 9 档,tools/make_icon.py 生成)
|
||
├── MainForm.cs # 导入 / 修 Area / 补挂父项流程
|
||
├── MainFormUi.cs # 界面外壳:步骤卡片布局、手风琴折叠、状态呈现
|
||
├── Theme.cs # 设计 token(颜色/字号/间距/缩放)与自绘控件
|
||
├── app.manifest # System DPI 感知,像素尺寸按缩放换算
|
||
├── TfsClient.cs # TFS Client OM 封装
|
||
├── ExcelService.cs # 读 Excel、导出默认模板、时间/数值归一
|
||
├── Config.cs # exe 同目录 config.json 读写
|
||
└── config.json # 桌面版配置
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 桌面版
|
||
|
||
```powershell
|
||
dotnet build TfsExcelImporter.sln -c Release
|
||
# 或用 Visual Studio 打开 TfsExcelImporter.sln(目标框架 net48,无需装 VS 也能 dotnet build)
|
||
TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe
|
||
```
|
||
|
||
「导出Excel模板」优先用 exe 同目录的 `TFS导入模板.xlsx`(现场可替换),没有则从 exe 内嵌资源导出——**分发时只带一个 exe 就够,不用再附带模板文件**。
|
||
|
||
应用图标是 `TfsExcelImporter/app.ico`(蓝色渐变圆角底 + 白色上升箭头),csproj 里 `ApplicationIcon` 写进 exe 的 Win32 图标资源,同时以托管资源再嵌一份给 `Form.Icon` 用(多尺寸帧,高 DPI 下不糊)。要改设计就改 `tools/make_icon.py` 后重跑,覆盖 `app.ico` 再编译:
|
||
|
||
```powershell
|
||
python tools/make_icon.py # 重新生成 app.ico,并出预览图 tools/icon_preview.png
|
||
python tools/win_screenshot.py TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe tools\_win.png
|
||
```
|
||
|
||
界面按 4 步排:顶部状态条常驻显示服务器/项目/类型与连接状态,四张卡片一次只展开一张(展开一张会把其它收成一行摘要),预览表格永远占满剩余高度。
|
||
|
||
1. **步骤 1 连接**:填 URL、项目、工作项类型与账号密码(认证固定 NTLM,用户名可写 `user` 或 `DOMAIN\user`),点「测试连接」——右上状态灯变绿即连通。
|
||
2. **步骤 2 选数据**:点「加载迭代列表」选目标迭代(也可手填完整路径);**Area 路径同样在这一步填,且为必填**(新导入的工作项都写到该 Area,留空会导致团队视图看不到,所以导入前会被拦下),点「加载 Area 列表」可从服务器拉真实 Area 树,也可手填完整路径;点「导出 Excel 模板」拿默认模板,或「浏览...」选已有 `.xlsx`。需要挂父项时填「父 PBI Id」(默认全部挂同一个;Excel 里加 `PBI Id` 列可逐行指定不同父项),点「加载 PBI 列表」可拉出该迭代下的 Product Backlog Item(下拉显示「#Id 标题 · 状态」,选中自动回填纯 Id),也可直接手输 Id。字段校验**不可绕过**,不合法时日志会直接告诉你改哪一列。
|
||
3. **步骤 3 预览核对**:选完文件自动读表,表格里逐行核对;缺迭代/文件路径无效时红字直接标在对应输入框下方,不再弹框。
|
||
4. **步骤 4 导入**:底部操作条点「开始导入 TFS」,进度条 + `n/m` + 成功/跳过/失败三个计数实时更新,日志抽屉逐行输出 `OK/SKIP/FAIL` 与工作项 ID;跑完点「打开报告 CSV」看逐行结果。主按钮右侧的「跳过已存在标题」(默认不勾)是去重开关,按「项目/类型/迭代 + 标题」判重——它只作用于导入这一个动作,勾上后立即生效于下一次导入;导入跑起来后该勾选框会置灰(开关在一次导入开始时只读一次)。
|
||
5. 历史数据 Area 不对、或漏挂父项时,展开底部「高级工具」用「按迭代修 Area」「补挂父项」批量纠正;两者都按第 2 步的「目标迭代 / Area 路径」筛选已有工作项。
|
||
|
||
桌面版按 System DPI 感知实现,界面尺寸随 Windows 显示缩放换算;**改了显示缩放比例要重启程序**才生效。
|
||
|
||
### 便携版(单 exe,免安装)
|
||
|
||
构建产物只有一个文件:`TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe`(约 9.3 MB)。依赖和资源全部内嵌:
|
||
|
||
| 内容 | 打包方式 |
|
||
| --- | --- |
|
||
| 58 个托管 DLL(ClosedXML / OpenXml / TFS Client OM …) | `Costura.Fody` 内嵌,运行时经 `AssemblyResolve` 从内存加载,不落盘 |
|
||
| `Microsoft.WITDataStore32/64.dll`(纯 win32,TFS OM 用) | 独立内嵌,启动时按进程位数释放到 `%TEMP%\TfsExcelImporter\native\{内容hash}` 并 `LoadLibrary` 预加载 |
|
||
| 默认模板 `TFS导入模板.xlsx` | 内嵌资源,「导出Excel模板」时释放;exe 同目录放同名文件则优先用它 |
|
||
| `config.json` | 首次保存时写到 exe 同目录(便携);目录只读时回退 `%AppData%\TfsExcelImporter\config.json` |
|
||
|
||
分发:
|
||
|
||
```powershell
|
||
mkdir dist
|
||
copy TfsExcelImporter\bin\Release\net48\TfsExcelImporter.exe dist\
|
||
```
|
||
|
||
把 `dist\TfsExcelImporter.exe` 拷到任意目录双击即用。想连配置一起带走,就把 exe 同目录的 `config.json` 一起拷。
|
||
|
||
前提:目标机需有 .NET Framework 4.8(Win10 1903+ / Win11 系统自带,一般无需额外安装)。`FodyWeavers.xml` 是 Costura 的打包配置,删掉它或用 `-p:FodyDisable=true` 构建即回到「exe + 一堆 DLL」的普通输出。
|
||
|
||
## 配置说明
|
||
|
||
桌面版的配置落在 exe 同目录的 `config.json`(界面改动后自动写回);exe 目录不可写时回退到 `%AppData%\TfsExcelImporter\config.json`。
|
||
|
||
配置的语义、留空字段如何解析见 [Config.cs](TfsExcelImporter/Config.cs),关键字段:
|
||
|
||
| 键 | 说明 |
|
||
| --- | --- |
|
||
| `TfsUrl` / `Project` / `WorkItemType` | 目标集合、项目、工作项类型(`Task` 或 `Product Backlog Item`) |
|
||
| `IterationPath` | 可只写 `2026年Sprint\2026年Sprint10`,程序自动补项目名前缀 |
|
||
| `AreaPath` | 区域路径;导入必填,界面上与迭代同在第 2 步卡片(可点「加载 Area 列表」从 TFS 分类树拉取),值直接写进 `System.AreaPath` |
|
||
| `Username` / `Password` | 认证固定 NTLM,用户名可写 `user` 或 `DOMAIN\user` |
|
||
| `FinancialProjectRef` / `CompletedRef` / `LogDateRef` / `OriginalEstimateRef` | 自定义字段引用名;留空则按显示名在服务器字段表里查(支持前缀模糊匹配,如 `Completed` → `Completed Work`) |
|
||
| `ParentPbiId` | 所有 Task 默认挂接的父 PBI Id(界面上可从「加载 PBI 列表」里挑,落库只存纯数字);Excel 每行 `PBI Id` 列可覆盖 |
|
||
| `SkipExistingTitles` | 同项目/同类型/同迭代下按标题去重;**默认不勾选**,需要去重时手动勾 |
|
||
| `DefaultState` | Excel `State` 列留空时的默认值 |
|
||
| `LastExcelPath` | 上次选中的数据文件,界面回填用 |
|
||
|
||
> 配置里含明文密码,请勿提交到共享仓库;`config.json` 保持空值,凭据本地填写。
|
||
|
||
## Excel 列
|
||
|
||
桌面版 Excel 模板 = 仓库里的 `templates\TFS导入模板.xlsx`,界面「导出Excel模板」直接复制它:
|
||
|
||
`Title`、`State`、`Assigned To`、`FinancialProject`、`Completed`、`LogDate`、`PBI Id`、`Description`
|
||
|
||
写入 TFS 的对应关系:
|
||
|
||
| 模板列 | 目标字段 |
|
||
| --- | --- |
|
||
| `Description` | `System.Description` |
|
||
| `Completed` | 显示名 `Completed`(模糊匹配到 `Completed Work`)解析出的字段 |
|
||
| —(无列) | `Original Estimate` 取该行 `Completed` 的值;引用名留空则按显示名 `Original Estimate` 解析,可写在 `OriginalEstimateRef` 里固定 |
|
||
|
||
系统日期列(`Created Date` / `Closed Date`)已从模板移除:`System.CreatedDate` 由服务器按导入时间填写,`Microsoft.VSTS.Common.ClosedDate` 不再写入、保持为空。
|
||
|
||
真实台账含人员与项目信息,不入库,放在 `data\work_items.csv`(已在 `.gitignore` 里)。
|
||
|
||
- 表头按名匹配,顺序不限,`AssignedTo` / `Assigned To` 等价;缺列按模板顺序兜底,但该列已被别的表头占用时不套用位置(旧版模板文件不会串列)。
|
||
- 空 `Title` 的行直接跳过;`State` 为空时用 `DefaultState`;未识别的列(如 `Group`)忽略。
|
||
- 日期支持 `yyyy-MM-dd[ / ][ HH:mm[:ss]]` 等格式,Excel 日期序列号(如 `46300`)会被还原成日期。
|
||
|
||
## 环境坑点
|
||
|
||
这些是实测结论,改代码前先看:
|
||
|
||
- **wit REST 路由全 404**:TFS 2013 RTM/低 Update 不带 `/_apis/wit/*`,`Classification.asmx?WSDL` 也被禁 → 桌面版整体走 Client OM(NuGet `Microsoft.TeamFoundationServer.ExtendedClient` 钉 `14.*`,对应 VS2015 客户端代际;15.x 不兼容 TFS 2013)。
|
||
- **502 Bad Gateway**:TFS OM 内部用 `HttpWebRequest`,默认跟随 IE 系统代理,代理会拦内网 TFS。桌面版固定把 `WebRequest.DefaultWebProxy` 置空强制直连,界面不提供代理开关(本机若只能经代理上网,程序会直连失败)。
|
||
- **Area 与迭代同源**:两者都是 `ICommonStructureService` 的分类树(Area = `ProjectModelHierarchy`,迭代 = `ProjectLifecycle`),`GetNodesXml` 深度展开后取每个节点的 `Path`,再由 `NormalizeNodePath` 去掉项目名前缀与 `Area`/`Iteration` 根段、拼回 `项目\子...`。项目根 Area 归一化后为空会被过滤,所以下拉里只有子区域;列表拉不到时手填完整路径一样可用。`TF30063` 是「服务器拒绝当前身份」(填步骤 1 的账号密码),与 DNS/代理导致的连不上是两码事,界面已分开提示。
|
||
- **TF400276 / 关闭状态只读**:改已关闭工作项的 Area 会被拒;`FixAreaAsync` 走「重新打开 → 改 Area → 还原状态」的状态循环,中途失败回滚状态。
|
||
- **`System.CreatedDate` 写不进去**:OM 14.x 没有 `SaveFlags.BypassRules`(15.x 才加),该字段服务器托管;`Microsoft.VSTS.Common.ClosedDate` 在 `State=Done` 时按普通字段可尝试写入。模板已去掉日期列,所以导入不再走这条路——若某模板要求 `Done` 必填 Closed Date,需另加列或改回写日期。
|
||
- **字段校验不可绕过**:OM 14.x 没有 `SaveFlags.BypassRules`,旧界面上那个 `bypassRules` 勾选从未真正生效,已移除。校验失败时日志与 report CSV 直接给出「字段(原因) → 怎么改」,列表字段还会列出允许值,按提示改 Excel 对应列后重跑即可。
|
||
- **孤立 Task 在积压工作页不可见**:TFS 2013 的 backlog/任务板按「父项 → 子任务」组织,没挂父链接的 Task 只能在查询里看到,所以导入必须带父项(界面填父 PBI Id,或 Excel 里加 `PBI Id` 列)。
|
||
- **DNS 解析失败 / 超时**:多为 VPN 或网络断开,不是代码问题;桌面版会对这类异常附加排查提示。
|
||
|
||
## 验证
|
||
|
||
桌面版改完,先用只含几行的 Excel 跑一次小批量导入(先删服务器已有同标题项或勾操作条上「跳过已存在标题」),再在积压工作页展开父项确认子任务可见;改动涉及界面布局或图标时,跑 `python tools/win_screenshot.py` 抓图核对。确认无误后再跑全量。
|