
內容目錄
前言:你是不是也在做這種事?
每個月月底,辦公桌上都會疊著一大堆合約、通知書、或公文。
每一份都長得差不多,就差在「客戶名字」、「金額」、「日期」這幾個欄位不同。
你打開 Word,複製上個月的版本,手動改名字,改日期,存檔,再開下一份……
**一個下午就這樣沒了。**
這篇文章要教你一個方法:把 Excel 當資料庫,Word 當模板,用 VBA 寫一段代碼,**按一個按鈕,幾秒內自動生成幾十份公文**,還會自動標記哪些已完成、哪些需要跳過。
Excel 轉 Word 自動化範例懶人包
為了讓練完手的讀者更容易有機會上手實用,我提供了合約的懶人包給各位下載,把模版的內容改一改就可以用了。如果5秒沒有點擊下載需要在另外一個頁面再點擊直接開啟文件。


打開之後你會看到一個ZIP檔,點擊下載,解壓之後是兩個檔Data和Template,我有試過不用壓縮,分成兩個檔,也沒有比較方便,另外,因為它是巨集關係,我直接把VBA的code寫到裡面去了,Google對它的防範還是比較高一點。(因為有讀者反映看不到rar檔,圖片我就不再換,zip檔就是新的了。)


第一次使用點擊啟用編輯,

然後會有安全性風險,關掉data,到資料夾選取data右鍵按內容,勾選解除封鎖按套用。
為了方便演示才全部都按出來,其實一開始解壓縮之後就可以先解除封鎖,不過如果在別的地方下載巨集.xlsm,最後要先到開發人員的VISUAL BASIC看一看裡面的代碼,會不會挾帶別的惡意程式,它可以拿到很大的權限,真的要非常非常小心!所以,你很少會看到有網站把整個巨集丟出來下載。


最後,再點擊啟用內容就可以用了,其它使用上的問題可以看使用前注意事項,類似說明書之類的。

自動化範例的簡易說明書
解壓縮之後有兩個檔案,一個叫data的Excel檔,另一個叫Template的word檔,要放在相同路徑,第一次執行Excel後會在相同路徑產生一個資料夾叫GeneratedContracts,裡面會用來裝著我們用data產生的合約,有些電腦在第一次產生資料夾時會產生word檔,有些版本會產生錯誤,把data頁面F欄的completed清除,再次執行就可以了。
要怎樣執行呢?原本我已經制作了按鈕,但有些電腦下載時會把按鈕清除,
你可以在開發人員–>插入表單控制項按鈕(最左上面角落那個),然後點選要執行的巨集–>工作表1.GenerateWordContracts–>確定


