From fa4d16c542981b471a4ae4d0417029d4aa55c4c2 Mon Sep 17 00:00:00 2001 From: dap <15891557205@163.com> Date: Fri, 23 Jan 2026 11:37:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(icons):=20=E6=B7=BB=E5=8A=A0=E7=A6=BB?= =?UTF-8?q?=E7=BA=BF=E5=9B=BE=E6=A0=87=E7=94=9F=E6=88=90=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增脚本 generate-offline-icons.js 用于自动生成离线图标集合 - 将手动维护的 menu-icons.ts 替换为脚本生成的 offline-icons.ts - 更新 @iconify/json 依赖版本至 2.2.431 - 在 icons 包中添加 @iconify/json 作为工作区依赖 - 在根 package.json 中添加生成离线图标的 npm 脚本 --- package.json | 3 +- packages/icons/package.json | 3 +- packages/icons/src/iconify-offline/index.ts | 2 +- .../icons/src/iconify-offline/menu-icons.ts | 123 --------- .../src/iconify-offline/offline-icons.ts | 258 ++++++++++++++++++ pnpm-workspace.yaml | 2 +- scripts/generate-offline-icons.js | 158 +++++++++++ 7 files changed, 422 insertions(+), 127 deletions(-) delete mode 100644 packages/icons/src/iconify-offline/menu-icons.ts create mode 100644 packages/icons/src/iconify-offline/offline-icons.ts create mode 100644 scripts/generate-offline-icons.js diff --git a/package.json b/package.json index b59d1c30..f152d640 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "test:e2e": "turbo run test:e2e", "update:deps": "npx taze -r -w", "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile", - "catalog": "pnpx codemod pnpm/catalog" + "catalog": "pnpx codemod pnpm/catalog", + "generate-offline-icons": "node scripts/generate-offline-icons.js" }, "devDependencies": { "@changesets/changelog-github": "catalog:", diff --git a/packages/icons/package.json b/packages/icons/package.json index 9857a720..57d07eb5 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -55,6 +55,7 @@ "@iconify/icons-tabler": "^1.2.95", "@iconify/icons-uiw": "^1.2.6", "@iconify/icons-vscode-icons": "^1.2.29", - "@iconify/icons-wpf": "^1.2.3" + "@iconify/icons-wpf": "^1.2.3", + "@iconify/json": "catalog:" } } diff --git a/packages/icons/src/iconify-offline/index.ts b/packages/icons/src/iconify-offline/index.ts index f36396d0..6f75d72d 100644 --- a/packages/icons/src/iconify-offline/index.ts +++ b/packages/icons/src/iconify-offline/index.ts @@ -3,7 +3,7 @@ import { createIconifyOfflineIcon } from '@vben-core/icons'; import dingdingFill from '@iconify/icons-ri/dingding-fill'; import giteeIcon from '@iconify/icons-simple-icons/gitee'; -import './menu-icons'; +import './offline-icons'; // 第三方登录相关图标 export const DingdingIcon = createIconifyOfflineIcon( diff --git a/packages/icons/src/iconify-offline/menu-icons.ts b/packages/icons/src/iconify-offline/menu-icons.ts deleted file mode 100644 index 85a0f285..00000000 --- a/packages/icons/src/iconify-offline/menu-icons.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { addIcon } from '@vben-core/icons'; - -import schedule from '@iconify/icons-akar-icons/schedule'; -import settingOutline from '@iconify/icons-ant-design/setting-outlined'; -import antdTool from '@iconify/icons-ant-design/tool-outlined'; -import UserAntd from '@iconify/icons-ant-design/user-outlined'; -import Operation from '@iconify/icons-arcticons/one-hand-operation'; -import BaseLineHousesFill from '@iconify/icons-bi/houses-fill'; -import BxPackage from '@iconify/icons-bx/package'; -import modelAlt from '@iconify/icons-carbon/model-alt'; -import taskApproved from '@iconify/icons-carbon/task-approved'; -import redisWordmark from '@iconify/icons-devicon/redis-wordmark'; -import springWordmark from '@iconify/icons-devicon/spring-wordmark'; -import vscode from '@iconify/icons-devicon/vscode'; -import evergreenTree from '@iconify/icons-emojione/evergreen-tree'; -import RoleBindingOutlined from '@iconify/icons-eos-icons/role-binding-outlined'; -import SystemGroup from '@iconify/icons-eos-icons/system-group'; -import NoticePush from '@iconify/icons-fe/notice-push'; -import leave from '@iconify/icons-flat-color-icons/leave'; -import plus from '@iconify/icons-flat-color-icons/plus'; -import builDefinition from '@iconify/icons-fluent-mdl2/build-definition'; -import Dictionary from '@iconify/icons-fluent-mdl2/dictionary'; -import flow from '@iconify/icons-fluent-mdl2/flow'; -import leaveUser from '@iconify/icons-fluent-mdl2/leave-user'; -import from24 from '@iconify/icons-fluent/form-24-regular'; -import BaseLineHouse from '@iconify/icons-ic/baseline-house'; -import monitor from '@iconify/icons-ic/baseline-monitor'; -import roundLaunch from '@iconify/icons-ic/round-launch'; -import MenuSharp from '@iconify/icons-ic/sharp-menu'; -import Appointment from '@iconify/icons-icon-park-outline/appointment'; -import SettingTwo from '@iconify/icons-icon-park-twotone/setting-two'; -import boolOpenText from '@iconify/icons-lucide/book-open-text'; -import copyright from '@iconify/icons-lucide/copyright'; -import table from '@iconify/icons-lucide/table'; -import cloudDoneOutlineRounded from '@iconify/icons-material-symbols/cloud-done-outline-rounded'; -import generatingTokensOutline from '@iconify/icons-material-symbols/generating-tokens-outline'; -import LogoDevOutline from '@iconify/icons-material-symbols/logo-dev-outline'; -import expressionIcon from '@iconify/icons-material-symbols/regular-expression-rounded'; -import ccOutline from '@iconify/icons-mdi/cc-outline'; -import tools from '@iconify/icons-mdi/tools'; -import workflowOutline from '@iconify/icons-mdi/workflow-outline'; -import DepartmentLine from '@iconify/icons-mingcute/department-line'; -import profileLine from '@iconify/icons-mingcute/profile-line'; -import UserDuotone from '@iconify/icons-ph/user-duotone'; -import userList from '@iconify/icons-ph/user-list'; -import users from '@iconify/icons-ph/users-light'; -import insatnceLine from '@iconify/icons-ri/instance-line'; -import todoLine from '@iconify/icons-ri/todo-line'; -import Authy from '@iconify/icons-simple-icons/authy'; -import FolderWithFilesOutline from '@iconify/icons-solar/folder-with-files-outline'; -import monitorBoldDuotone from '@iconify/icons-solar/monitor-bold-duotone'; -import monitorCameraOutlined from '@iconify/icons-solar/monitor-camera-outline'; -import monitorPhoneOutlined from '@iconify/icons-solar/monitor-smartphone-outline'; -import InterfaceLoginDialPadFingerPasswordDialPadDotFinger from '@iconify/icons-streamline/interface-login-dial-pad-finger-password-dial-pad-dot-finger'; -import categoryPlus from '@iconify/icons-tabler/category-plus'; -import code from '@iconify/icons-tabler/code'; - -/** - * 这里添加菜单图标 - */ -addIcon('eos-icons:system-group', SystemGroup); -addIcon('ph:user-duotone', UserDuotone); -addIcon('ant-design:user-outlined', UserAntd); -addIcon('eos-icons:role-binding-outlined', RoleBindingOutlined); -addIcon('ic:sharp-menu', MenuSharp); -addIcon('mingcute:department-line', DepartmentLine); -addIcon('icon-park-outline:appointment', Appointment); -addIcon('fluent-mdl2:dictionary', Dictionary); -addIcon('icon-park-twotone:setting-two', SettingTwo); -addIcon('fe:notice-push', NoticePush); -addIcon('material-symbols:logo-dev-outline', LogoDevOutline); -addIcon('arcticons:one-hand-operation', Operation); -addIcon( - 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger', - InterfaceLoginDialPadFingerPasswordDialPadDotFinger, -); -addIcon('solar:folder-with-files-outline', FolderWithFilesOutline); -addIcon('simple-icons:authy', Authy); -addIcon('solar:monitor-smartphone-outline', monitorPhoneOutlined); -addIcon('ic:baseline-house', BaseLineHouse); -addIcon('ph:users-light', users); -addIcon('bi:houses-fill', BaseLineHousesFill); -addIcon('ph:user-list', userList); -addIcon('bx:package', BxPackage); -addIcon('solar:monitor-bold-duotone', monitorBoldDuotone); -addIcon('solar:monitor-camera-outline', monitorCameraOutlined); -addIcon('material-symbols:generating-tokens-outline', generatingTokensOutline); -addIcon('devicon:redis-wordmark', redisWordmark); -addIcon('devicon:spring-wordmark', springWordmark); -addIcon('akar-icons:schedule', schedule); -addIcon('mdi:tools', tools); -addIcon('ant-design:tool-outlined', antdTool); -addIcon('tabler:code', code); -addIcon('flat-color-icons:plus', plus); -addIcon('devicon:vscode', vscode); -addIcon('lucide:table', table); -addIcon('emojione:evergreen-tree', evergreenTree); -addIcon('fluent-mdl2:leave-user', leaveUser); -addIcon('mdi:workflow-outline', workflowOutline); -addIcon('tabler:category-plus', categoryPlus); -addIcon('carbon:model-alt', modelAlt); -addIcon('fluent-mdl2:build-definition', builDefinition); -addIcon('fluent-mdl2:build-definition', builDefinition); -addIcon('icon-park-outline:monitor', monitor); -addIcon('ri:instance-line', insatnceLine); -addIcon('ri:todo-line', todoLine); -addIcon('fluent:form-24-regular', from24); -addIcon('carbon:task-approved', taskApproved); -addIcon('ic:round-launch', roundLaunch); -addIcon('material-symbols:cloud-done-outline-rounded', cloudDoneOutlineRounded); -addIcon('mdi:cc-outline', ccOutline); -addIcon('lucide:book-open-text', boolOpenText); -addIcon('lucide:copyright', copyright); -// 个人中心 -addIcon('mingcute:profile-line', profileLine); -// oss配置 -addIcon('ant-design:setting-outlined', settingOutline); -// 请假 -addIcon('flat-color-icons:leave', leave); -// flow -addIcon('fluent-mdl2:flow', flow); -// 流程表达式 -addIcon('material-symbols:regular-expression-rounded', expressionIcon); diff --git a/packages/icons/src/iconify-offline/offline-icons.ts b/packages/icons/src/iconify-offline/offline-icons.ts new file mode 100644 index 00000000..ae00a2dd --- /dev/null +++ b/packages/icons/src/iconify-offline/offline-icons.ts @@ -0,0 +1,258 @@ +// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改 +// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改 +// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改 +import { addIcon } from '@vben-core/icons'; + +addIcon('eos-icons:system-group', { + body: '', + width: 24, + height: 24, +}); +addIcon('ant-design:user-outlined', { + body: '', + width: 1024, + height: 1024, +}); +addIcon('eos-icons:role-binding-outlined', { + body: '', + width: 24, + height: 24, +}); +addIcon('ic:sharp-menu', { + body: '', + width: 24, + height: 24, +}); +addIcon('mingcute:department-line', { + body: '', + width: 24, + height: 24, +}); +addIcon('icon-park-outline:appointment', { + body: '', + width: 48, + height: 48, +}); +addIcon('fluent-mdl2:dictionary', { + body: '', + width: 2048, + height: 2048, +}); +addIcon('ant-design:setting-outlined', { + body: '', + width: 1024, + height: 1024, +}); +addIcon('fe:notice-push', { + body: '', + width: 24, + height: 24, +}); +addIcon('material-symbols:logo-dev-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('solar:folder-with-files-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('solar:monitor-smartphone-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('ph:users-light', { + body: '', + width: 256, + height: 256, +}); +addIcon('ph:user-list', { + body: '', + width: 256, + height: 256, +}); +addIcon('bx:package', { + body: '', + width: 24, + height: 24, +}); +addIcon('solar:monitor-camera-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('material-symbols:generating-tokens-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('devicon:redis-wordmark', { + body: '', + width: 128, + height: 128, +}); +addIcon('devicon:spring-wordmark', { + body: '', + width: 128, + height: 128, +}); +addIcon('ant-design:tool-outlined', { + body: '', + width: 1024, + height: 1024, +}); +addIcon('tabler:code', { + body: '', + width: 24, + height: 24, +}); +addIcon('flat-color-icons:plus', { + body: '', + width: 48, + height: 48, +}); +addIcon('devicon:vscode', { + body: '', + width: 128, + height: 128, +}); +addIcon('lucide:table', { + body: '', + width: 24, + height: 24, +}); +addIcon('emojione:evergreen-tree', { + body: '', + width: 64, + height: 64, +}); +addIcon('fluent-mdl2:leave-user', { + body: '', + width: 2048, + height: 2048, +}); +addIcon('mdi:workflow-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('tabler:category-plus', { + body: '', + width: 24, + height: 24, +}); +addIcon('material-symbols:regular-expression-rounded', { + body: '', + width: 24, + height: 24, +}); +addIcon('fluent-mdl2:build-definition', { + body: '', + width: 2048, + height: 2048, +}); +addIcon('icon-park-outline:monitor', { + body: '', + width: 48, + height: 48, +}); +addIcon('fluent-mdl2:flow', { + body: '', + width: 2048, + height: 2048, +}); +addIcon('flat-color-icons:leave', { + body: '', + width: 48, + height: 48, +}); +addIcon('carbon:task-approved', { + body: '', + width: 32, + height: 32, +}); +addIcon('ic:round-launch', { + body: '', + width: 24, + height: 24, +}); +addIcon('ri:todo-line', { + body: '', + width: 24, + height: 24, +}); +addIcon('material-symbols:cloud-done-outline-rounded', { + body: '', + width: 24, + height: 24, +}); +addIcon('mdi:cc-outline', { + body: '', + width: 24, + height: 24, +}); +addIcon('arcticons:one-hand-operation', { + body: '', + width: 48, + height: 48, +}); +addIcon( + 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger', + { + body: '', + width: 14, + height: 14, + }, +); +addIcon('ri:instance-line', { + body: '', + width: 24, + height: 24, +}); +addIcon('skill-icons:java-light', { + body: '', + width: 256, + height: 256, +}); +addIcon('tabler:file-type-xml', { + body: '', + width: 24, + height: 24, +}); +addIcon('carbon:sql', { + body: '', + width: 32, + height: 32, +}); +addIcon('skill-icons:typescript', { + body: '', + width: 256, + height: 256, +}); +addIcon('logos:vue', { + body: '', + width: 256, + height: 221, +}); +addIcon('flat-color-icons:folder', { + body: '', + width: 48, + height: 48, +}); +addIcon('ep:fold', { + body: '', + width: 1024, + height: 1024, +}); +addIcon('lucide:book-open-text', { + body: '', + width: 24, + height: 24, +}); +addIcon('lucide:copyright', { + body: '', + width: 24, + height: 24, +}); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5cd8b0f5..faa9608b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -24,7 +24,7 @@ catalog: '@ctrl/tinycolor': ^4.1.0 '@eslint/js': ^9.39.1 '@faker-js/faker': ^9.9.0 - '@iconify/json': ^2.2.406 + '@iconify/json': 2.2.431 '@iconify/tailwind': ^1.2.0 '@iconify/vue': ^5.0.0 '@intlify/core-base': ^11.1.7 diff --git a/scripts/generate-offline-icons.js b/scripts/generate-offline-icons.js new file mode 100644 index 00000000..b9e406ca --- /dev/null +++ b/scripts/generate-offline-icons.js @@ -0,0 +1,158 @@ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const offlineIconList = [ + 'eos-icons:system-group', + 'ant-design:user-outlined', + 'eos-icons:role-binding-outlined', + 'eos-icons:role-binding-outlined', + 'ic:sharp-menu', + 'mingcute:department-line', + 'icon-park-outline:appointment', + 'fluent-mdl2:dictionary', + 'ant-design:setting-outlined', + 'fe:notice-push', + 'material-symbols:logo-dev-outline', + 'solar:folder-with-files-outline', + 'ant-design:setting-outlined', + 'solar:monitor-smartphone-outline', + 'ph:users-light', + 'ph:user-list', + 'bx:package', + 'solar:monitor-camera-outline', + 'material-symbols:generating-tokens-outline', + 'devicon:redis-wordmark', + 'devicon:spring-wordmark', + 'ant-design:tool-outlined', + 'tabler:code', + 'tabler:code', + 'flat-color-icons:plus', + 'devicon:vscode', + 'lucide:table', + 'emojione:evergreen-tree', + 'fluent-mdl2:leave-user', + 'mdi:workflow-outline', + 'tabler:category-plus', + 'material-symbols:regular-expression-rounded', + 'fluent-mdl2:build-definition', + 'icon-park-outline:monitor', + 'fluent-mdl2:flow', + 'flat-color-icons:leave', + 'carbon:task-approved', + 'ic:round-launch', + 'ri:todo-line', + 'material-symbols:cloud-done-outline-rounded', + 'mdi:cc-outline', + 'arcticons:one-hand-operation', + 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger', + 'ri:instance-line', + 'skill-icons:java-light', + 'tabler:file-type-xml', + 'carbon:sql', + 'skill-icons:typescript', + 'logos:vue', + 'flat-color-icons:folder', + // 其他需要离线的 + 'ep:fold', + 'lucide:book-open-text', + 'lucide:copyright', +]; + +// Deduplicate list +const uniqueIcons = [...new Set(offlineIconList)]; + +const outputLines = [ + '// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改', + '// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改', + '// 该文件由脚本 generate-offline-icons.js 生成 ,不要手动修改', + "import { addIcon } from '@vben-core/icons';", + '', +]; + +const projectRoot = path.resolve(__dirname, '..'); +const nodeModules = path.join(projectRoot, 'packages/icons', 'node_modules'); + +// Helper to find icon data +function getIconData(prefix, name) { + const jsonPath = path.join( + nodeModules, + '@iconify/json/json', + `${prefix}.json`, + ); + if (!fs.existsSync(jsonPath)) { + console.warn(`Warning: Icon set ${prefix} not found at ${jsonPath}`); + return null; + } + + const content = fs.readFileSync(jsonPath, 'utf8'); + const data = JSON.parse(content); + + if (data.icons[name]) { + return { + ...data.icons[name], + width: data.icons[name].width || data.width || 24, + height: data.icons[name].height || data.height || 24, + }; + } + + if (data.aliases && data.aliases[name]) { + const alias = data.aliases[name]; + const parentName = alias.parent; + const parentData = getIconData(prefix, parentName); + if (parentData) { + return { + ...parentData, + ...alias, + // Remove alias specific fields if not needed, but they overwrite parent + }; + } + } + + console.warn(`Warning: Icon ${name} not found in ${prefix}`); + return null; +} + +uniqueIcons.forEach((iconStr) => { + const [prefix, ...nameParts] = iconStr.split(':'); + const name = nameParts.join(':'); // In case name has colons, though unlikely in Iconify + + if (!prefix || !name) { + console.warn(`Invalid icon format: ${iconStr}`); + return; + } + + const iconData = getIconData(prefix, name); + if (iconData) { + // Clean up data to be minimal + const cleanData = { + body: iconData.body, + width: iconData.width, + height: iconData.height, + left: iconData.left, + top: iconData.top, + hFlip: iconData.hFlip, + vFlip: iconData.vFlip, + rotate: iconData.rotate, + }; + // Remove undefined keys + Object.keys(cleanData).forEach( + (key) => cleanData[key] === undefined && delete cleanData[key], + ); + + outputLines.push(`addIcon('${iconStr}', ${JSON.stringify(cleanData)});`); + } +}); + +const outputPath = path.join( + projectRoot, + 'packages/icons/src/iconify-offline', + 'offline-icons.ts', +); +fs.writeFileSync(outputPath, `${outputLines.join('\n')}\n`); + +console.log(`Successfully generated ${outputPath}`);