ExPortToExcel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. using System;
  2. using System.Collections;
  3. using System.Windows.Forms;
  4. //using Excel = Microsoft.Office.Interop.Excel;
  5. using System;
  6. using System.Data;
  7. using System.IO;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Collections.Generic;
  11. using System.Threading.Tasks;
  12. namespace PTMedicalInsurance.Common
  13. {
  14. public class ExportToExcel
  15. {
  16. /// <summary>
  17. /// 将DataTable导出为CSV文件(完全替代原有的Excel导出)
  18. /// </summary>
  19. public static string DataTabletoCSV(DataTable tmpDataTable, string strFileName)
  20. {
  21. // 1. 前置校验:空值和路径合法性检查
  22. if (tmpDataTable == null || tmpDataTable.Rows.Count == 0)
  23. {
  24. MessageBox.Show("无导出数据!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
  25. return string.Empty;
  26. }
  27. // 校验并处理导出路径
  28. if (!ValidateAndFixExportPath(ref strFileName))
  29. {
  30. return string.Empty;
  31. }
  32. // 确保扩展名为.csv
  33. strFileName = EnsureCsvExtension(strFileName);
  34. FileStream fs = null;
  35. StreamWriter sw = null;
  36. StringBuilder sb = new StringBuilder(65536); // 64KB缓冲区
  37. try
  38. {
  39. int rowCount = tmpDataTable.Rows.Count;
  40. int columnCount = tmpDataTable.Columns.Count;
  41. const int batchSize = 5000; // 每批处理5000行
  42. // 2. 创建文件流
  43. fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write, FileShare.None);
  44. // 写入UTF-8 BOM,确保Excel打开不乱码
  45. fs.WriteByte(0xEF);
  46. fs.WriteByte(0xBB);
  47. fs.WriteByte(0xBF);
  48. sw = new StreamWriter(fs, Encoding.UTF8);
  49. // 3. 写入表头
  50. List<string> headers = new List<string>();
  51. for (int i = 0; i < columnCount; i++)
  52. {
  53. string headerName = GetColumnNameMapping(tmpDataTable.Columns[i].ColumnName);
  54. headers.Add(EscapeCsvValue(headerName));
  55. }
  56. sw.WriteLine(string.Join(",", headers));
  57. // 4. 分批写入数据
  58. for (int r = 0; r < rowCount; r++)
  59. {
  60. // 每批处理完成后写入文件
  61. if (r > 0 && r % batchSize == 0)
  62. {
  63. sw.Write(sb.ToString());
  64. sb.Clear();
  65. sw.Flush();
  66. Application.DoEvents(); // 防止UI卡死
  67. }
  68. // 构建当前行
  69. for (int i = 0; i < columnCount; i++)
  70. {
  71. object cellValue = tmpDataTable.Rows[r][i];
  72. // 格式化单元格值
  73. if (cellValue == null || cellValue == DBNull.Value)
  74. {
  75. sb.Append(string.Empty);
  76. }
  77. else
  78. {
  79. string value = cellValue.ToString().Trim();
  80. // 特殊处理:如果是数字类型但需要保留前导零(如身份证号、电话号码)
  81. if (IsNumeric(value))
  82. {
  83. // 检查是否是需要保留前导零的字段
  84. string columnName = tmpDataTable.Columns[i].ColumnName;
  85. if (IsIdNumberField(columnName))
  86. {
  87. // 身份证号、电话号码等字段,强制作为文本处理
  88. sb.Append(EscapeCsvValue(value));
  89. }
  90. else
  91. {
  92. sb.Append(value);
  93. }
  94. }
  95. else
  96. {
  97. sb.Append(EscapeCsvValue(value));
  98. }
  99. }
  100. if (i < columnCount - 1)
  101. {
  102. sb.Append(',');
  103. }
  104. }
  105. sb.AppendLine();
  106. }
  107. // 5. 写入剩余数据
  108. if (sb.Length > 0)
  109. {
  110. sw.Write(sb.ToString());
  111. }
  112. sw.Flush();
  113. // 6. 导出成功提示
  114. DialogResult result = MessageBox.Show(
  115. $"CSV导出成功!\n文件路径:{strFileName}\n总行数:{rowCount}\n\n是否立即打开文件?",
  116. "导出成功",
  117. MessageBoxButtons.YesNo,
  118. MessageBoxIcon.Information);
  119. if (result == DialogResult.Yes)
  120. {
  121. try
  122. {
  123. System.Diagnostics.Process.Start(strFileName);
  124. }
  125. catch (Exception ex)
  126. {
  127. MessageBox.Show($"打开文件失败:{ex.Message}", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  128. }
  129. }
  130. return strFileName;
  131. }
  132. catch (UnauthorizedAccessException ex)
  133. {
  134. MessageBox.Show($"导出失败:权限不足!请检查文件路径是否可写。\n详情:{ex.Message}",
  135. "权限异常", MessageBoxButtons.OK, MessageBoxIcon.Error);
  136. return string.Empty;
  137. }
  138. catch (IOException ex)
  139. {
  140. MessageBox.Show($"导出失败:文件读写错误。\n详情:{ex.Message}",
  141. "IO异常", MessageBoxButtons.OK, MessageBoxIcon.Error);
  142. return string.Empty;
  143. }
  144. catch (Exception ex)
  145. {
  146. MessageBox.Show($"导出异常:{ex.Message}\n堆栈信息:{ex.StackTrace}",
  147. "导出异常", MessageBoxButtons.OK, MessageBoxIcon.Error);
  148. return string.Empty;
  149. }
  150. finally
  151. {
  152. // 7. 确保资源释放
  153. if (sw != null)
  154. {
  155. try { sw.Close(); sw.Dispose(); } catch { }
  156. }
  157. if (fs != null)
  158. {
  159. try { fs.Close(); fs.Dispose(); } catch { }
  160. }
  161. sb.Clear();
  162. GC.Collect();
  163. }
  164. }
  165. /// <summary>
  166. /// 杀掉不死进程
  167. /// </summary>
  168. public void killProcessThread()
  169. {
  170. ArrayList myProcess = new ArrayList();
  171. for (int i = 0; i < myProcess.Count; i++)
  172. {
  173. try
  174. {
  175. System.Diagnostics.Process.GetProcessById(int.Parse((string)myProcess[i])).Kill();
  176. }
  177. catch { }
  178. }
  179. }
  180. #region 辅助方法
  181. /// <summary>
  182. /// 校验并修复导出路径
  183. /// </summary>
  184. private static bool ValidateAndFixExportPath(ref string filePath)
  185. {
  186. try
  187. {
  188. // 1. 检查路径是否为空
  189. if (string.IsNullOrWhiteSpace(filePath))
  190. {
  191. filePath = Path.Combine(
  192. Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
  193. $"导出数据_{DateTime.Now:yyyyMMddHHmmss}.csv");
  194. }
  195. // 2. 检查目录是否存在
  196. string directory = Path.GetDirectoryName(filePath);
  197. if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
  198. {
  199. Directory.CreateDirectory(directory);
  200. }
  201. // 3. 检查文件是否被占用
  202. if (File.Exists(filePath))
  203. {
  204. try
  205. {
  206. using (FileStream fs = File.Open(filePath, FileMode.Open, FileAccess.Write, FileShare.None))
  207. {
  208. // 文件可写,直接返回
  209. }
  210. }
  211. catch
  212. {
  213. DialogResult result = MessageBox.Show($"文件【{filePath}】已被占用,是否覆盖?",
  214. "文件占用", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
  215. if (result == DialogResult.No)
  216. {
  217. return false;
  218. }
  219. // 尝试删除
  220. try { File.Delete(filePath); } catch { }
  221. }
  222. }
  223. return true;
  224. }
  225. catch (Exception ex)
  226. {
  227. MessageBox.Show($"路径校验失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
  228. return false;
  229. }
  230. }
  231. /// <summary>
  232. /// 确保文件扩展名为.csv
  233. /// </summary>
  234. private static string EnsureCsvExtension(string filePath)
  235. {
  236. string extension = Path.GetExtension(filePath)?.ToLower();
  237. if (extension != ".csv")
  238. {
  239. return Path.ChangeExtension(filePath, ".csv");
  240. }
  241. return filePath;
  242. }
  243. /// <summary>
  244. /// 列名映射(保留原有的映射逻辑)
  245. /// </summary>
  246. private static string GetColumnNameMapping(string originalName)
  247. {
  248. var columnMapping = new Dictionary<string, string>
  249. {
  250. { "Adm_Dr", "HIS就诊号" },
  251. { "MdtrtID", "医保住院号" },
  252. { "PatientName", "姓名" },
  253. { "Age", "年龄" },
  254. { "Gend", "性别" },
  255. { "PsnCertNO", "身份证号" },
  256. { "EmpName", "单位或地址" },
  257. { "PsnType", "医保类型" },
  258. { "AdmInDepName", "病人部门名称" },
  259. { "AdmBed", "床号" },
  260. { "MainDiagName", "入院诊断" },
  261. { "AttendDoctorNO", "床位医生" },
  262. { "ChiefPhyDocName", "门诊医生" },
  263. { "OpertName", "入院登记人姓名" },
  264. { "RegDate", "入院登记时间" },
  265. { "insuplcAdmdvs", "参保地区划" },
  266. { "InHospNo", "HIS住院号" },
  267. { "Address", "现住址" }
  268. };
  269. return columnMapping.TryGetValue(originalName, out string newName) ? newName : originalName;
  270. }
  271. /// <summary>
  272. /// 判断是否为身份证号等需要保留前导零的字段
  273. /// </summary>
  274. private static bool IsIdNumberField(string columnName)
  275. {
  276. string lowerName = columnName.ToLower();
  277. return lowerName.Contains("id") ||
  278. lowerName.Contains("cert") ||
  279. lowerName.Contains("身份证") ||
  280. lowerName.Contains("电话") ||
  281. lowerName.Contains("phone") ||
  282. lowerName.Contains("code") ||
  283. lowerName.Contains("编码");
  284. }
  285. /// <summary>
  286. /// 判断是否为数字
  287. /// </summary>
  288. private static bool IsNumeric(string str)
  289. {
  290. return double.TryParse(str, out _);
  291. }
  292. /// <summary>
  293. /// 转义CSV值中的特殊字符
  294. /// </summary>
  295. private static string EscapeCsvValue(string value)
  296. {
  297. if (string.IsNullOrEmpty(value))
  298. {
  299. return string.Empty;
  300. }
  301. // 检查是否包含特殊字符
  302. bool containsSpecialChar = value.Contains(",") ||
  303. value.Contains("\"") ||
  304. value.Contains("\r") ||
  305. value.Contains("\n") ||
  306. value.Contains("\t");
  307. if (containsSpecialChar)
  308. {
  309. // 双引号转义
  310. value = value.Replace("\"", "\"\"");
  311. return $"\"{value}\"";
  312. }
  313. return value;
  314. }
  315. #endregion
  316. }
  317. }