Files
MES_Manage_View_V20/work/system-manual/build-full-manual.ps1

421 lines
22 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param([string]$ScriptRootOverride = '')
$ErrorActionPreference = 'Stop'
$scriptRootPath = if ($ScriptRootOverride) { $ScriptRootOverride } else { $PSScriptRoot }
$root = (Resolve-Path (Join-Path $scriptRootPath '..\..')).Path
$catalogPath = Join-Path $scriptRootPath 'full-page-catalog.json'
$screenshots = Join-Path $scriptRootPath 'full-screenshots'
$docDir = Join-Path $root 'doc'
$desktop = [Environment]::GetFolderPath('Desktop')
$sourceDocx = Join-Path $docDir '大连元利流体技术有限公司MES系统操作手册_系统管理篇_样稿.docx'
$fileName = '大连元利流体技术有限公司MES系统用户操作手册_V1.1_2026-07-23'
$docxPath = Join-Path $docDir ($fileName + '.docx')
$pdfPath = Join-Path $docDir ($fileName + '.pdf')
$desktopDocx = Join-Path $desktop ($fileName + '.docx')
$desktopPdf = Join-Path $desktop ($fileName + '.pdf')
$progressPath = Join-Path ([IO.Path]::GetTempPath()) 'mes-full-manual-build.log'
[IO.File]::WriteAllText($progressPath, "开始生成 $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`n", [Text.Encoding]::UTF8)
function Write-BuildProgress {
param([string]$Message)
[IO.File]::AppendAllText($progressPath, "$(Get-Date -Format 'HH:mm:ss') $Message`r`n", [Text.Encoding]::UTF8)
}
if (-not (Test-Path -LiteralPath $sourceDocx)) { throw "系统管理篇源文档不存在:$sourceDocx" }
if (-not (Test-Path -LiteralPath $catalogPath)) { throw "页面目录不存在:$catalogPath" }
$catalog = Get-Content -Raw -Encoding UTF8 $catalogPath | ConvertFrom-Json
if ($catalog.Length -ne 69) { throw "页面目录数量应为69实际为$($catalog.Length)" }
foreach ($page in $catalog) {
$imagePath = Join-Path $screenshots $page.screenshot
if (-not (Test-Path -LiteralPath $imagePath)) { throw "页面截图不存在:$imagePath" }
}
New-Item -ItemType Directory -Force -Path $docDir | Out-Null
$moduleChapters = [ordered]@{
'设备管理' = 7
'研发管理' = 8
'工艺管理' = 9
'计划排产' = 10
'生产管理' = 11
'异常提醒' = 12
'质量管理' = 13
'销售订单管理' = 14
}
function Join-Values {
param([object]$Values, [int]$Limit = 12)
if ($null -eq $Values -or $Values.Length -eq 0) { return '无' }
$items = @($Values | Select-Object -First $Limit)
$text = $items -join '、'
if ($Values.Length -gt $Limit) { $text += "$($Values.Length)" }
return $text
}
function Get-FilterType {
param([string]$Field)
if ($Field -match '日期|时间|开始|结束|完工|入账') { return '日期/时间或范围选择' }
if ($Field -match '状态|类型|类别|属性|性质|结果|部门|工位|工序|人员|指派|来源|班次|齐套|送检|入库|发货') { return '下拉、开关或选项选择' }
if ($Field -match '编码|编号|订单|任务ID|计划号|合同号|图号|名称|操作人|物料|产品') { return '文本输入或远程选择' }
return '输入或选择控件'
}
function Get-FilterGuide {
param([string]$Field)
if ($Field -match '日期|时间|开始|结束|完工|入账') { return '选择起止时间时按区间过滤;清空表示不限制该时间条件。' }
if ($Field -match '状态|类型|类别|属性|性质|结果|部门|工位|工序|人员|指派|来源|班次|齐套|送检|入库|发货') { return '从页面提供的选项中选择;“全部”或清空通常表示不限制该条件。' }
if ($Field -match '编码|编号|订单|任务ID|计划号|合同号|图号|名称|操作人|物料|产品') { return '输入关键值或从远程候选项中选择,再与其他已填写条件共同查询。' }
return '按页面控件输入或选择,点击【查询】后与其他条件共同生效。'
}
function Get-ActionGuide {
param([string]$Button)
if ($Button -match '查询|搜索|刷新') { return '读取当前筛选条件并刷新列表,不写入业务数据。' }
if ($Button -match '导出|Excel|下载') { return '将当前查询或当前页签结果导出/下载;导出前先确认筛选范围。' }
if ($Button -match '查看|预览|明细|记录|图纸|附件') { return '打开当前记录的只读详情、历史、图纸或附件;关闭窗口返回列表。' }
if ($Button -match '新增|新建|添加|设备$') { return '打开新增窗口,填写必填项并确认后写入;正式操作前检查编码和关联主数据。' }
if ($Button -match '编辑|修改|修正') { return '选择目标记录后打开编辑窗口,核对影响范围并保存修改。' }
if ($Button -match '删除|移除|作废|关闭订单') { return '执行不可逆或影响状态的操作;必须核对下游占用并在确认框中再次确认。' }
if ($Button -match '取消|关闭$|返回') { return '关闭当前窗口或放弃本次未提交内容,通常不写入数据。' }
if ($Button -match '保存|确定|提交|确认') { return '提交当前表单或选择结果;提交前复核必填项、数量、时间和目标记录。' }
if ($Button -match '批量|排产|下发|派工|指派') { return '先选择目标行并完整填写工位、人员、数量和时间,再批量生成或下发任务。' }
if ($Button -match '开始|开工|暂停|结束|完成|报工|送检|收验|收检|发料|收料|领料|退库') { return '改变任务或物料流转状态并形成操作记录;只对已核对的当前任务执行。' }
if ($Button -match '上传') { return '选择文件并上传到当前业务记录;上传前确认文件版本、格式和归属。' }
return '按当前选中记录执行对应业务动作;根据弹窗提示核对后再确认。'
}
function Test-WritePage {
param([object]$Page)
if ($Page.writeProcedures.Length -gt 0) { return $true }
return [bool](@($Page.buttons | Where-Object { $_ -match '新增|新建|编辑|修改|删除|保存|确定|提交|开始|结束|完成|报工|送检|收料|发料|排产|下发|上传' }).Length)
}
$word = $null
$doc = $null
$buildError = $null
try {
$word = New-Object -ComObject Word.Application
$word.Visible = $false
$word.DisplayAlerts = 0
$doc = $word.Documents.Open($sourceDocx, $false, $false)
$doc.SaveAs2($docxPath, 16)
Write-BuildProgress '已打开系统管理篇并另存完整篇'
$selection = $word.Selection
$normal = $doc.Styles.Item(-1)
function Set-ParagraphText {
param([object]$Paragraph, [string]$Text)
$range = $Paragraph.Range.Duplicate
if ($range.End -gt $range.Start) { $range.End = $range.End - 1 }
$range.Text = $Text
}
$headingMap = @{
'7 典型业务链路' = '15 典型业务链路'
'7.1 新增可登录人员' = '15.1 新增可登录人员'
'7.2 下线一个角色' = '15.2 下线一个角色'
'7.3 新增生产工位' = '15.3 新增生产工位'
'7.4 追溯一次报工操作' = '15.4 追溯一次报工操作'
'8 常见问题' = '16 常见问题'
'9 正式环境操作红线' = '17 正式环境操作红线'
}
$paragraphCount = $doc.Paragraphs.Count
for ($paragraphIndex = 1; $paragraphIndex -le $paragraphCount; $paragraphIndex++) {
$paragraph = $doc.Paragraphs.Item($paragraphIndex)
$text = ($paragraph.Range.Text -replace '[\r\a]+$', '').Trim()
if ($text -eq '系统管理篇(样稿)') {
Set-ParagraphText $paragraph '完整操作手册(系统管理至销售订单)'
} elseif ($text -eq '版本V1.0') {
Set-ParagraphText $paragraph '版本V1.1'
} elseif ($text -eq '编制日期2026年7月23日') {
Set-ParagraphText $paragraph '编制日期2026年7月23日'
} elseif ($text.StartsWith('当前正式菜单以数据库配置为准,包括人员管理')) {
Set-ParagraphText $paragraph '当前正式菜单以数据库配置为准。本版在系统管理基础上继续覆盖设备管理、研发管理、工艺管理、计划排产、生产管理、异常提醒、质量管理和销售订单管理共计74个正式功能页面。'
} elseif ($headingMap.ContainsKey($text)) {
Set-ParagraphText $paragraph $headingMap[$text]
}
}
Write-BuildProgress '已更新封面、阅读说明和后续章节编号'
$revisionTable = $null
for ($tableIndex = 1; $tableIndex -le $doc.Tables.Count; $tableIndex++) {
$candidate = $doc.Tables.Item($tableIndex)
if ($candidate.Columns.Count -ge 4) {
$firstCell = ($candidate.Cell(1, 1).Range.Text -replace '[\r\a]+$', '').Trim()
if ($firstCell -eq '版本') {
$revisionTable = $candidate
break
}
}
}
if ($null -ne $revisionTable) {
$newRow = $revisionTable.Rows.Add()
$newRow.Cells.Item(1).Range.Text = 'V1.1'
$newRow.Cells.Item(2).Range.Text = '2026-07-23'
$newRow.Cells.Item(3).Range.Text = '增加设备至销售订单八个模块'
$newRow.Cells.Item(4).Range.Text = '新增69个正式功能页、69张页面截图及完整数据关系说明'
}
Write-BuildProgress '已更新修订记录'
$insertAt = $null
$paragraphCount = $doc.Paragraphs.Count
for ($paragraphIndex = 1; $paragraphIndex -le $paragraphCount; $paragraphIndex++) {
$paragraph = $doc.Paragraphs.Item($paragraphIndex)
$text = ($paragraph.Range.Text -replace '[\r\a]+$', '').Trim()
if ($text -eq '15 典型业务链路') {
$insertAt = $paragraph.Range.Start
break
}
}
if ($null -eq $insertAt) { throw '未找到“15 典型业务链路”插入位置' }
$selection.SetRange($insertAt, $insertAt)
Write-BuildProgress '已定位模块章节插入位置'
function Move-End {
$selection.SetRange($selection.Range.End, $selection.Range.End)
}
function Add-Text {
param(
[string]$Text,
[double]$Size = 10.5,
[bool]$Bold = $false,
[int]$Align = 0,
[int]$Color = 0,
[double]$Before = 0,
[double]$After = 6,
[double]$FirstIndentCm = 0
)
$selection.Font.NameFarEast = '宋体'
$selection.Font.Name = 'Arial'
$selection.Font.Size = $Size
$selection.Font.Bold = [int]$Bold
$selection.Font.Color = $Color
$selection.ParagraphFormat.Alignment = $Align
$selection.ParagraphFormat.SpaceBefore = $Before
$selection.ParagraphFormat.SpaceAfter = $After
$selection.ParagraphFormat.FirstLineIndent = $word.CentimetersToPoints($FirstIndentCm)
$selection.TypeText($Text)
$selection.TypeParagraph()
}
function Add-Heading {
param([string]$Text, [int]$Level)
$styleId = switch ($Level) { 1 { -2 } 2 { -3 } default { -4 } }
$selection.Style = $doc.Styles.Item($styleId)
$selection.Font.NameFarEast = '微软雅黑'
$selection.Font.Name = 'Arial'
$selection.Font.Color = 0x8F4A31
$selection.Font.Bold = 1
$selection.ParagraphFormat.KeepWithNext = -1
$selection.TypeText($Text)
$selection.TypeParagraph()
$selection.Style = $normal
}
function Add-Table {
param([object[]]$Rows, [double[]]$WidthsCm = @())
if ($Rows.Count -eq 0) { return }
$range = $selection.Range
$table = $doc.Tables.Add($range, $Rows.Count, $Rows[0].Count)
$table.Borders.Enable = 1
$table.Range.Font.NameFarEast = '宋体'
$table.Range.Font.Name = 'Arial'
$table.Range.Font.Size = 8.5
$table.Range.ParagraphFormat.SpaceAfter = 0
for ($r = 0; $r -lt $Rows.Count; $r++) {
for ($c = 0; $c -lt $Rows[$r].Count; $c++) {
$table.Cell($r + 1, $c + 1).Range.Text = [string]$Rows[$r][$c]
$table.Cell($r + 1, $c + 1).VerticalAlignment = 1
if ($WidthsCm.Count -gt $c) {
$table.Cell($r + 1, $c + 1).Width = $word.CentimetersToPoints($WidthsCm[$c])
}
}
}
$table.Rows.Item(1).Range.Bold = 1
$table.Rows.Item(1).Shading.BackgroundPatternColor = 0xE6D8D0
$table.Rows.Item(1).Range.ParagraphFormat.Alignment = 1
$table.AllowAutoFit = -1
$selection.SetRange($table.Range.End, $table.Range.End)
$selection.TypeParagraph()
}
function Add-Note {
param([string]$Title, [string]$Text, [string]$Kind = 'info')
$range = $selection.Range
$table = $doc.Tables.Add($range, 1, 1)
$table.Borders.Enable = 1
$cell = $table.Cell(1, 1)
$cell.Range.Text = $Title + '' + $Text
$cell.Range.Font.NameFarEast = '微软雅黑'
$cell.Range.Font.Name = 'Arial'
$cell.Range.Font.Size = 9.5
$cell.Range.ParagraphFormat.SpaceAfter = 0
if ($Kind -eq 'warn') { $cell.Shading.BackgroundPatternColor = 0xD9F2FF }
elseif ($Kind -eq 'danger') { $cell.Shading.BackgroundPatternColor = 0xD9E2F3 }
else { $cell.Shading.BackgroundPatternColor = 0xF4F1EE }
$selection.SetRange($table.Range.End, $table.Range.End)
$selection.TypeParagraph()
}
function Add-Figure {
param([string]$Path, [string]$Caption, [double]$MaxWidthCm = 16.4)
$selection.ParagraphFormat.Alignment = 1
$shape = $selection.InlineShapes.AddPicture($Path, $false, $true, $selection.Range)
$shape.LockAspectRatio = -1
$maxWidth = $word.CentimetersToPoints($MaxWidthCm)
if ($shape.Width -gt $maxWidth) { $shape.Width = $maxWidth }
$selection.SetRange($shape.Range.End, $shape.Range.End)
$selection.TypeParagraph()
Add-Text $Caption 9 $false 1 0x777777 0 8 0
}
function Add-PageBreak {
$selection.InsertBreak(7)
}
Add-PageBreak
foreach ($moduleName in $moduleChapters.Keys) {
$chapter = $moduleChapters[$moduleName]
$modulePages = @($catalog | Where-Object { $_.module -eq $moduleName } | Sort-Object pageIndex)
if ($modulePages.Count -eq 0) { continue }
Write-BuildProgress "开始生成第$chapter章 $moduleName"
Add-Heading ("$chapter $moduleName") 1
Add-Text $modulePages[0].moduleRelation 10.5 $false 0 0 0 6 0.74
Add-Note '本章范围' "本章按正式菜单顺序说明$($modulePages.Count)个功能页。截图仅执行登录、页面导航和默认查询,没有点击任何业务写入按钮。" 'warn'
$moduleRows = [System.Collections.Generic.List[object]]::new()
$moduleRows.Add(@('序号', '功能页面', '主要用途'))
for ($i = 0; $i -lt $modulePages.Count; $i++) {
$moduleRows.Add(@(($i + 1).ToString(), $modulePages[$i].title, $modulePages[$i].purpose))
}
Add-Table $moduleRows.ToArray() @(1.2, 3.4, 10.8)
for ($pageIndex = 0; $pageIndex -lt $modulePages.Count; $pageIndex++) {
$page = $modulePages[$pageIndex]
$section = "$chapter.$($pageIndex + 1)"
Write-BuildProgress "生成 $section $($page.title)"
Add-PageBreak
Add-Heading ("$section $($page.title)") 2
Add-Text $page.purpose 10.5 $false 0 0 0 6 0.74
$writePage = Test-WritePage $page
Add-Table @(
@('项目', '说明'),
@('菜单路径', "$moduleName > $($page.title)"),
@('页面路由', $page.route),
@('数据性质', $(if ($writePage) { '查询为只读;业务按钮可能新增、修改或改变状态' } else { '以只读查询、汇总或导出为主' }))
) @(3.2, 12.2)
Add-Figure (Join-Path $screenshots $page.screenshot) ("$section-1 $moduleName - $($page.title)正式页面")
Add-Heading ("$section.1 搜索与过滤") 3
if ($page.searchFields.Length -gt 0) {
$filterRows = [System.Collections.Generic.List[object]]::new()
$filterRows.Add(@('条件', '控件形式', '使用说明'))
foreach ($field in $page.searchFields) {
$filterRows.Add(@($field, (Get-FilterType $field), (Get-FilterGuide $field)))
}
Add-Table $filterRows.ToArray() @(3.2, 4.2, 8.2)
Add-Note '组合规则' '多个条件同时填写时,由页面一并传入后台查询,通常取条件交集。若查询不到记录,先逐项清空状态、日期和编号条件,再重新查询;精确或包含匹配以对应页面后台过程为准。' 'info'
} else {
Add-Text '本页面没有独立的顶部搜索区,进入页面后按默认条件加载数据,或通过页签、树节点、当前工位/人员上下文切换数据范围。' 10.5 $false 0 0 0 6 0.74
}
if ($page.tabs.Length -gt 0) {
Add-Table @(
@('页面页签/视图', '切换说明'),
@((Join-Values $page.tabs 20), '切换页签会改变展示维度或数据范围;导出前确认当前页签。')
) @(5.2, 10.4)
}
Add-Heading ("$section.2 列表字段与页面结果") 3
if ($page.tableColumns.Length -gt 0) {
$columnRows = [System.Collections.Generic.List[object]]::new()
$columnRows.Add(@('分组', '字段/列'))
for ($columnIndex = 0; $columnIndex -lt $page.tableColumns.Length; $columnIndex += 8) {
$endIndex = [Math]::Min($columnIndex + 7, $page.tableColumns.Length - 1)
$group = @($page.tableColumns[$columnIndex..$endIndex])
$columnRows.Add(@("$($columnIndex + 1)-$($endIndex + 1)", ($group -join '、')))
}
Add-Table $columnRows.ToArray() @(3.2, 12.4)
Add-Text '列表用于展示当前筛选结果。存在横向滚动条时,应左右滚动核对全部状态、数量、时间和操作列;存在分页时,页码和每页条数只影响当前显示范围。' 10.5 $false 0 0 0 6 0.74
} else {
Add-Text '该页面主要使用自定义看板、卡片或复用组件展示结果,字段布局以截图及页面实时内容为准。' 10.5 $false 0 0 0 6 0.74
}
Add-Heading ("$section.3 功能按钮与操作") 3
if ($page.buttons.Length -gt 0) {
$buttonRows = [System.Collections.Generic.List[object]]::new()
$buttonRows.Add(@('按钮/命令', '操作说明'))
foreach ($button in $page.buttons) {
$buttonRows.Add(@($button, (Get-ActionGuide $button)))
}
Add-Table $buttonRows.ToArray() @(4.2, 11.4)
} else {
Add-Text '本页主要通过大尺寸现场操作按钮、图标按钮、表格行操作或复用组件执行命令。操作前必须先选择当前任务,并根据按钮文字和确认提示核对目标记录。' 10.5 $false 0 0 0 6 0.74
}
if ($writePage) {
Add-Note '正式操作注意' '新增、编辑、删除、开始、结束、排产、报工、送检、收料、上传等操作会写入业务数据或改变状态。本文档编制未实际执行这些按钮;正式使用时应按岗位权限和业务单据逐条确认。' 'danger'
}
Add-Heading ("$section.4 前后数据关系") 3
Add-Text $page.moduleRelation 10.5 $false 0 0 0 6 0.74
Add-Table @(
@('关系层次', '内容'),
@('页面输入', $(if ($page.searchFields.Length -gt 0) { Join-Values $page.searchFields 18 } else { '当前登录人员、角色、工位、默认状态或上游任务上下文' })),
@('只读查询过程/接口', (Join-Values $page.readProcedures 10)),
@('写入过程/接口', (Join-Values $page.writeProcedures 10)),
@('页面输出', $(if ($page.tableColumns.Length -gt 0) { Join-Values $page.tableColumns 16 } else { '自定义看板、统计结果、状态卡片或复用组件结果' }))
) @(4.2, 11.4)
Add-Note '技术核对说明' '过程名称来自当前前端源码和已引用接口文件,用于说明数据流向;动态过程名、通用审批接口及服务端二次调用可能不会全部显示在本表中。' 'info'
}
Write-BuildProgress "完成第$chapter章 $moduleName"
}
Write-BuildProgress '开始更新目录并导出PDF'
if ($doc.TablesOfContents.Count -gt 0) {
$doc.TablesOfContents.Item(1).Update()
}
$doc.Save()
Write-BuildProgress 'DOCX正文及目录已保存'
$doc.ExportAsFixedFormat($pdfPath, 17)
Write-BuildProgress 'PDF已导出'
# WPS 兼容层在大型文档导出后读取统计信息可能长时间阻塞,统计校验改由生成后的 Open XML/PDF 文件完成。
$doc.Close($false)
[void][Runtime.InteropServices.Marshal]::ReleaseComObject($doc)
$doc = $null
$word.Quit()
[void][Runtime.InteropServices.Marshal]::ReleaseComObject($word)
$word = $null
Copy-Item -LiteralPath $docxPath -Destination $desktopDocx -Force
Copy-Item -LiteralPath $pdfPath -Destination $desktopPdf -Force
Write-Output "DOCX=$docxPath"
Write-Output "PDF=$pdfPath"
Write-BuildProgress '完整操作手册生成完成'
} catch {
$buildError = $_
Write-BuildProgress ("生成失败:" + $_.Exception.Message)
} finally {
if ($doc) {
try { $doc.Close($false) } catch { }
}
if ($word) {
try { $word.Quit() } catch { }
}
if ($doc) { [void][Runtime.InteropServices.Marshal]::ReleaseComObject($doc) }
if ($word) { [void][Runtime.InteropServices.Marshal]::ReleaseComObject($word) }
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
if ($buildError) { throw $buildError }