{"id":7244,"date":"2026-10-28T10:31:00","date_gmt":"2026-10-28T02:31:00","guid":{"rendered":"https:\/\/t.n-years.com\/?p=7244"},"modified":"2026-10-28T13:51:15","modified_gmt":"2026-10-28T05:51:15","slug":"%e4%b8%ad%e5%9b%bd%e5%8f%b0%e6%b9%be%e5%9c%b0%e5%8c%ba%e7%94%b5%e5%ad%90%e5%8f%91%e7%a5%a8-csv-%e5%af%bc%e5%85%a5%e5%8a%9f%e8%83%bd%e3%83%bb%e5%bc%80%e5%8f%91%e4%b8%8e%e6%8a%80%e6%9c%af%e6%8c%87","status":"publish","type":"post","link":"https:\/\/t.n-years.com\/?p=7244","title":{"rendered":"\u4e2d\u56fd\u53f0\u6e7e\u5730\u533a\u7535\u5b50\u53d1\u7968 CSV \u5bfc\u5165\u529f\u80fd\u30fb\u5f00\u53d1\u4e0e\u6280\u672f\u6307\u5357"},"content":{"rendered":"<p>\u5728\u5f00\u53d1\u9762\u5411\u4e2d\u56fd\u53f0\u6e7e\u5730\u533a\u7684\u8bb0\u8d26\u5e94\u7528\u65f6\uff0c\u652f\u6301\u672c\u5730\u4e3b\u6d41\u7684\u201c\u7535\u5b50\u53d1\u7968\u8f7d\u5177\u201d\u6570\u636e\u5bfc\u5165\u662f\u63d0\u5347\u7528\u6237\u4f53\u9a8c\u7684\u6838\u5fc3\u529f\u80fd\u3002\u7531\u4e8e\u5f53\u5730\u5b98\u65b9\u5bfc\u51fa\u7684 CSV \u5177\u6709\u56fa\u5b9a\u7684\u7ed3\u6784\uff0c\u672c\u6587\u5c06\u4ece\u5de5\u7a0b\u5b9e\u73b0\u89d2\u5ea6\uff0c\u68b3\u7406\u5f00\u53d1\u8fc7\u7a0b\u4e2d\u7684\u6838\u5fc3\u75db\u70b9\u3001\u9632\u5751\u6307\u5357\u53ca Swift \u89e3\u6790\u843d\u5730\u4ee3\u7801\u3002<\/p>\n<hr \/>\n<h2>\u4e00\u3001 \u6838\u5fc3\u6280\u672f\u96be\u70b9\u4e0e\u907f\u5751\u6307\u5357<\/h2>\n<p>\u5728\u7f16\u5199 iOS \u7aef CSV \u89e3\u6790\u5668\uff08\u6216\u4f7f\u7528\u7b2c\u4e09\u65b9\u5e93\u5982 <code>CHCSVParser<\/code> \/ <code>SwiftCSV<\/code>\uff09\u65f6\uff0c\u9700\u91cd\u70b9\u5904\u7406\u4ee5\u4e0b 4 \u4e2a\u672c\u5730\u5316\u6280\u672f\u7ec6\u8282\uff1a<\/p>\n<h3>1. \u5b57\u7b26\u7f16\u7801\u5bb9\u9519 (Big5 vs UTF-8)<\/h3>\n<ul>\n<li><strong>\u6280\u672f\u75db\u70b9<\/strong>\uff1a\u5f53\u5730\u5b98\u65b9\u7cfb\u7edf\u6216\u65e7\u7248\u7f51\u9875\u5bfc\u51fa\u7684 CSV \u5f80\u5f80\u91c7\u7528 <strong>Big5<\/strong> \u7f16\u7801\uff0c\u800c\u975e\u73b0\u4ee3\u6807\u51c6\u7684 UTF-8\u3002\u82e5\u76f4\u63a5\u7528 <code>String(contentsOf: encoding: .utf8)<\/code> \u8bfb\u53d6\uff0c\u4e2d\u6587\u5546\u6237\u540d\uff08\u5982\u201c\u7d71\u4e00\u8d85\u5546\u201d\uff09\u4f1a\u5168\u90e8\u53d8\u6210\u4e71\u7801\u3002<\/li>\n<li><strong>\u89e3\u51b3\u65b9\u6848<\/strong>\uff1a\u8bfb\u53d6\u6587\u4ef6\u65f6\u5fc5\u987b\u5b9e\u73b0<strong>\u964d\u7ea7\u91cd\u8bd5\u903b\u8f91<\/strong>\uff1a\u4f18\u5148\u5c1d\u8bd5 <code>.utf8<\/code>\uff0c\u82e5\u5931\u8d25\u6216\u89e3\u6790\u51fa\u4e71\u7801\uff0c\u5219\u964d\u7ea7\u4f7f\u7528 <code>String.Encoding.big5<\/code>\u3002\n<pre><code class=\"language-swift\">\n\/\/ Swift \u793a\u4f8b\uff1aBig5 \u7f16\u7801\u517c\u5bb9\u5199\u6cd5\nlet big5Encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue)))<\/code><\/pre>\n<\/li>\n<\/ul>\n<pre><code>\n### 2. \u65e5\u671f\u4e0e\u65f6\u95f4\u62c6\u5206\u5904\u7406\n\n* **\u6280\u672f\u75db\u70b9**\uff1a\u5b98\u65b9 CSV \u7684\u65f6\u95f4\u5b57\u6bb5\u88ab\u62c6\u6210\u4e86\u4e24\u5217\u2014\u2014<code>\u53d1\u7968\u65e5\u671f<\/code>\uff08\u683c\u5f0f\u5982 <code>2026\/10\/28<\/code>\uff09\u548c <code>\u53d1\u7968\u65f6\u95f4<\/code>\uff08\u683c\u5f0f\u5982 <code>09:30:15<\/code>\uff09\u3002\n* **\u89e3\u51b3\u65b9\u6848**\uff1a\u89e3\u6790\u65f6\u9700\u5c06\u4e24\u5217\u5b57\u7b26\u4e32\u62fc\u63a5\u4e3a <code>2026\/10\/28 09:30:15<\/code>\uff0c\u518d\u914d\u5408 <code>DateFormatter<\/code>\uff08\u6307\u5b9a <code>yyyy\/MM\/dd HH:mm:ss<\/code>\uff09\u8f6c\u6362\u4e3a iOS \u7684 <code>Date<\/code> \u5bf9\u8c61\u3002\n\n### 3. \u4e00\u7968\u591a\u5546\u54c1\uff08\u884c\u805a\u5408\uff09\u5904\u7406\n\n* **\u6280\u672f\u75db\u70b9**\uff1a\u7535\u5b50\u53d1\u7968 CSV \u91c7\u7528\u7684\u662f\u201c\u6309\u5546\u54c1\u660e\u7ec6\u9010\u884c\u5e73\u94fa\u201d\u7684\u7ed3\u6784\u3002\u82e5\u7528\u6237\u5728\u4e00\u7b14\u6d88\u8d39\u4e2d\u8d2d\u4e70\u4e86 3 \u4ef6\u5546\u54c1\uff0cCSV \u4e2d\u4f1a\u51fa\u73b0 3 \u884c\u8bb0\u5f55\uff0c\u4f46\u5b83\u4eec\u7684 <code>\u53d1\u7968\u53f7\u7801<\/code> \u548c <code>\u4ea4\u6613\u91d1\u989d<\/code> \u5b8c\u5168\u76f8\u540c\u3002\n* **\u89e3\u51b3\u65b9\u6848**\uff1a\n* **\u660e\u7ec6\u6d41**\uff1a\u6bcf\u4e00\u884c\u751f\u6210\u4e00\u6761\u8d26\u5355\uff0c\u5907\u6ce8\u8bb0\u5f55\u5177\u4f53\u7684 <code>\u54c1\u540d<\/code>\uff08\u9002\u5408\u7cbe\u7ec6\u5316\u8bb0\u8d26\uff09\u3002\n* **\u805a\u5408\u6d41\uff08\u63a8\u8350\uff09**\uff1a\u6839\u636e <code>\u53d1\u7968\u53f7\u7801<\/code> \u8fdb\u884c <code>Group By<\/code> \u805a\u5408\u3002\u91d1\u989d\u53d6\u8be5\u53d1\u7968\u7684\u603b\u91d1\u989d\uff0c\u5907\u6ce8\u5c06\u591a\u4e2a <code>\u54c1\u540d<\/code> \u7528\u7b26\u53f7\u8fde\u63a5\uff08\u5982 <code>\u7f8e\u5f0f\u5496\u5561\u5927\u676f\u3001\u9c9c\u4e73<\/code>\uff09\uff0c\u907f\u514d\u5355\u7b14\u6d88\u8d39\u88ab\u62c6\u6210\u591a\u7b14\u72ec\u7acb\u8d26\u5355\u3002\n\n### 4. \u667a\u80fd\u5206\u7c7b\u5173\u952e\u5b57\u5339\u914d (Auto-Categorization)\n\n* **\u6280\u672f\u75db\u70b9**\uff1a\u53d1\u7968\u4e2d\u7684 <code>\u5356\u5bb6\u540d\u79f0<\/code> \u901a\u5e38\u4e3a\u5b8c\u6574\u7684\u4f01\u4e1a\u5168\u79f0\uff08\u4f8b\u5982\uff1a<code>\u7d71\u4e00\u8d85\u5546\u80a1\u4efd\u6709\u9650\u516c\u53f8<\/code>\uff09\u3002\n* **\u89e3\u51b3\u65b9\u6848**\uff1a\u91c7\u7528\u5305\u542b\u5339\u914d\uff08Contains\uff09\u6216\u6b63\u5219\u8868\u8fbe\u5f0f\uff0c\u800c\u975e\u7cbe\u786e\u5339\u914d\uff1a\n* \u5305\u542b <code>\u7d71\u4e00\u8d85\u5546<\/code>\u3001<code>\u5168\u5bb6\u4fbf\u5229\u5546\u5e97<\/code> $\\rightarrow$ \u81ea\u52a8\u5f52\u7c7b\u4e3a **\u9910\u996e\/\u4fbf\u5229\u5e97**\n* \u5305\u542b <code>\u5168\u806f\u5be6\u696d<\/code>\u3001<code>\u5bb6\u798f<\/code> $\\rightarrow$ \u81ea\u52a8\u5f52\u7c7b\u4e3a **\u65e5\u5e38\/\u8d85\u5e02**\n* \u5305\u542b <code>\u53f0\u7063\u4e2d\u6cb9<\/code> $\\rightarrow$ \u81ea\u52a8\u5f52\u7c7b\u4e3a **\u4ea4\u901a\/\u52a0\u6cb9**\n\n---\n\n## \u4e8c\u3001 \u9a8c\u8bc1\u7528\u6807\u51c6\u793a\u4f8b CSV \u6587\u4ef6\n\n\u53ef\u5c06\u4e0b\u65b9\u5185\u5bb9\u4fdd\u5b58\u4e3a <code>invoice_sample.csv<\/code>\uff08\u63a8\u8350\u7f16\u7801\u4e3a **UTF-8 with BOM** \u6216 **Big5**\uff09\uff0c\u7528\u4e8e\u672c\u5730\u5355\u5143\u6d4b\u8bd5\u6216\u6a21\u62df\u5668\u6570\u636e\u89e3\u6790\u9a8c\u8bc1\uff1a\n\n```csv\n\u53d1\u7968\u53f7\u7801,\u53d1\u7968\u65e5\u671f,\u53d1\u7968\u65f6\u95f4,\u5356\u5bb6\u540d\u79f0,\u5356\u5bb6\u7edf\u4e00\u7f16\u53f7,\u4ea4\u6613\u91d1\u989d,\u8f7d\u5177\u7c7b\u522b,\u54c1\u540d,\u6570\u91cf,\u5355\u4ef7\nAB12345678,2026\/10\/28,09:30:15,\u7d71\u4e00\u8d85\u5546\u80a1\u4efd\u6709\u9650\u516c\u53f8,22555003,105,\u624b\u6a5f\u689d\u78bc,\u7f8e\u5f0f\u5496\u5561\u5927\u676f,1,55\nAB12345678,2026\/10\/28,09:30:15,\u7d71\u4e00\u8d85\u5546\u80a1\u4efd\u6709\u9650\u516c\u53f8,22555003,105,\u624b\u6a5f\u689d\u78bc,\u8336\u8449\u86cb,2,25\nCD87654321,2026\/10\/28,12:15:00,\u5168\u806f\u5be6\u696d\u80a1\u4efd\u6709\u9650\u516c\u53f8,04322559,328,\u624b\u6a5f\u689d\u78bc,\u9bae\u4e73946ml,1,115\nCD87654321,2026\/10\/28,12:15:00,\u5168\u806f\u5be6\u696d\u80a1\u4efd\u6709\u9650\u516c\u53f8,04322559,328,\u624b\u6a5f\u689d\u78bc,\u6709\u6a5f\u571f\u96de\u86cb10\u5165,1,113\nCD87654321,2026\/10\/28,12:15:00,\u5168\u806f\u5be6\u696d\u80a1\u4efd\u6709\u9650\u516c\u53f8,04322559,328,\u624b\u6a5f\u689d\u78bc,\u9999\u8549\u4e00\u675f,1,100\nEF99887766,2026\/10\/28,18:45:20,\u53f0\u7063\u4e2d\u6cb9\u80a1\u4efd\u6709\u9650\u516c\u53f8,03722901,500,\u624b\u6a5f\u689d\u78bc,95\u7121\u925b\u6c7d\u6cb9,25.2,19.84\n<\/code><\/pre>\n<hr \/>\n<h2>\u4e09\u3001 Swift \u89e3\u6790\u903b\u8f91\u6838\u5fc3\u4ee3\u7801\u53c2\u8003<\/h2>\n<p>\u4ee5\u4e0b\u4e3a\u5355\u884c CSV \u7ed3\u6784\u5316\u6620\u5c04\u7684 Swift \u5b9e\u73b0\u793a\u4f8b\uff1a<\/p>\n<pre><code class=\"language-swift\">import Foundation\n\nstruct InvoiceItemModel {\n    let invoiceNumber: String\n    let date: Date\n    let merchantName: String\n    let amount: Decimal\n    let itemName: String\n    let quantity: Decimal\n    let unitPrice: Decimal\n}\n\nclass InvoiceParserUtil {\n\n    \/\/ \u89e3\u6790\u5355\u884c CSV\uff08\u4f20\u5165\u5df2\u6309\u9017\u53f7\u5207\u5206\u51fa\u7684\u5b57\u6bb5\u6570\u7ec4\uff09\n    static func parseRow(_ columns: [String]) -&gt; InvoiceItemModel? {\n        guard columns.count &gt;= 10 else { return nil }\n\n        let invoiceNum = columns[0].trimmingCharacters(in: .whitespaces)\n        let dateStr = columns[1].trimmingCharacters(in: .whitespaces)\n        let timeStr = columns[2].trimmingCharacters(in: .whitespaces)\n        let merchant = columns[3].trimmingCharacters(in: .whitespaces)\n\n        guard let amount = Decimal(string: columns[5].trimmingCharacters(in: .whitespaces)),\n              let qty = Decimal(string: columns[8].trimmingCharacters(in: .whitespaces)),\n              let price = Decimal(string: columns[9].trimmingCharacters(in: .whitespaces)) else {\n            return nil\n        }\n\n        let itemName = columns[7].trimmingCharacters(in: .whitespaces)\n\n        \/\/ \u7ec4\u5408\u65e5\u671f\u65f6\u95f4\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = &quot;yyyy\/MM\/dd HH:mm:ss&quot;\n        dateFormatter.timeZone = TimeZone(identifier: &quot;Asia\/Taipei&quot;)\n        let fullDate = dateFormatter.date(from: &quot;\\(dateStr) \\(timeStr)&quot;) ?? Date()\n\n        return InvoiceItemModel(\n            invoiceNumber: invoiceNum,\n            date: fullDate,\n            merchantName: merchant,\n            amount: amount,\n            itemName: itemName,\n            quantity: qty,\n            unitPrice: price\n        )\n    }\n}\n<\/code><\/pre>\n<p>\u56db\u3001 \u8fdb\u9636\uff1a\u5e94\u7528\u5185 WebView \u62e6\u622a\u4e0e\u81ea\u52a8\u5316\u8def\u7ebf<br \/>\n\u5bf9\u4e8e\u5df2\u7ecf\u62e5\u6709\u4e00\u5b9a\u7528\u6237\u89c4\u6a21\uff08\u4f8b\u5982\u8fbe\u5230\u6570\u5343 DAU\uff09\u7684\u5e94\u7528\uff0c\u4e3a\u4e86\u5c06\u7528\u6237\u7684\u64cd\u4f5c\u6469\u64e6\u529b\u964d\u5230\u6700\u4f4e\uff0c\u53ef\u4ee5\u91c7\u7528\u5e94\u7528\u5185\u5185\u7f6e WKWebView \u767b\u5f55\u5e76\u62e6\u622a\u4e0b\u8f7d\u56de\u8c03\u7684\u6280\u672f\u65b9\u6848\uff1a<\/p>\n<p>\u5185\u5d4c WebView \u767b\u5f55\uff1a\u5728 App \u5185\u90e8\u52a0\u8f7d\u8d22\u653f\u90e8\u7535\u5b50\u53d1\u7968\u5e73\u53f0\uff0c\u5229\u7528 Cookie \u6301\u4e45\u5316\u4fdd\u5b58\u7528\u6237\u7684\u767b\u5f55\u72b6\u6001\u3002<\/p>\n<p>\u4e0b\u8f7d\u62e6\u622a\uff08WKDownload\uff09\uff1a\u901a\u8fc7 iOS \u7684 WKNavigationDelegate \u4e0e WKDownloadDelegate\uff0c\u5728\u4ee3\u7801\u5c42\u76f4\u63a5\u62e6\u622a CSV \u7684\u4e0b\u8f7d\u52a8\u4f5c\uff0c\u4e0d\u8ba9\u5176\u6d41\u8f6c\u5230\u5916\u90e8\u6d4f\u89c8\u5668\u3002<\/p>\n<p>\u9759\u9ed8\u5165\u5e93\uff1a\u83b7\u53d6\u4e0b\u8f7d\u5230\u6c99\u76d2\u7684 CSV \u6587\u4ef6\u6d41\u540e\uff0c\u540e\u53f0\u81ea\u52a8\u8c03\u7528\u4e0a\u8ff0 InvoiceParserUtil \u5b8c\u6210\u89e3\u6790\u3001\u53bb\u91cd\u4e0e\u5165\u5e93\uff0c\u5b9e\u73b0\u201c\u70b9\u4e00\u4e0b\u94fe\u63a5\uff0c\u540e\u53f0\u81ea\u52a8\u8bb0\u8d26\u201d\u7684\u6781\u4f73\u4f53\u9a8c\u3002<\/p>\n<p>\u957f\u671f\u5546\u4e1a\u6f14\u8fdb\uff1a\u524d\u671f\u5229\u7528\u5185\u5d4c WebView \u65b9\u6848\u6ee1\u8db3 MVP \u9700\u6c42\uff1b\u5f85\u4ea7\u54c1\u8fdb\u5165\u7a33\u5b9a\u76c8\u5229\u671f\u540e\uff0c\u53ef\u8fdb\u4e00\u6b65\u901a\u8fc7\u5f53\u5730\u5408\u4f19\u4eba\u6216\u5408\u89c4\u6e20\u9053\u7533\u8bf7\u5b98\u65b9 Open API\uff0c\u65e0\u7f1d\u5347\u7ea7\u4e3a\u9ad8\u9636\u7684 Pro \u4f1a\u5458\u201c\u4e00\u952e\u81ea\u52a8\u540c\u6b65\u201d\u62a4\u57ce\u6cb3\u529f\u80fd\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728\u5f00\u53d1\u9762\u5411\u4e2d\u56fd\u53f0\u6e7e\u5730\u533a\u7684\u8bb0\u8d26\u5e94\u7528\u65f6\uff0c\u652f\u6301\u672c\u5730\u4e3b\u6d41\u7684\u201c\u7535\u5b50\u53d1\u7968\u8f7d\u5177\u201d\u6570\u636e\u5bfc\u5165\u662f\u63d0\u5347\u7528\u6237\u4f53\u9a8c\u7684\u6838\u5fc3\u529f\u80fd\u3002\u7531\u4e8e\u5f53\u5730\u5b98&#8230; <\/p>\n<div class=\"read-more navbutton\"><a href=\"https:\/\/t.n-years.com\/?p=7244\">\u9605\u8bfb\u66f4\u591a<i class=\"fa fa-angle-double-right\"><\/i><\/a><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[135],"tags":[],"class_list":["post-7244","post","type-post","status-publish","format-standard","hentry","category-135"],"_links":{"self":[{"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/posts\/7244","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/t.n-years.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7244"}],"version-history":[{"count":2,"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/posts\/7244\/revisions"}],"predecessor-version":[{"id":7246,"href":"https:\/\/t.n-years.com\/index.php?rest_route=\/wp\/v2\/posts\/7244\/revisions\/7246"}],"wp:attachment":[{"href":"https:\/\/t.n-years.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/t.n-years.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/t.n-years.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}