index.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import { h, unref } from 'vue';
  2. import type { App, Component, Plugin } from 'vue';
  3. import { NIcon, NTag } from 'naive-ui';
  4. import { isObject } from './is/index';
  5. /**
  6. * render 图标
  7. * */
  8. export function renderIcon(icon: any) {
  9. return () => h(NIcon, null, { default: () => h(icon) });
  10. }
  11. /**
  12. * font 图标(Font class)
  13. * */
  14. export function renderFontClassIcon(icon: string, iconName = 'iconfont') {
  15. return () => h('span', { class: [iconName, icon] });
  16. }
  17. /**
  18. * font 图标(Unicode)
  19. * */
  20. export function renderUnicodeIcon(icon: string, iconName = 'iconfont') {
  21. return () => h('span', { class: [iconName], innerHTML: icon });
  22. }
  23. /**
  24. * font svg 图标
  25. * */
  26. export function renderfontsvg(icon: any) {
  27. return () =>
  28. h(NIcon, null, {
  29. default: () => h('svg', { class: `icon`, 'aria-hidden': 'true' }, h('use', { 'xlink:href': `#${icon}` }))
  30. });
  31. }
  32. /**
  33. * render new Tag
  34. * */
  35. const newTagColors = { color: '#f90', textColor: '#fff', borderColor: '#f90' };
  36. export function renderNew(type = 'warning', text = 'New', color: object = newTagColors) {
  37. return () =>
  38. h(
  39. NTag as any,
  40. {
  41. type,
  42. round: true,
  43. size: 'small',
  44. color
  45. },
  46. { default: () => text }
  47. );
  48. }
  49. export const withInstall = <T extends Component>(component: T, alias?: string) => {
  50. const comp = component as any;
  51. comp.install = (app: App) => {
  52. app.component(comp.name || comp.displayName, component);
  53. if (alias) {
  54. app.config.globalProperties[alias] = component;
  55. }
  56. };
  57. return component as T & Plugin;
  58. };
  59. /**
  60. * 找到所有节点
  61. * */
  62. const treeAll: any[] = [];
  63. export function getTreeAll(data: any[]): any[] {
  64. data.forEach(item => {
  65. treeAll.push(item.key);
  66. if (item.children && item.children.length) {
  67. getTreeAll(item.children);
  68. }
  69. });
  70. return treeAll;
  71. }
  72. // dynamic use hook props
  73. export function getDynamicProps<T extends Record<string, any>, U>(props: T): Partial<U> {
  74. const ret: Recordable = {};
  75. Object.keys(props).forEach((key: any) => {
  76. ret[key] = unref((props as Recordable)[key]);
  77. });
  78. return ret as Partial<U>;
  79. }
  80. export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
  81. let key: string;
  82. for (key in target) {
  83. if (Object.hasOwn(target, key)) {
  84. src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]);
  85. }
  86. }
  87. return src;
  88. }
  89. /**
  90. * Sums the passed percentage to the R, G or B of a HEX color
  91. * @param {string} color The color to change
  92. * @param {number} amount The amount to change the color by
  93. * @returns {string} The processed part of the color
  94. */
  95. function addLight(color: string, amount: number) {
  96. const cc = Number.parseInt(color, 16) + amount;
  97. const c = cc > 255 ? 255 : cc;
  98. return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
  99. }
  100. /**
  101. * Lightens a 6 char HEX color according to the passed percentage
  102. * @param {string} color The color to change
  103. * @param {number} amount The amount to change the color by
  104. * @returns {string} The processed color represented as HEX
  105. */
  106. export function lighten(color: string, amount: number) {
  107. const hexColor = color.includes('#') ? color.substring(1, color.length) : color;
  108. const adjustedAmount = Math.trunc((255 * amount) / 100);
  109. return `#${addLight(hexColor.substring(0, 2), adjustedAmount)}${addLight(
  110. hexColor.substring(2, 4),
  111. adjustedAmount
  112. )}${addLight(hexColor.substring(4, 6), adjustedAmount)}`;
  113. }
  114. /**
  115. * 判断是否 url
  116. * */
  117. export function isUrl(url: string) {
  118. return /^(http|https):\/\//g.test(url);
  119. }
  120. /**
  121. * 获取uuid
  122. */
  123. export function getUUID() {
  124. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
  125. const r = Math.random() * 16;
  126. const v = c === 'x' ? Math.floor(r) : Math.floor((r * 4) % 4) + 8;
  127. return v.toString(16);
  128. });
  129. }
  130. interface OriginalMenuItem {
  131. menuId: number;
  132. parentId: number;
  133. parentName: string | null;
  134. name: string;
  135. url: string;
  136. perms: string;
  137. type: number;
  138. icon: string | null;
  139. orderNum: number;
  140. list: OriginalMenuItem[] | null;
  141. component: string;
  142. hideInMenu: boolean;
  143. }
  144. interface OriginalMenuData {
  145. menuList: OriginalMenuItem[];
  146. }
  147. interface ConvertedMenuItem {
  148. name: string;
  149. path: string;
  150. component: string;
  151. meta: {
  152. title: string;
  153. // i18nKey: string;
  154. icon?: string;
  155. order?: number;
  156. hideInMenu?: boolean;
  157. };
  158. children?: ConvertedMenuItem[];
  159. }
  160. export function convertMenuData(originalData: OriginalMenuData): ConvertedMenuItem[] {
  161. // 首先过滤出顶级菜单 (parentId === 0)
  162. const topLevelMenus = originalData.menuList.filter(menu => menu.parentId === 0);
  163. return topLevelMenus.map(menu => {
  164. return convertMenuItem(menu, originalData.menuList);
  165. });
  166. }
  167. function convertMenuItem(menu: OriginalMenuItem, allMenus: OriginalMenuItem[]): ConvertedMenuItem {
  168. // 生成路由名称 (使用小写字母和下划线)
  169. const routeName = menu.url
  170. .replace(/^\/+/, '') // 去掉开头的一个或多个 '/'
  171. .replace(/[/\s]/g, '_') // 替换剩余的 '/' 和空格为 '_'
  172. .toLowerCase();
  173. // 生成路由路径
  174. let routePath = `${routeName}`;
  175. if (menu.url && menu.url.trim() !== '') {
  176. routePath = `${menu.url}`;
  177. }
  178. // 转换基本信息
  179. const converted: ConvertedMenuItem = {
  180. name: routeName,
  181. path: routePath,
  182. component: menu.component,
  183. meta: {
  184. title: menu.name,
  185. // i18nKey: `route.${routeName}`,
  186. order: menu.orderNum,
  187. hideInMenu: menu.hideInMenu,
  188. icon: String(menu.icon)
  189. }
  190. };
  191. // 处理子菜单
  192. if (menu.list && menu.list.length > 0) {
  193. converted.children = menu.list.map(child => convertMenuItem(child, allMenus));
  194. } else {
  195. // 如果没有直接子菜单,但可能有其他关联的子菜单
  196. const children = allMenus.filter(m => m.parentId === menu.menuId);
  197. if (children.length > 0) {
  198. converted.children = children.map(child => convertMenuItem(child, allMenus));
  199. }
  200. }
  201. return converted;
  202. }
  203. /**
  204. * 菜单转为树型结构
  205. * @param data
  206. * @returns
  207. */
  208. export function buildMenuTree(data: any) {
  209. // 创建根节点数组和映射表
  210. const rootNodes: any = [];
  211. const nodeMap: any = {};
  212. // 首先将所有节点存入映射表
  213. data.forEach((item: any) => {
  214. nodeMap[item.menuId] = { ...item };
  215. });
  216. // 构建树形结构
  217. data.forEach((item: any) => {
  218. const node = nodeMap[item.menuId];
  219. if (item.parentId === 0) {
  220. // 根节点
  221. rootNodes.push(node);
  222. } else {
  223. // 子节点,找到父节点并添加到父节点的children中
  224. const parent = nodeMap[item.parentId];
  225. if (parent) {
  226. if (!parent.children) {
  227. parent.children = [];
  228. }
  229. parent.children.push(node);
  230. } else {
  231. // 如果父节点不存在,也作为根节点
  232. rootNodes.push(node);
  233. }
  234. }
  235. });
  236. // 删除空children字段
  237. const removeEmptyChildren = (nodes: any) => {
  238. nodes.forEach((node: any) => {
  239. if (node.children && node.children.length === 0) {
  240. delete node.children;
  241. } else if (node.children) {
  242. removeEmptyChildren(node.children);
  243. }
  244. });
  245. return nodes;
  246. };
  247. // 对根节点和子节点按orderNum排序
  248. const sortByOrderNum = (nodes: any) => {
  249. return nodes
  250. .sort((a: any, b: any) => a.orderNum - b.orderNum)
  251. .map((node: any) => {
  252. if (node.children) {
  253. node.children = sortByOrderNum(node.children);
  254. }
  255. return node;
  256. });
  257. };
  258. return removeEmptyChildren(sortByOrderNum(rootNodes));
  259. }