下面给你一份完整可用的 VBA,已经把你要的两点都加上了:
- RULES 里不写通配符也能匹配(代码自动补
*) - 橙色(RGB=255,192,0 / #FFC000)表示“不刷”
- 只要 P 或 S 目标单元格是这个橙色 → 该格不写值、不变色、不记录 issues
- 原厚(Org/ORI THK)为空/非数字/<=0 → P/S 不刷、不变色、不记录 issues
- 只在生成的 _gauged.xlsx 里刷(原文件不动)
另外我也按之前建议加了“文字跳过”能力(不影响你用颜色):
- Description/Item 里包含
SKIP→ 该行 P/S 都不刷 - 包含
SKIP(P)→ 只不刷 P - 包含
SKIP(S)→ 只不刷 S
✅ 你直接把模块代码整段替换成下面这份
入口宏:G_BatchGauged
Option Explicit
'================= 常量:不刷颜色(橙色 #FFC000)=================
Private Const SKIP_R As Long = 255
Private Const SKIP_G As Long = 192
Private Const SKIP_B As Long = 0
Private Const SKIP_COLOR As Long = (SKIP_B * 65536#) + (SKIP_G * 256#) + SKIP_R 'RGB(255,192,0)
'================= 全局配置 =================
Private gDefaultMinP As Double, gDefaultMaxP As Double
Private gDefaultMinS As Double, gDefaultMaxS As Double
Private gRulesWs As Worksheet
Private gHeaderMode As String ' "BV" / "GEN"
'==== Issues 汇总 ====
Private gIssuesWs As Worksheet
Private gIssueRow As Long
Private gCurrentSrcFile As String
Private gCurrentOutFile As String
Private gNegCount As Long, gExceedCount As Long, gSkipSheetCount As Long
'==== 多区块(UPPER/MID/LOWER)结构 ====
Private Type GaugedBlock
partName As String
OrgCol As Long
PCol As Long
SCol As Long
End Type
'================= 主入口 =================
Public Sub G_BatchGauged()
Dim rootPath As String
rootPath = G_PickFolder()
If rootPath = "" Then Exit Sub
If Not G_PromptHeaderMode() Then Exit Sub
On Error Resume Next
Set gRulesWs = ThisWorkbook.Worksheets("RULES")
On Error GoTo 0
If gRulesWs Is Nothing Then
MsgBox "找不到 RULES 工作表。", vbExclamation
Exit Sub
End If
'从 RULES 读取默认腐蚀范围(DescContains="__DEFAULT__")
G_LoadDefaultRangeFromRules
G_InitIssuesSheet
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Randomize
G_ProcessFolderRecursive rootPath
Application.DisplayAlerts = True
Application.ScreenUpdating = True
G_FinalizeIssuesSheet
MsgBox "完成。" & vbCrLf & _
"模式: " & gHeaderMode & vbCrLf & _
"默认范围 P: " & gDefaultMinP & "-" & gDefaultMaxP & " / S: " & gDefaultMinS & "-" & gDefaultMaxS & vbCrLf & _
"ISSUES:" & vbCrLf & _
" <0(红): " & gNegCount & vbCrLf & _
" >OrgThk(黄): " & gExceedCount & vbCrLf & _
" 跳过Sheet: " & gSkipSheetCount, vbInformation
End Sub
'================= 选择表头模式 =================
Private Function G_PromptHeaderMode() As Boolean
Dim s As String
s = InputBox("选择船级社表头模式:" & vbCrLf & _
"1 = BV(严格表头)" & vbCrLf & _
"2 = 其他(通用,支持 UPPER/MID/LOWER 多区块)", _
"Header Mode", "2")
If s = "" Then G_PromptHeaderMode = False: Exit Function
If Trim$(s) = "1" Then
gHeaderMode = "BV"
ElseIf Trim$(s) = "2" Then
gHeaderMode = "GEN"
Else
MsgBox "请输入 1 或 2。", vbExclamation
G_PromptHeaderMode = False
Exit Function
End If
G_PromptHeaderMode = True
End Function
'================= 从 RULES 读取默认范围 =================
'规则:DescContains="__DEFAULT__" 的行
Private Sub G_LoadDefaultRangeFromRules()
'兜底
gDefaultMinP = 0.1: gDefaultMaxP = 0.3
gDefaultMinS = 0.1: gDefaultMaxS = 0.3
Dim lastRow As Long, r As Long
lastRow = gRulesWs.Cells(gRulesWs.Rows.Count, 3).End(xlUp).Row
If lastRow < 2 Then Exit Sub
For r = 2 To lastRow
Dim kDesc As String
kDesc = UCase$(Trim$(CStr(gRulesWs.Cells(r, 3).Value2)))
If kDesc = "__DEFAULT__" Then
gDefaultMinP = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 6).Value2))
gDefaultMaxP = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 7).Value2))
gDefaultMinS = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 8).Value2))
gDefaultMaxS = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 9).Value2))
Exit Sub
End If
Next r
End Sub
'================= Issues Sheet =================
Private Sub G_InitIssuesSheet()
On Error Resume Next
Set gIssuesWs = ThisWorkbook.Worksheets("ISSUES")
On Error GoTo 0
If gIssuesWs Is Nothing Then
Set gIssuesWs = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
gIssuesWs.Name = "ISSUES"
End If
gIssuesWs.Cells.Clear
gIssuesWs.Range("A1:K1").Value = Array("OutputFile(_gauged)", "Sheet", "Row(Click)", "Side", "Issue", "Description", "Item", "Part", "OrgThk", "Gauged", "Cell")
gIssuesWs.Rows(1).Font.Bold = True
gIssueRow = 2
gNegCount = 0
gExceedCount = 0
gSkipSheetCount = 0
End Sub
Private Sub G_FinalizeIssuesSheet()
If gIssueRow <= 2 Then gIssuesWs.Range("A2").Value2 = "(No issues found)"
gIssuesWs.Columns("A:K").AutoFit
On Error Resume Next
gIssuesWs.Range("A1:K1").AutoFilter
On Error GoTo 0
End Sub
Private Sub G_LogIssue(ByVal filePath As String, ByVal sheetName As String, ByVal rowNum As Long, _
ByVal side As String, ByVal issue As String, _
ByVal desc As String, ByVal item As String, ByVal partName As String, _
ByVal orgThk As Double, ByVal gauged As Variant, _
ByVal cellAddr As String)
With gIssuesWs
.Cells(gIssueRow, 1).Value2 = filePath
On Error Resume Next
.Hyperlinks.Add Anchor:=.Cells(gIssueRow, 1), Address:=filePath, TextToDisplay:=filePath
On Error GoTo 0
.Cells(gIssueRow, 2).Value2 = sheetName
.Cells(gIssueRow, 3).Value2 = rowNum
If cellAddr <> "" And rowNum > 0 Then
On Error Resume Next
.Hyperlinks.Add Anchor:=.Cells(gIssueRow, 3), Address:=filePath, _
SubAddress:="'" & sheetName & "'!" & cellAddr, TextToDisplay:=CStr(rowNum)
On Error GoTo 0
End If
.Cells(gIssueRow, 4).Value2 = side
.Cells(gIssueRow, 5).Value2 = issue
.Cells(gIssueRow, 6).Value2 = desc
.Cells(gIssueRow, 7).Value2 = item
.Cells(gIssueRow, 8).Value2 = partName
.Cells(gIssueRow, 9).Value2 = orgThk
.Cells(gIssueRow, 10).Value2 = gauged
.Cells(gIssueRow, 11).Value2 = cellAddr
End With
gIssueRow = gIssueRow + 1
End Sub
'================= 递归遍历文件夹 =================
Private Sub G_ProcessFolderRecursive(ByVal folderPath As String)
Dim fso As Object, folder As Object, subF As Object, fil As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set folder = fso.GetFolder(folderPath)
Dim resultFolder As String
resultFolder = folderPath & "\_gauged_result"
If Dir(resultFolder, vbDirectory) = "" Then MkDir resultFolder
For Each fil In folder.Files
Dim nameL As String
nameL = LCase$(fil.Name)
If Right$(nameL, 5) = ".xlsx" Then
If Left$(fil.Name, 2) <> "~$" And InStr(1, fil.Name, "_gauged", vbTextCompare) = 0 Then
G_ProcessOneFile CStr(fil.Path), resultFolder
End If
End If
Next fil
For Each subF In folder.SubFolders
If LCase$(subF.Name) <> "_gauged_result" Then G_ProcessFolderRecursive CStr(subF.Path)
Next subF
End Sub
'================= 处理单个文件(只改 _gauged)=================
Private Sub G_ProcessOneFile(ByVal fullName As String, ByVal resultFolder As String)
gCurrentSrcFile = fullName
Dim srcBase As String
srcBase = Mid(fullName, InStrRev(fullName, "\") + 1)
Dim outName As String
outName = Replace(srcBase, ".xlsx", "_gauged.xlsx")
gCurrentOutFile = resultFolder & "\" & outName
'1) 打开原文件 -> SaveCopyAs -> 关闭原文件不保存
Dim wbSrc As Workbook
On Error Resume Next
Set wbSrc = Workbooks.Open(fullName, ReadOnly:=True)
On Error GoTo 0
If wbSrc Is Nothing Then Exit Sub
On Error Resume Next
Kill gCurrentOutFile
On Error GoTo 0
wbSrc.SaveCopyAs gCurrentOutFile
wbSrc.Close SaveChanges:=False
'2) 打开 _gauged 文件,刷数据只发生在这里
Dim wbOut As Workbook
On Error Resume Next
Set wbOut = Workbooks.Open(gCurrentOutFile, ReadOnly:=False)
On Error GoTo 0
If wbOut Is Nothing Then Exit Sub
Dim ws As Worksheet
For Each ws In wbOut.Worksheets
G_ApplyRulesOnSheet ws
Next ws
wbOut.Save
wbOut.Close SaveChanges:=False
End Sub
'================= 单 Sheet 处理 =================
Private Sub G_ApplyRulesOnSheet(ByVal ws As Worksheet)
If gHeaderMode = "BV" Then
G_ApplySheet_BV ws
Else
G_ApplySheet_Generic ws
End If
End Sub
'================= BV =================
Private Sub G_ApplySheet_BV(ByVal ws As Worksheet)
Dim cDesc As Long, cItem As Long, cThk As Long, cP As Long, cS As Long, hdrRow As Long
If Not G_ResolveColumns_BV_Robust(ws, cDesc, cItem, cThk, cP, cS, hdrRow) Then
gSkipSheetCount = gSkipSheetCount + 1
G_LogIssue gCurrentOutFile, ws.Name, 0, "", "HEADER_NOT_FOUND", "", "", "", 0, "", ""
Exit Sub
End If
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, cThk).End(xlUp).Row
If lastRow <= hdrRow Then Exit Sub
Dim lastDescRaw As String: lastDescRaw = ""
Dim r As Long
For r = hdrRow + 1 To lastRow
Dim thkV As Variant
thkV = ws.Cells(r, cThk).Value2
'原厚无效(空/非数字/<=0)-> 跳过:不刷、不变色
If (Not IsNumeric(thkV)) Then GoTo NextR
If CDbl(thkV) <= 0 Then GoTo NextR
Dim orgThk As Double: orgThk = CDbl(thkV)
Dim descRaw As String, itemRaw As String
descRaw = G_CellText(ws, r, cDesc)
itemRaw = G_CellText(ws, r, cItem)
If descRaw <> "" Then lastDescRaw = descRaw Else descRaw = lastDescRaw
If itemRaw = "" Then itemRaw = descRaw
G_WriteOneRowOnePart ws, r, orgThk, descRaw, itemRaw, ws.Name, "BV", ws.Cells(r, cP), ws.Cells(r, cS)
NextR:
Next r
End Sub
'================= GEN(通用)=================
Private Sub G_ApplySheet_Generic(ByVal ws As Worksheet)
Dim blocks() As GaugedBlock
Dim cDesc As Long, cItem As Long, hdrRow As Long
If G_ResolveBlocks_UpperMidLower(ws, cDesc, cItem, hdrRow, blocks) Then
G_ApplyByBlocks ws, cDesc, cItem, hdrRow, blocks
Exit Sub
End If
Dim cThk As Long, cP As Long, cS As Long
If Not G_ResolveColumns_Generic_Single(ws, cDesc, cItem, cThk, cP, cS, hdrRow) Then
gSkipSheetCount = gSkipSheetCount + 1
G_LogIssue gCurrentOutFile, ws.Name, 0, "", "HEADER_NOT_FOUND", "", "", "", 0, "", ""
Exit Sub
End If
Dim one(0 To 0) As GaugedBlock
one(0).partName = "GEN"
one(0).OrgCol = cThk
one(0).PCol = cP
one(0).SCol = cS
G_ApplyByBlocks ws, cDesc, cItem, hdrRow, one
End Sub
Private Sub G_ApplyByBlocks(ByVal ws As Worksheet, ByVal cDesc As Long, ByVal cItem As Long, ByVal hdrRow As Long, ByRef blocks() As GaugedBlock)
Dim i As Long, lastRow As Long, lr As Long
lastRow = 0
For i = LBound(blocks) To UBound(blocks)
lr = ws.Cells(ws.Rows.Count, blocks(i).OrgCol).End(xlUp).Row
If lr > lastRow Then lastRow = lr
Next i
If lastRow <= hdrRow Then Exit Sub
Dim lastDescRaw As String: lastDescRaw = ""
Dim r As Long
For r = hdrRow + 1 To lastRow
Dim descRaw As String, itemRaw As String
descRaw = G_CellText(ws, r, cDesc)
If cItem > 0 Then itemRaw = G_CellText(ws, r, cItem) Else itemRaw = ""
If descRaw <> "" Then lastDescRaw = descRaw Else descRaw = lastDescRaw
If itemRaw = "" Then itemRaw = descRaw
For i = LBound(blocks) To UBound(blocks)
Dim thkV As Variant
thkV = ws.Cells(r, blocks(i).OrgCol).Value2
'原厚无效(空/非数字/<=0)-> 跳过:不刷、不变色
If (Not IsNumeric(thkV)) Then GoTo NextBlock
If CDbl(thkV) <= 0 Then GoTo NextBlock
Dim orgThk As Double: orgThk = CDbl(thkV)
G_WriteOneRowOnePart ws, r, orgThk, descRaw, itemRaw, ws.Name, blocks(i).partName, _
ws.Cells(r, blocks(i).PCol), ws.Cells(r, blocks(i).SCol)
NextBlock:
Next i
Next r
End Sub
'================= 单行单Part:支持 SKIP 文本 + 颜色跳过 =================
Private Sub G_WriteOneRowOnePart(ByVal ws As Worksheet, ByVal rowNum As Long, ByVal orgThk As Double, _
ByVal descRaw As String, ByVal itemRaw As String, _
ByVal sheetName As String, ByVal partName As String, _
ByVal pCell As Range, ByVal sCell As Range)
Dim descU As String, itemU As String
descU = UCase$(descRaw)
itemU = UCase$(itemRaw)
'文字跳过:SKIP / SKIP(P) / SKIP(S)
Dim skipAll As Boolean, skipP As Boolean, skipS As Boolean
skipAll = (InStr(descU, "SKIP") > 0 Or InStr(itemU, "SKIP") > 0)
skipP = (InStr(descU, "SKIP(P)") > 0 Or InStr(itemU, "SKIP(P)") > 0)
skipS = (InStr(descU, "SKIP(S)") > 0 Or InStr(itemU, "SKIP(S)") > 0)
If skipAll And (Not skipP) And (Not skipS) Then Exit Sub
'side tag(可选)
Dim onlyP As Boolean, onlyS As Boolean
onlyP = G_HasSideTag(descRaw, "P") Or G_HasSideTag(itemRaw, "P")
onlyS = G_HasSideTag(descRaw, "S") Or G_HasSideTag(itemRaw, "S")
Dim descClean As String, itemClean As String
descClean = G_NormalizeText(descRaw)
itemClean = G_NormalizeText(itemRaw)
'颜色跳过(橙色):谁是橙色就不刷谁
Dim pSkipByColor As Boolean, sSkipByColor As Boolean
pSkipByColor = G_IsSkipColor(pCell)
sSkipByColor = G_IsSkipColor(sCell)
'最终跳过判断
If skipP Then pSkipByColor = True
If skipS Then sSkipByColor = True
If onlyP And Not onlyS Then
If Not pSkipByColor Then
G_WriteGauged ws, pCell, orgThk, descClean, itemClean, sheetName, partName, True, rowNum
End If
ElseIf onlyS And Not onlyP Then
If Not sSkipByColor Then
G_WriteGauged ws, sCell, orgThk, descClean, itemClean, sheetName, partName, False, rowNum
End If
Else
If Not pSkipByColor Then
G_WriteGauged ws, pCell, orgThk, descClean, itemClean, sheetName, partName, True, rowNum
End If
If Not sSkipByColor Then
G_WriteGauged ws, sCell, orgThk, descClean, itemClean, sheetName, partName, False, rowNum
End If
End If
End Sub
'================= 写入 Gauged =================
Private Sub G_WriteGauged(ByVal ws As Worksheet, ByVal tgt As Range, ByVal orgThk As Double, _
ByVal desc As String, ByVal item As String, _
ByVal sheetName As String, ByVal partName As String, _
ByVal isP As Boolean, ByVal rowNum As Long)
'再保险:目标格是橙色则不刷
If G_IsSkipColor(tgt) Then Exit Sub
If orgThk <= 0 Then Exit Sub
Dim mn As Double, mx As Double
If Not G_GetRangeFromRules(desc, item, G_GetBaseFileName(gCurrentSrcFile), sheetName, partName, isP, mn, mx) Then
If isP Then
mn = gDefaultMinP: mx = gDefaultMaxP
Else
mn = gDefaultMinS: mx = gDefaultMaxS
End If
End If
Dim corr As Double
corr = mn + Rnd() * (mx - mn)
Dim g As Double
g = Round(orgThk - corr, 1)
tgt.Interior.Pattern = xlNone
Dim side As String: If isP Then side = "P" Else side = "S"
Dim cellAddr As String: cellAddr = tgt.Address(False, False)
If g < 0 Then
tgt.Value2 = ""
tgt.Interior.Color = RGB(255, 0, 0)
gNegCount = gNegCount + 1
G_LogIssue gCurrentOutFile, ws.Name, rowNum, side, "NEGATIVE (<0)", desc, item, partName, orgThk, g, cellAddr
ElseIf g > orgThk Then
tgt.Value2 = orgThk
tgt.Interior.Color = RGB(255, 255, 0)
gExceedCount = gExceedCount + 1
G_LogIssue gCurrentOutFile, ws.Name, rowNum, side, "EXCEED (>OrgThk) -> CORRECTED", desc, item, partName, orgThk, g, cellAddr
Else
tgt.Value2 = g
End If
End Sub
Private Function G_GetBaseFileName(ByVal fullPath As String) As String
Dim s As String
s = Mid$(fullPath, InStrRev(fullPath, "\") + 1)
G_GetBaseFileName = UCase$(s)
End Function
'================= 橙色跳过判断(支持条件格式:DisplayFormat)=================
Private Function G_IsSkipColor(ByVal cell As Range) As Boolean
On Error GoTo Fallback
If cell.DisplayFormat.Interior.Color = SKIP_COLOR Then
G_IsSkipColor = True
Else
G_IsSkipColor = False
End If
Exit Function
Fallback:
'某些版本 DisplayFormat 可能异常,退回普通 Interior
G_IsSkipColor = (cell.Interior.Color = SKIP_COLOR)
End Function
'================= RULES 匹配(自动补*;同Priority更具体优先)=================
' A FileLike | B SheetLike | C DescContains | D ItemLike | E PartLike | F P_min | G P_max | H S_min | I S_max | J Priority
Private Function G_GetRangeFromRules(ByVal desc As String, ByVal item As String, _
ByVal fileName As String, ByVal sheetName As String, ByVal partName As String, _
ByVal isP As Boolean, ByRef outMin As Double, ByRef outMax As Double) As Boolean
Dim lastRow As Long
lastRow = gRulesWs.Cells(gRulesWs.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then Exit Function
Dim bestPr As Double: bestPr = -1E+99
Dim bestSpec As Long: bestSpec = -1
Dim r As Long
For r = 2 To lastRow
Dim kFile As String, kSheet As String, kDesc As String, kItem As String, kPart As String
kFile = UCase$(Trim$(CStr(gRulesWs.Cells(r, 1).Value2)))
kSheet = UCase$(Trim$(CStr(gRulesWs.Cells(r, 2).Value2)))
kDesc = UCase$(Trim$(CStr(gRulesWs.Cells(r, 3).Value2)))
kItem = UCase$(Trim$(CStr(gRulesWs.Cells(r, 4).Value2)))
kPart = UCase$(Trim$(CStr(gRulesWs.Cells(r, 5).Value2)))
'跳过默认行
If kDesc = "__DEFAULT__" Then GoTo NextRule
'FileLike:自动补*
If kFile <> "" Then
kFile = G_AutoPattern_FileSheetPart(kFile)
If Not (UCase$(fileName) Like kFile) Then GoTo NextRule
End If
'SheetLike:自动补*
If kSheet <> "" Then
kSheet = G_AutoPattern_FileSheetPart(kSheet)
If Not (UCase$(sheetName) Like kSheet) Then GoTo NextRule
End If
'DescContains:空=通用;不空=包含匹配
If kDesc <> "" Then
If InStr(UCase$(desc), kDesc) <= 0 Then GoTo NextRule
End If
'ItemLike:自动补(数字默认前缀匹配;非数字默认包含匹配)
If kItem <> "" Then
kItem = Replace(kItem, "*", "*")
kItem = G_AutoPattern_Item(kItem)
If Not (UCase$(item) Like kItem) Then GoTo NextRule
End If
'PartLike:自动补*
If kPart <> "" Then
kPart = G_AutoPattern_FileSheetPart(kPart)
If Not (G_NormalizePart(partName) Like kPart) Then GoTo NextRule
End If
Dim pr As Double
pr = Val(gRulesWs.Cells(r, 10).Value2) '空=0
Dim spec As Long
spec = 0
If kFile <> "" Then spec = spec + 1
If kSheet <> "" Then spec = spec + 1
If kDesc <> "" Then spec = spec + 1
If kItem <> "" Then spec = spec + 1
If kPart <> "" Then spec = spec + 1
If (pr > bestPr) Or (pr = bestPr And spec > bestSpec) Then
bestPr = pr
bestSpec = spec
If isP Then
outMin = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 6).Value2))
outMax = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 7).Value2))
Else
outMin = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 8).Value2))
outMax = CDbl(G_ZeroIfBlank(gRulesWs.Cells(r, 9).Value2))
End If
If outMax < outMin Then
Dim tmp As Double: tmp = outMin: outMin = outMax: outMax = tmp
End If
G_GetRangeFromRules = True
End If
NextRule:
Next r
End Function
'自动补*(File/Sheet/Part):无通配符 -> *xxx*
Private Function G_AutoPattern_FileSheetPart(ByVal token As String) As String
Dim t As String
t = UCase$(Trim$(token))
If InStr(t, "*") > 0 Or InStr(t, "?") > 0 Then
G_AutoPattern_FileSheetPart = t
Else
G_AutoPattern_FileSheetPart = "*" & t & "*"
End If
End Function
'自动补(Item):无通配符 -> 数字用 前缀匹配 "6*";非数字用 包含 "*S1-W1*"
Private Function G_AutoPattern_Item(ByVal token As String) As String
Dim t As String
t = UCase$(Trim$(token))
If InStr(t, "*") > 0 Or InStr(t, "?") > 0 Then
G_AutoPattern_Item = t
Exit Function
End If
If G_IsAllDigits(t) Then
G_AutoPattern_Item = t & "*"
Else
G_AutoPattern_Item = "*" & t & "*"
End If
End Function
Private Function G_IsAllDigits(ByVal s As String) As Boolean
Dim i As Long
If Len(s) = 0 Then G_IsAllDigits = False: Exit Function
For i = 1 To Len(s)
If Mid$(s, i, 1) < "0" Or Mid$(s, i, 1) > "9" Then
G_IsAllDigits = False
Exit Function
End If
Next i
G_IsAllDigits = True
End Function
Private Function G_NormalizePart(ByVal s As String) As String
Dim t As String
t = UCase$(Trim$(s))
Do While InStr(t, " ") > 0
t = Replace(t, " ", " ")
Loop
G_NormalizePart = t
End Function
Private Function G_ZeroIfBlank(ByVal v As Variant) As Double
If IsNumeric(v) Then G_ZeroIfBlank = CDbl(v) Else G_ZeroIfBlank = 0#
End Function
'================= 表头识别(BV + GEN)=================
Private Function G_ResolveColumns_BV_Robust(ByVal ws As Worksheet, _
ByRef cDesc As Long, ByRef cItem As Long, ByRef cThk As Long, ByRef cP As Long, ByRef cS As Long, _
ByRef hdrRow As Long) As Boolean
cDesc = 0: cItem = 0: cThk = 0: cP = 0: cS = 0: hdrRow = 0
Dim r As Long, c As Long
For r = 1 To 40
For c = 1 To 300
Dim v As String
v = G_CellText(ws, r, c)
If v = "" Then GoTo NextCell
If cDesc = 0 And InStr(v, "DESCRIP") > 0 Then cDesc = c: hdrRow = G_MaxL(hdrRow, r)
If cItem = 0 And v = "ITEM" Then cItem = c: hdrRow = G_MaxL(hdrRow, r)
If cThk = 0 And G_IsOrgThkHeader(ws, r, c) Then cThk = c: hdrRow = G_MaxL(hdrRow, r)
If cP = 0 And v = "P" Then cP = c: hdrRow = G_MaxL(hdrRow, r)
If cS = 0 And v = "S" Then cS = c: hdrRow = G_MaxL(hdrRow, r)
NextCell:
Next c
Next r
G_ResolveColumns_BV_Robust = (cDesc > 0 And cItem > 0 And cThk > 0 And cP > 0 And cS > 0)
End Function
Private Function G_ResolveColumns_Generic_Single(ByVal ws As Worksheet, _
ByRef cDesc As Long, ByRef cItem As Long, ByRef cThk As Long, ByRef cP As Long, ByRef cS As Long, _
ByRef hdrRow As Long) As Boolean
cDesc = 0: cItem = 0: cThk = 0: cP = 0: cS = 0: hdrRow = 0
Dim r As Long, c As Long
For r = 1 To 30
For c = 1 To 200
Dim v As String
v = G_CellText(ws, r, c)
If v = "" Then GoTo NextCell
If cDesc = 0 And InStr(v, "DESCRIP") > 0 Then cDesc = c: hdrRow = G_MaxL(hdrRow, r)
If cItem = 0 And InStr(v, "ITEM") > 0 Then cItem = c: hdrRow = G_MaxL(hdrRow, r)
If cThk = 0 And G_IsOrgThkHeader(ws, r, c) Then cThk = c: hdrRow = G_MaxL(hdrRow, r)
If cP = 0 And v = "P" Then cP = c: hdrRow = G_MaxL(hdrRow, r)
If cS = 0 And v = "S" Then cS = c: hdrRow = G_MaxL(hdrRow, r)
NextCell:
Next c
Next r
G_ResolveColumns_Generic_Single = (cDesc > 0 And cThk > 0 And cP > 0 And cS > 0)
End Function
Private Function G_ResolveBlocks_UpperMidLower(ByVal ws As Worksheet, _
ByRef cDesc As Long, ByRef cItem As Long, ByRef hdrRow As Long, _
ByRef blocks() As GaugedBlock) As Boolean
Dim r As Long, c As Long
Dim upperAnchor As Long, midAnchor As Long, lowerAnchor As Long
upperAnchor = 0: midAnchor = 0: lowerAnchor = 0
cDesc = 0: cItem = 0: hdrRow = 0
For r = 1 To 10
For c = 1 To 300
Dim v As String
v = G_CellText(ws, r, c)
If v = "" Then GoTo NextCell1
If cDesc = 0 And InStr(v, "DESCRIP") > 0 Then cDesc = c: hdrRow = G_MaxL(hdrRow, r)
If cItem = 0 And InStr(v, "ITEM") > 0 Then cItem = c: hdrRow = G_MaxL(hdrRow, r)
If upperAnchor = 0 And InStr(v, "UPPER") > 0 And InStr(v, "PART") > 0 Then upperAnchor = c: hdrRow = G_MaxL(hdrRow, r)
If midAnchor = 0 And InStr(v, "MID") > 0 And InStr(v, "PART") > 0 Then midAnchor = c: hdrRow = G_MaxL(hdrRow, r)
If lowerAnchor = 0 And InStr(v, "LOWER") > 0 And InStr(v, "PART") > 0 Then lowerAnchor = c: hdrRow = G_MaxL(hdrRow, r)
NextCell1:
Next c
Next r
If upperAnchor = 0 And midAnchor = 0 And lowerAnchor = 0 Then
G_ResolveBlocks_UpperMidLower = False
Exit Function
End If
Dim tmp As Collection: Set tmp = New Collection
If upperAnchor > 0 Then G_AddBlockClosest ws, "UPPER PART", upperAnchor, hdrRow, tmp
If midAnchor > 0 Then G_AddBlockClosest ws, "MID PART", midAnchor, hdrRow, tmp
If lowerAnchor > 0 Then G_AddBlockClosest ws, "LOWER PART", lowerAnchor, hdrRow, tmp
If tmp.Count = 0 Then
G_ResolveBlocks_UpperMidLower = False
Exit Function
End If
ReDim blocks(0 To tmp.Count - 1)
Dim i As Long, vv As Variant
For i = 1 To tmp.Count
vv = tmp(i)
blocks(i - 1).partName = CStr(vv(1))
blocks(i - 1).OrgCol = CLng(vv(2))
blocks(i - 1).PCol = CLng(vv(3))
blocks(i - 1).SCol = CLng(vv(4))
Next i
G_ResolveBlocks_UpperMidLower = (cDesc > 0 And hdrRow > 0)
End Function
Private Sub G_AddBlockClosest(ByVal ws As Worksheet, ByVal partName As String, ByVal anchorCol As Long, ByVal hdrRow As Long, ByVal tmp As Collection)
Dim orgC As Long, pC As Long, sC As Long
orgC = G_FindClosestOrgThk(ws, anchorCol, hdrRow)
pC = G_FindClosestExact(ws, "P", anchorCol, hdrRow)
sC = G_FindClosestExact(ws, "S", anchorCol, hdrRow)
If orgC > 0 And pC > 0 And sC > 0 Then
Dim v(1 To 4) As Variant
v(1) = partName
v(2) = orgC
v(3) = pC
v(4) = sC
tmp.Add v
End If
End Sub
Private Function G_FindClosestExact(ByVal ws As Worksheet, ByVal token As String, ByVal anchorCol As Long, ByVal hdrRow As Long) As Long
Dim bestScore As Double: bestScore = 1E+99
Dim bestCol As Long: bestCol = 0
Dim r As Long, c As Long
For r = 1 To hdrRow + 3
For c = Application.Max(1, anchorCol - 40) To anchorCol + 40
If G_CellText(ws, r, c) = token Then
Dim score As Double
score = Abs(c - anchorCol) + Abs(r - hdrRow) * 5
If score < bestScore Then bestScore = score: bestCol = c
End If
Next c
Next r
G_FindClosestExact = bestCol
End Function
Private Function G_FindClosestOrgThk(ByVal ws As Worksheet, ByVal anchorCol As Long, ByVal hdrRow As Long) As Long
Dim bestScore As Double: bestScore = 1E+99
Dim bestCol As Long: bestCol = 0
Dim r As Long, c As Long
For r = 1 To hdrRow + 3
For c = Application.Max(1, anchorCol - 40) To anchorCol + 40
If G_IsOrgThkHeader(ws, r, c) Then
Dim score As Double
score = Abs(c - anchorCol) + Abs(r - hdrRow) * 5
If score < bestScore Then bestScore = score: bestCol = c
End If
Next c
Next r
G_FindClosestOrgThk = bestCol
End Function
Private Function G_IsOrgThkHeader(ByVal ws As Worksheet, ByVal r As Long, ByVal c As Long) As Boolean
Dim v As String: v = G_CellText(ws, r, c)
If InStr(v, "THK") = 0 Then Exit Function
If InStr(v, "ORG") > 0 Or InStr(v, "ORI") > 0 Or InStr(v, "ORIG") > 0 Then
G_IsOrgThkHeader = True
Exit Function
End If
If InStr(G_CellText(ws, r, c - 1), "ORG") > 0 Or InStr(G_CellText(ws, r, c - 1), "ORI") > 0 Or _
InStr(G_CellText(ws, r, c + 1), "ORG") > 0 Or InStr(G_CellText(ws, r, c + 1), "ORI") > 0 Or _
InStr(G_CellText(ws, r - 1, c), "ORG") > 0 Or InStr(G_CellText(ws, r - 1, c), "ORI") > 0 Or _
InStr(G_CellText(ws, r + 1, c), "ORG") > 0 Or InStr(G_CellText(ws, r + 1, c), "ORI") > 0 Then
G_IsOrgThkHeader = True
End If
End Function
'================= 文本处理 =================
Private Function G_HasSideTag(ByVal s As String, ByVal side As String) As Boolean
Dim t As String: t = UCase$(s)
G_HasSideTag = (InStr(t, "(" & side & ")") > 0 Or InStr(t, "(" & side & ")") > 0)
End Function
Private Function G_NormalizeText(ByVal s As String) As String
Dim t As String
t = UCase$(Trim$(s))
t = Replace(t, "(P)", "")
t = Replace(t, "(S)", "")
t = Replace(t, "(P)", "")
t = Replace(t, "(S)", "")
Do While InStr(t, " ") > 0
t = Replace(t, " ", " ")
Loop
G_NormalizeText = t
End Function
Private Function G_CellText(ByVal ws As Worksheet, ByVal r As Long, ByVal c As Long) As String
On Error GoTo SafeExit
If r < 1 Or c < 1 Then GoTo SafeExit
G_CellText = UCase$(Trim$(CStr(ws.Cells(r, c).Value2)))
Exit Function
SafeExit:
G_CellText = ""
End Function
Private Function G_MaxL(ByVal a As Long, ByVal b As Long) As Long
If a > b Then G_MaxL = a Else G_MaxL = b
End Function
'================= 选择文件夹 =================
Private Function G_PickFolder() As String
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "选择要递归处理的根文件夹"
.AllowMultiSelect = False
If .Show <> -1 Then
G_PickFolder = ""
Else
G_PickFolder = .SelectedItems(1)
End If
End With
End Function
你现在 RULES 怎么填(不需要通配符)
你这张 RULES 现在这样写就可以生效了(不用 *):
- FileLike:
FRAME(代码会自动当成*FRAME*) - PartLike:
LOWER PART或LOWER(代码会自动当成*LOWER PART*/*LOWER*) - ItemLike:
6(数字自动变成6*,避免误匹配 16) - DescContains:
FACE(包含匹配)
橙色不刷怎么用
你只要把 不需要刷的 P 或 S 单元格填充成你这张图的橙色(#FFC000),程序就会跳过那一格。
如果你跑完还发现“LOWER 没用到 0.1–0.3”,下一步我让你看一个最直观的验证方式:
在代码里临时把“命中的规则行号”写到 issues 或者写到单元格批注里(debug),可以一眼看到到底命中了哪条规则。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END











暂无评论内容