menu-icon.js 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /**
  2. * 工作台菜单图标解析:取菜单名称关键字 + 自动配色。
  3. *
  4. * 规则:
  5. * 1. 先按 KEYWORD_RULES 的关键字匹配(specific 在前、generic 在后,顺序不能乱);
  6. * 2. 没命中时去掉通用后缀(申请/工单/管理等)取首字,保证后续新增菜单无需改前端就能有图标;
  7. * 3. 颜色:命中规则用规则色;兜底按名称哈希从色板取色,同一个菜单名永远同一个颜色。
  8. */
  9. // 已知菜单的关键字与颜色(顺序敏感:如「离职交接单」要先于「离职」、「补卡」要先于「打卡」)
  10. const KEYWORD_RULES = [
  11. { keywords: ["工资", "薪资", "薪酬"], char: "薪", color: "#f59a23" },
  12. { keywords: ["档案"], char: "档", color: "#9270ca" },
  13. { keywords: ["交接"], char: "交", color: "#4b7902" },
  14. { keywords: ["离职"], char: "离", color: "#ee7a6e" },
  15. { keywords: ["调岗", "异动"], char: "岗", color: "#7262fd" },
  16. { keywords: ["转正"], char: "正", color: "#269a99" },
  17. { keywords: ["入职"], char: "入", color: "#07a44d" },
  18. { keywords: ["请假"], char: "假", color: "#6e7bf2" },
  19. { keywords: ["补卡"], char: "卡", color: "#4fa8ee" },
  20. { keywords: ["打卡"], char: "打", color: "#157a2c" },
  21. { keywords: ["排班"], char: "调", color: "#8f7bf0" },
  22. { keywords: ["加班"], char: "班", color: "#4fc08d" },
  23. { keywords: ["外勤", "外出"], char: "外", color: "#93a0b4" },
  24. { keywords: ["出差"], char: "差", color: "#f2b63c" },
  25. { keywords: ["考勤"], char: "勤", color: "#157a2c" },
  26. { keywords: ["质检"], char: "质", color: "#269a99" },
  27. { keywords: ["不合格"], char: "不", color: "#ee7a6e" },
  28. { keywords: ["申请"], char: "申", color: "#157a2c" },
  29. { keywords: ["审批", "待办"], char: "批", color: "#f59a23" },
  30. ];
  31. // 兜底取字时依次去掉的通用后缀(循环去,直到不再变化)
  32. const STRIP_SUFFIXES = ["申请", "办理", "管理", "记录", "审批", "工单", "清单", "列表", "单"];
  33. // 兜底配色板:新菜单哈希取色,视觉上与主题绿协调
  34. const PALETTE = [
  35. "#157a2c", "#2699a9", "#9270ca", "#61cdbb",
  36. "#f2b63c", "#6e7bf2", "#ee7a6e", "#4b7902",
  37. ];
  38. export function hexToRgba(hex, alpha) {
  39. const value = String(hex || "").replace("#", "");
  40. if (value.length !== 6) return hex;
  41. const r = parseInt(value.slice(0, 2), 16);
  42. const g = parseInt(value.slice(2, 4), 16);
  43. const b = parseInt(value.slice(4, 6), 16);
  44. return `rgba(${r}, ${g}, ${b}, ${alpha})`;
  45. }
  46. export function stripMenuSuffixes(name) {
  47. let result = String(name || "").trim();
  48. let changed = true;
  49. while (changed && result.length > 1) {
  50. changed = false;
  51. for (const suffix of STRIP_SUFFIXES) {
  52. if (result.length > suffix.length && result.endsWith(suffix)) {
  53. result = result.slice(0, -suffix.length);
  54. changed = true;
  55. }
  56. }
  57. }
  58. return result;
  59. }
  60. function hashName(name) {
  61. const text = String(name || "");
  62. let hash = 0;
  63. for (let i = 0; i < text.length; i += 1) {
  64. hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
  65. }
  66. return hash;
  67. }
  68. /**
  69. * @param {string} name 菜单名称(如「离职交接单」「保养工单」)
  70. * @returns {{ char: string, color: string, bg: string }}
  71. */
  72. export function resolveMenuIcon(name) {
  73. const text = String(name || "").trim();
  74. if (!text) return { char: "·", color: "#93a0b4", bg: hexToRgba("#93a0b4", 0.14) };
  75. for (const rule of KEYWORD_RULES) {
  76. if (rule.keywords.some((k) => text.indexOf(k) >= 0)) {
  77. return { char: rule.char, color: rule.color, bg: hexToRgba(rule.color, 0.14) };
  78. }
  79. }
  80. // 兜底:去通用后缀取首字(维修工单→维、保养工单→保、备品备件申请单→备)
  81. const stem = stripMenuSuffixes(text);
  82. const char = (stem || text).charAt(0);
  83. const color = PALETTE[hashName(text) % PALETTE.length];
  84. return { char, color, bg: hexToRgba(color, 0.14) };
  85. }