using System; using System.Collections.Generic; namespace 成套报价软件 { /// /// 价格显示格式化的统一规则: /// 1) 小数位选择:控制价格单元格显示的小数位数(0~6 位) /// 2) 取整方式:额外的取整规则(不取整/四舍五入到角/元/最近5元/最近10元等) /// 配置存在每个项目的「项目信息」表里,四个字段: /// 单台小数位 / 单台取整方式 / 项目小数位 / 项目取整方式 /// 本类只负责“显示前格式化”,不改原始数值,不写库(精度不丢失)。 /// public static class 价格格式化 { // ============================================================ // 下拉框预置项(供新建项目/修改项目窗口使用) // ============================================================ /// 小数位下拉框的预置选项文本(同时也是存库的值)。 public static readonly string[] 小数位选项 = { "保留整数", "保留1位小数", "保留2位小数", "保留3位小数", "保留4位小数", "保留5位小数", "保留6位小数" }; /// 取整方式下拉框的预置选项文本(同时也是存库的值)。 public static readonly string[] 取整方式选项 = { "不取整", "四舍五入", // 精确到 1(通用别名,与“四舍五入到元”等价) "四舍五入到角", // 精确到 0.1 "四舍五入到元", // 精确到 1 "四舍五入到十元", // 精确到 10 "向上取整到元", // Math.Ceiling 到 1 "最近5元", // 精确到 5 "最近10元" // 精确到 10 }; // ============================================================ // 解析:把下拉框文本 → 小数位数 / 取整步长 // ============================================================ /// 把“保留N位小数”选项文本解析成小数位数;解析失败返回 -1(表示用默认)。 public static int 解析小数位(string 选项文本) { if (string.IsNullOrWhiteSpace(选项文本)) return -1; string s = 选项文本.Trim(); // "保留整数" → 0;"保留N位小数" → N if (s.Contains("整数")) return 0; // 截取“保留”和“位”之间的数字 int i1 = s.IndexOf('保'); int i2 = s.IndexOf('位'); if (i1 >= 0 && i2 > i1 + 1) { string num = s.Substring(i1 + 1, i2 - i1 - 1).Replace("留", "").Trim(); int n; if (int.TryParse(num, out n) && n >= 0 && n <= 10) return n; } return -1; } /// /// 把取整方式文本解析成对应的步长(>0 表示按此步长取整); /// 返回 null 表示“不取整”或无法识别。 /// 例:“四舍五入到角” → 0.1m;“最近5元” → 5m;“向上取整到元” → 1m(另带向上标记)。 /// /// out 参数:为 true 时表示向上取整(Math.Ceiling),否则四舍五入。 public static decimal? 解析取整步长(string 选项文本, out bool 向上) { 向上 = false; if (string.IsNullOrWhiteSpace(选项文本)) return null; string s = 选项文本.Trim(); if (s == "不取整" || s.Contains("不取")) return null; // “四舍五入”(纯文本) = 精确到元(步长 1) if (s == "四舍五入") return 1m; if (s.Contains("到角")) return 0.1m; if (s.Contains("最近5")) return 5m; if (s.Contains("最近10") || s.Contains("到十元")) return 10m; if (s.Contains("向上") && s.Contains("到元")) { 向上 = true; return 1m; } if (s.Contains("到元") || s.Contains("四舍五入到元")) return 1m; if (s.Contains("到十")) return 10m; return null; } // ============================================================ // 核心:格式化一个数值(只改显示,不改原始精度) // ============================================================ /// /// 把原始 decimal 按“小数位 + 取整方式”格式化为显示字符串。 /// 规则:先按取整方式对原值取整(用于显示,不改原值),再按小数位格式化。 /// 值为 0 时返回空串(与原 SetCell 的“0 显示空”规则保持一致)。 /// /// 原始数值 /// -1 表示未配置(默认 2 位);0~6 表示保留位数 /// 取整方式文本(可为空/null) public static string 格式化(decimal value, int 小数位, string 取整方式) { if (value == 0m) return ""; // 1) 先按取整方式得到“用于显示的值”(不改 value 本身) decimal 显示值 = value; bool 向上; decimal? 步长 = 解析取整步长(取整方式, out 向上); if (步长.HasValue && 步长.Value > 0m) { if (向上) { 显示值 = Math.Ceiling(显示值 / 步长.Value) * 步长.Value; } else { 显示值 = Math.Round(显示值 / 步长.Value, MidpointRounding.AwayFromZero) * 步长.Value; } } // 2) 小数位:-1 用默认 2 位;否则按配置 int 位数 = (小数位 < 0 || 小数位 > 10) ? 2 : 小数位; return 显示值.ToString("F" + 位数.ToString()); } /// /// 便捷重载:直接用 4 个配置文本格式化(避免调用方手动解析)。 /// 单台层和项目层共用同一套逻辑,只是传入的配置不同。 /// public static string 格式化(decimal value, string 小数位文本, string 取整方式文本) { int n = 解析小数位(小数位文本); return 格式化(value, n, 取整方式文本); } } }