chore: 初始化 TFS 2013 工作项批量导入工具集

- TfsExcelImporter: WinForms 桌面版,走 TFS Client OM(本环境 wit REST 路由 404,OM 是唯一可行通道)
- tfs_import.py: REST 版命令行导入器,供 wit 接口可用的 TFS 实例使用
- templates: Excel 导入模板及其 OOXML 源部件
- README: 两套工具定位、配置、环境坑点与验证方式
- 真实台账与本机工作笔记不入库,仅提交 work_items.example.csv 说明列结构
This commit is contained in:
chenshoulin
2026-11-01 11:21:20 +08:00
commit 8d1af0d11c
23 changed files with 2423 additions and 0 deletions
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
</Types>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>
<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>
<borders count="1"><border/></borders>
<cellStyleXfs count="1"><xf/></cellStyleXfs>
<cellXfs count="1"><xf/></cellXfs>
</styleSheet>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
<sheet name="工作项" sheetId="1" r:id="rId1"/>
</sheets>
</workbook>
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1">
<c r="A1" t="inlineStr"><is><t>Title</t></is></c>
<c r="B1" t="inlineStr"><is><t>State</t></is></c>
<c r="C1" t="inlineStr"><is><t>Assigned To</t></is></c>
<c r="D1" t="inlineStr"><is><t>FinancialProject</t></is></c>
<c r="E1" t="inlineStr"><is><t>Completed</t></is></c>
<c r="F1" t="inlineStr"><is><t>LogDate</t></is></c>
<c r="G1" t="inlineStr"><is><t>Created Date</t></is></c>
<c r="H1" t="inlineStr"><is><t>Closed Date</t></is></c>
<c r="I1" t="inlineStr"><is><t>PBI Id</t></is></c>
<c r="J1" t="inlineStr"><is><t>Notes</t></is></c>
</row>
<row r="2">
<c r="A2" t="inlineStr"><is><t>20261012 - 示例:货载报告解析并上传凭证受托报告相关内容</t></is></c>
<c r="B2" t="inlineStr"><is><t>To Do</t></is></c>
<c r="C2" t="inlineStr"><is><t>李晓柯</t></is></c>
<c r="D2" t="inlineStr"><is><t>信托 - 上海信托</t></is></c>
<c r="E2" t="inlineStr"><is><t>5</t></is></c>
<c r="F2" t="inlineStr"><is><t>2026-10-12</t></is></c>
<c r="G2" t="inlineStr"><is><t>2026-10-30 11:14:41</t></is></c>
<c r="H2" t="inlineStr"><is><t></t></is></c>
<c r="I2" t="inlineStr"><is><t>219975</t></is></c>
<c r="J2" t="inlineStr"><is><t>示例行,导入前请删除</t></is></c>
</row>
<row r="3">
<c r="A3" t="inlineStr"><is><t>20261009 示例:受托报告bug修复</t></is></c>
<c r="B3" t="inlineStr"><is><t>Done</t></is></c>
<c r="C3" t="inlineStr"><is><t>陈卫宇</t></is></c>
<c r="D3" t="inlineStr"><is><t>信托 - 上海信托</t></is></c>
<c r="E3" t="inlineStr"><is><t>8</t></is></c>
<c r="F3" t="inlineStr"><is><t>2026-10-09</t></is></c>
<c r="G3" t="inlineStr"><is><t>2026-10-10 18:20:00</t></is></c>
<c r="H3" t="inlineStr"><is><t>2026-10-30 18:00:00</t></is></c>
<c r="I3" t="inlineStr"><is><t>219975</t></is></c>
<c r="J3" t="inlineStr"><is><t>示例行,导入前请删除</t></is></c>
</row>
</sheetData>
</worksheet>
+104
View File
@@ -0,0 +1,104 @@
# Build TFS import xlsx from XML parts as a stored (uncompressed) ZIP.
# Pure in-process .NET byte APIs: no external processes, no assembly loading.
# Output file name uses ASCII+char trick to stay encoding-safe.
$ErrorActionPreference = 'Stop'
$parts = Join-Path $PSScriptRoot '_parts'
$outName = 'TFS' + [char]0x5BFC + [char]0x5165 + [char]0x6A21 + [char]0x677F + '.xlsx'
$out = Join-Path $PSScriptRoot $outName
# ---------- CRC32 (IEEE 802.3) ----------
$tbl = New-Object 'System.Int64[]' 256
for ($i = 0; $i -lt 256; $i++) {
$c = [int64]$i
for ($k = 0; $k -lt 8; $k++) {
if (($c -band 1) -eq 1) { $c = (3988292384 -bxor ($c -shr 1)) } else { $c = ($c -shr 1) }
$c = $c -band 4294967295
}
$tbl[$i] = $c
}
function Get-Crc32([byte[]]$data) {
$crc = [int64]4294967295
foreach ($b in $data) {
$idx = [int](($crc -bxor [int64]$b) -band 255)
$crc = ($tbl[$idx] -bxor ($crc -shr 8)) -band 4294967295
}
return ($crc -bxor 4294967295)
}
# ---------- entry list (source relative path, zip entry name) ----------
$entries = @(
@{ src = '[Content_Types].xml'; zip = '[Content_Types].xml' },
@{ src = '_rels\.rels'; zip = '_rels/.rels' },
@{ src = 'xl\workbook.xml'; zip = 'xl/workbook.xml' },
@{ src = 'xl\_rels\workbook.xml.rels'; zip = 'xl/_rels/workbook.xml.rels' },
@{ src = 'xl\styles.xml'; zip = 'xl/styles.xml' },
@{ src = 'xl\worksheets\sheet1.xml'; zip = 'xl/worksheets/sheet1.xml' }
)
$ms = New-Object System.IO.MemoryStream
$central = New-Object System.Collections.ArrayList
foreach ($e in $entries) {
$srcPath = Join-Path $parts $e['src']
$bytes = [System.IO.File]::ReadAllBytes($srcPath)
$nameBytes = [System.Text.Encoding]::ASCII.GetBytes([string]$e['zip'])
$crc = Get-Crc32 $bytes
$offset = $ms.Position
$ms.Write([BitConverter]::GetBytes([uint32]0x04034b50), 0, 4) # local header signature
$ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method: stored
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # mod time
$ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # mod date 1980-01-01
$ms.Write([BitConverter]::GetBytes([uint32]$crc), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint32]$bytes.Length), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint16]$nameBytes.Length), 0, 2)
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len
$ms.Write($nameBytes, 0, $nameBytes.Length)
$ms.Write($bytes, 0, $bytes.Length)
[void]$central.Add(@{ name = $nameBytes; crc = $crc; size = $bytes.Length; offset = $offset })
}
# ---------- central directory ----------
$cdStart = $ms.Position
foreach ($c in $central) {
$ms.Write([BitConverter]::GetBytes([uint32]0x02014b50), 0, 4) # central header signature
$ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # made by
$ms.Write([BitConverter]::GetBytes([uint16]20), 0, 2) # version needed
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # flags
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # method
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # time
$ms.Write([BitConverter]::GetBytes([uint16]33), 0, 2) # date
$ms.Write([BitConverter]::GetBytes([uint32]$c['crc']), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint32]$c['size']), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint16]$c['name'].Length), 0, 2)
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # extra len
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk start
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # internal attrs
$ms.Write([BitConverter]::GetBytes([uint32]0), 0, 4) # external attrs
$ms.Write([BitConverter]::GetBytes([uint32]$c['offset']), 0, 4)
$ms.Write($c['name'], 0, $c['name'].Length)
}
$cdSize = $ms.Position - $cdStart
# ---------- end of central directory ----------
$ms.Write([BitConverter]::GetBytes([uint32]0x06054b50), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # disk number
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # cd disk
$ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2)
$ms.Write([BitConverter]::GetBytes([uint16]$central.Count), 0, 2)
$ms.Write([BitConverter]::GetBytes([uint32]$cdSize), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint32]$cdStart), 0, 4)
$ms.Write([BitConverter]::GetBytes([uint16]0), 0, 2) # comment len
[System.IO.File]::WriteAllBytes($out, $ms.ToArray())
$ms.Dispose()
Write-Output ('CREATED ' + $out + ' size=' + (New-Object System.IO.FileInfo $out).Length)