VBA代碼的流程邏輯
整個流程分成四個階段:環境設定 → 啟動 Word → 迴圈處理 → 清理環境。
第一節:環境設定 ── 讀懂第一段代碼
vba
Sub GenerateWordContracts()
Dim wdApp As Object
Dim wdDoc As Object
Dim templatePath As String
Dim outputFolder As String
Dim dataSheet As Worksheet
Dim lastRow As Long
Dim i As Long
Dim rowData As Variant
Dim successCount As Long
Dim skipCount As Long
On Error GoTo ErrorHandler
templatePath = ThisWorkbook.Path & "\Template.docx"
outputFolder = ThisWorkbook.Path & "\GeneratedContracts\"
If Dir(templatePath) = "" Then
MsgBox "錯誤:找不到 Template.docx", vbCritical
Exit Sub
End If
If Dir(outputFolder, vbDirectory) = "" Then MkDir outputFolder
Set dataSheet = ThisWorkbook.Sheets("Data")
lastRow = dataSheet.Cells(dataSheet.Rows.Count, "A").End(xlUp).Row
If lastRow < 2 Then
MsgBox "Data 工作表沒有資料!", vbExclamation
Exit Sub
End If
Dim wdApp As Object / Dim wdDoc As Object
這兩個變數代表「Word 應用程式本身」和「單一 Word 文件」。宣告為 `Object` 是因為這段 VBA 是在 Excel 裡執行的,Excel 不認識 Word 的型別,所以用泛型 `Object` 來承接。這種技術叫做「Late Binding(晚期綁定)」,好處是不需要在 Excel 裡預先勾選 Word 的參考程式庫。
On Error GoTo ErrorHandler
這是整段程式的「安全網」。萬一在執行過程中發生錯誤(例如 Word 當機、磁碟空間不足),程式不會直接崩潰,而是跳到最底部的 `ErrorHandler` 區塊,乾淨地關閉 Word、顯示錯誤訊息。
templatePath = ThisWorkbook.Path & "\Template.docx"
ThisWorkbook.Path 會自動取得「這個 Excel 檔案所在的資料夾路徑」。這樣設計的好處是:不管你把整個資料夾搬到哪台電腦,路徑永遠是正確的,不需要寫死像 C:\Users\Charlie\Desktop\...這種會失效的絕對路徑。
If Dir(templatePath) = "" Then
`Dir()` 函式就像去摸一個路徑「有沒有東西」。如果回傳空字串 `""`,代表那個檔案不存在,程式就會跳出並提示錯誤,而不是繼續執行、最後出現一個讓人看不懂的錯誤。
**`If Dir(outputFolder, vbDirectory) = "" Then MkDir outputFolder`**
這行做了兩件事:先檢查輸出資料夾存不存在,如果不存在就自動建立。`vbDirectory` 是在告訴 `Dir()` 「我要找的是資料夾,不是檔案」。
**`lastRow = dataSheet.Cells(dataSheet.Rows.Count, "A").End(xlUp).Row`**
這是 VBA 界最著名的「找最後一行」技巧。它的邏輯是:先從 A 欄的最底部(第 1048576 行)往上找,找到第一個有資料的儲存格,那一行的行號就是 `lastRow`。比直接用 `dataSheet.UsedRange.Rows.Count` 更可靠,因為後者容易把空白的「曾用過」範圍也算進去。
## 第二節:啟動 Word,開始批次處理
Set wdApp = CreateObject("Word.Application")
wdApp.Visible = False
successCount = 0
skipCount = 0
For i = 2 To lastRow
If UCase(dataSheet.Cells(i, 6).Value) = "COMPLETED" Then
skipCount = skipCount + 1
GoTo NextRow
End If
rowData = dataSheet.Range("A" & i & ":E" & i).Value
Set wdDoc = wdApp.Documents.Add(templatePath)
Call ReplacePlaceholder(wdDoc, "{{ClientName}}", rowData(1, 1))
Call ReplacePlaceholder(wdDoc, "{{ContractDate}}", Format(rowData(1, 2), "yyyy年mm月dd日"))
Call ReplacePlaceholder(wdDoc, "{{Amount}}", Format(rowData(1, 3), "#,##0"))
Call ReplacePlaceholder(wdDoc, "{{CompanyName}}", rowData(1, 4))
Call ReplacePlaceholder(wdDoc, "{{Terms}}", rowData(1, 5))
Dim fileName As String
fileName = outputFolder & "合約_" & rowData(1, 1) & "_" & Format(Now, "hhmmss") & ".docx"
wdDoc.SaveAs2 fileName, 16
wdDoc.Close False
With dataSheet.Cells(i, 6)
.Value = "Completed"
.Font.Color = RGB(0, 128, 0)
.Font.Bold = True
End With
successCount = successCount + 1
NextRow:
Next i
```
逐行解析
**`Set wdApp = CreateObject("Word.Application")`**
這行代碼用 Windows 的 COM 技術,在背景靜默啟動一個 Word 應用程式實例。`CreateObject("Word.Application")` 是固定語法,引號裡的字串是 Word 在 Windows 登錄檔裡的「程式 ID(ProgID)」。
**`wdApp.Visible = False`**
讓 Word 在背景執行,使用者看不到視窗在跳動。如果你想看到 Word 的處理過程(例如debug時),可以改成 `True`。
**`If UCase(dataSheet.Cells(i, 6).Value) = "COMPLETED" Then`**
這是「智慧跳過」機制的關鍵。`UCase()` 把 F 欄的值轉成大寫再比較,所以不管之前存的是 `Completed`、`completed` 還是 `COMPLETED`,都能正確識別。搭配後面寫回 Excel 的 `"Completed"`,形成了一個可以多次執行的「冪等(idempotent)」操作 ── 重複跑程式不會重複生成已完成的文件。
**`rowData = dataSheet.Range("A" & i & ":E" & i).Value`**
一次把 A 到 E 欄的值讀入一個二維陣列 `rowData`。這樣比一格一格讀取快很多,尤其是資料列數多的時候。存取方式是 `rowData(1, 1)` 代表第一列第一欄(A欄),`rowData(1, 2)` 是第二欄(B欄),以此類推。
**`Set wdDoc = wdApp.Documents.Add(templatePath)`**
注意是 `.Add(templatePath)` 而不是 `.Open(templatePath)`。`.Open` 會直接打開並修改原始模板;`.Add` 則是「以這個模板為基礎,開一份新文件」,原始模板永遠不會被改動。
**`Format(rowData(1, 2), "yyyy年mm月dd日")`**
Excel 儲存日期的方式是一個序列數字(例如 46132 代表某個日期),`Format()` 函式把它轉成可讀的中文日期格式。同樣地,`Format(rowData(1, 3), "#,##0")` 把數字格式化成千分位(例如 85000 → 85,000)。
**`wdDoc.SaveAs2 fileName, 16`**
`SaveAs2` 是 Word 2010 之後的方法,比舊的 `SaveAs` 更穩定地支援 `.docx` 格式。第二個參數 `16` 是一個常數,代表 `wdFormatXMLDocument`,也就是現代的 `.docx` 格式。
**`With dataSheet.Cells(i, 6) ... End With`**
成功生成後,程式回頭在 Excel 的 F 欄標記 `"Completed"`,字體設為深綠色加粗。下次執行程式時,這一列就會被跳過。這個設計讓程式可以「中斷後繼續跑」,不怕跑到一半電腦當機。
---
第三節:代碼的核心 ── ReplacePlaceholder 替換函式詳解
Private Sub ReplacePlaceholder(wdDoc As Object, findText As String, replaceValue As Variant)
Dim txt As String
txt = IIf(IsNull(replaceValue) Or IsEmpty(replaceValue), "", CStr(replaceValue))
With wdDoc.Content.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = findText
.Replacement.Text = txt
.Forward = True
.Wrap = 1
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchByte = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=2
End With
End Sub
```
這個函式是整個系統的核心引擎。每次呼叫它,就是在 Word 文件裡執行一次「全部取代」操作,把像 `{{ClientName}}` 這樣的佔位符,換成真實的資料。
### 參數設計
函式接收三個參數:
- `wdDoc`:要操作的 Word 文件物件
- `findText`:要搜尋的佔位符,例如 `"{{ClientName}}"`
- `replaceValue`:要替換進去的實際值,型態是 `Variant` 以應對各種資料類型(文字、數字、日期)
### 防禦性的 Null 處理
```vba
txt = IIf(IsNull(replaceValue) Or IsEmpty(replaceValue), "", CStr(replaceValue))
```
`IIf()` 是 VBA 的三元運算子。這行說:「如果 `replaceValue` 是 Null 或空值,就用空字串替換;否則把它轉成字串」。這很重要,因為 Excel 儲存格可能是空的,如果直接用 `CStr()` 轉換一個空值,程式會報錯。
### Find 物件的各個屬性解析
**`.ClearFormatting` / `.Replacement.ClearFormatting`**
清除搜尋條件和替換條件上可能殘留的格式設定,確保每次搜尋都是乾淨的狀態。
**`.Forward = True`**
從文件開頭往結尾方向搜尋。
**`.Wrap = 1`(wdFindContinue)**
搜尋到文件結尾時,自動繞回開頭繼續找。這個設定確保不論游標在哪個位置,都能找到所有的佔位符。
**`.Format = False`**
告訴 Word「我只要找純文字,不要考慮格式」。如果設成 `True`,就必須連字型、大小都一樣才算符合。
**`.MatchCase = False`**
不區分大小寫。`{{ClientName}}` 和 `{{clientname}}` 都算符合。
**`.MatchByte = True`**
這個屬性在處理中文時非常重要。它區分全形和半形字元,避免把全形的 `{{` 誤判為半形的 `{{`。在公文系統裡,這個設定能避免一些很難debug的詭異替換問題。
**`.Execute Replace:=2`(wdReplaceAll)**
`Execute` 是真正觸發搜尋替換的方法。`Replace:=2` 代表「全部替換」,等同於 Word 裡按「全部取代」按鈕。如果改成 `Replace:=1`(wdReplaceOne),就只替換第一個找到的結果。
---
## 第四節:收尾 ── 清理環境與完成報告
```vba
wdApp.Quit
MsgBox "處理完成!" & vbCrLf & _
"---------------------------" & vbCrLf & _
" 新生成合約:" & successCount & " 份" & vbCrLf & _
" 已跳過(已完成):" & skipCount & " 份" & vbCrLf & _
" 位置:" & outputFolder, vbInformation
Exit Sub
ErrorHandler:
MsgBox "發生錯誤:" & Err.Description, vbCritical
If Not wdDoc Is Nothing Then wdDoc.Close False
If Not wdApp Is Nothing Then wdApp.Quit
End Sub
```
### 逐行解析
**`wdApp.Quit`**
關閉在背景執行的 Word。如果忘記這行,每次執行程式都會多開一個隱藏的 Word 實例在記憶體裡,跑幾次之後記憶體就撐不住了。
**`MsgBox ... vbCrLf & _`**
`vbCrLf` 是換行符號。`& _` 是 VBA 的連接字元,讓一行太長的代碼可以分多行書寫,增加可讀性。最終使用者會看到一個整齊的結果報告,清楚知道「生成了幾份、跳過了幾份、存在哪裡」。
**`Exit Sub`**
在正常執行完畢後,主動跳出程式,避免繼續執行到下面的 `ErrorHandler` 區塊。
**`ErrorHandler`**
```vba
If Not wdDoc Is Nothing Then wdDoc.Close False
If Not wdApp Is Nothing Then wdApp.Quit
```
發生錯誤時,先確認物件是否存在(`Not ... Is Nothing`),再關閉文件和 Word。這樣的雙重保險確保即使在錯誤狀態下,Word 也不會殘留在背景佔用資源。
---
第五節:如何擴充這套系統?
### 新增更多欄位
假設你要新增一個 `{{ManagerName}}` 標籤:
1. 在 Excel 的 Data 工作表 F 欄加入 `ManagerName` 的資料(注意原本 F 欄是狀態欄,可能要往後移)
2. 在 Word 模板 `Template.docx` 裡加入 `{{ManagerName}}`
3. 在代碼裡修改讀取範圍 `"A" & i & ":E" & i` → `"A" & i & ":F" & i`
4. 新增一行:`Call ReplacePlaceholder(wdDoc, "{{ManagerName}}", rowData(1, 6))`
### 用 PDF 格式輸出
把這行:
```vba
wdDoc.SaveAs2 fileName, 16
```
改成:
```vba
Dim pdfName As String
pdfName = outputFolder & "合約_" & rowData(1, 1) & "_" & Format(Now, "hhmmss") & ".pdf"
wdDoc.SaveAs2 pdfName, 17 ' 17 = wdFormatPDF
```
---
常見問題(FAQ )
**Q:執行時出現「找不到 Template.docx」?**
確認 `Template.docx` 和 `Data.xlsm` 放在同一個資料夾裡,且 Excel 檔案是已儲存的狀態(不能是「未儲存的新文件」,否則 `ThisWorkbook.Path` 會回傳空字串)。
**Q:生成的文件裡 `{{ClientName}}` 沒有被替換?**
最常見的原因是 Word 模板裡的 `{{ClientName}}` 被 Word 的「自動校正」功能拆開了(例如 `{{Client` 和 `Name}}` 變成兩個獨立的文字段落)。解決方式:在 Word 裡手動刪除佔位符,重新打一次,或是關閉 Word 的「自動校正」功能。
**Q:可以在 Mac 上執行嗎?**
不行。這套代碼使用了 Windows 專屬的 COM 自動化(`CreateObject`)。Mac 版的 Office 不支援 COM,需要改用 AppleScript 或其他方式實現。
—
結語
這套 VBA 自動化系統的設計有幾個值得學習的亮點:
– **防禦性程式設計**:每一個可能出錯的地方都有保護措施(檔案不存在、資料為空、執行錯誤)
– **冪等性**:重複執行不會重複產生結果,透過 F 欄狀態欄實現
– **模組化**:`ReplacePlaceholder` 獨立成一個函式,清晰且可重用
– **Late Binding**:不依賴特定版本的 Word 參考程式庫,相容性更好
把這個思維用在你自己的公文需求上,根據實際的 Word 模板和欄位調整,你也可以打造一套屬於自己部門的「公文自動化流水線」。
—
*如果這篇文章對你有幫助,歡迎分享給同樣在做重複公文工作的朋友!*
如果仍有無法執行的問題,歡迎與我聯絡。
如果你想補充更多vba對word的基礎操作,可幾參考《自學Excel VBA系列-如何用VBA控制WORD?》
或者你還需要學習vba對outlook的操作,可以參考《自學Excel VBA系列-如何用VBA控制OutLook?》這篇。
如果你學習VBA的終極目的是要減輕辦公室工作的壓力,請參考《Excel VBA 自動化教學完整指南:從入門到辦公室自動化應用》,又如果你想分享你的自動化需求,尋求意見,可以透過主頁的「與我聯絡」聯絡我,我很樂意為你解答。
「Charlie chacha,Excel VBA 愛好者、馬拉松跑者、
長線投資人。
🔧 目前在做:
📡 Yieldspot | 息率分位儀 <– 歡迎試用
— 幫存股族了解股息率歷史分位位置的分析工具」