2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 01:57:19 +08:00

feat: 实现图标本地加载

目前会导致打包体积失控,但是没有找到更好的方案。
This commit is contained in:
2025-12-18 04:33:15 +08:00
parent 93c1ecc6a3
commit 40cc33e61e
34 changed files with 107 additions and 77 deletions

View File

@@ -43,19 +43,24 @@ export default [
eslintrc: {
enabled: true
},
parser: 'acorn',
vueTemplate: true,
addons: {
vueDirectives: true
},
viteOptimizeDeps: true
}),
Icons({
compiler: 'vue3',
scale: 1,
defaultClass: 'inline-block'
}),
Components({
resolvers: [
NaiveUiResolver(),
IconsResolver({ customCollections: ['custom'], prefix: 'icon' })
IconsResolver()
],
dts: 'types/components.d.ts'
})
}),
Icons({
compiler: 'vue3',
scale: 1,
defaultClass: 'inline-block',
autoInstall: true
}),
]

View File

@@ -58,7 +58,8 @@
"vue3-gettext": "4.0.0-beta.1"
},
"devDependencies": {
"@iconify/json": "^2.2.397",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/simple-icons": "^1.2.63",
"@iconify/vue": "^5.0.0",
"@rushstack/eslint-patch": "^1.14.0",
"@tsconfig/node24": "^24.0.0",

23
web/pnpm-lock.yaml generated
View File

@@ -102,9 +102,12 @@ importers:
specifier: 4.0.0-beta.1
version: 4.0.0-beta.1(@vue/compiler-sfc@3.5.25)(vue@3.5.25(typescript@5.9.3))
devDependencies:
'@iconify/json':
specifier: ^2.2.397
version: 2.2.419
'@iconify-json/mdi':
specifier: ^1.2.3
version: 1.2.3
'@iconify-json/simple-icons':
specifier: ^1.2.63
version: 1.2.63
'@iconify/vue':
specifier: ^5.0.0
version: 5.0.0(vue@3.5.25(typescript@5.9.3))
@@ -693,8 +696,11 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@iconify/json@2.2.419':
resolution: {integrity: sha512-50GipvrxOs/b03MP0GxCTn65o4S/DDEXbf03oXbglp0m552TcT8yAidLl7kyQ1fiI+rx3LbvZ6hNSU3CrXZnIQ==}
'@iconify-json/mdi@1.2.3':
resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==}
'@iconify-json/simple-icons@1.2.63':
resolution: {integrity: sha512-xZl2UWCwE58VlqZ+pDPmaUhE2tq8MVSTJRr4/9nzzHlDdjJ0Ud1VxNXPrwTSgESKY29iCQw3S0r2nJTSNNngHw==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -4077,10 +4083,13 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@iconify/json@2.2.419':
'@iconify-json/mdi@1.2.3':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/simple-icons@1.2.63':
dependencies:
'@iconify/types': 2.0.0
pathe: 2.0.3
'@iconify/types@2.0.0': {}

View File

@@ -45,9 +45,9 @@ const columns: DataTableColumns<RowData> = [
defaultSortOrder: false,
sorter: 'default',
render(row) {
let icon = 'bi:file-earmark'
let icon = 'mdi:file-outline'
if (row.dir) {
icon = 'bi:folder'
icon = 'mdi:folder-outline'
} else {
icon = getIconByExt(getExt(row.name))
}
@@ -261,7 +261,7 @@ const handleClose = () => {
<n-button type="primary"> {{ $gettext('Create') }} </n-button>
</n-popselect>
<n-button @click="handleUp">
<icon-bi-arrow-up />
<i-mdi-arrow-up :size="16" />
</n-button>
<n-input-group flex-1>
<n-tag size="large" v-if="!isInput" flex-1 @click="handleInput">
@@ -288,7 +288,7 @@ const handleClose = () => {
/>
</n-input-group>
<n-button @click="refresh">
<icon-bi-arrow-clockwise />
<i-mdi-refresh :size="16" />
</n-button>
</n-flex>
<n-data-table

View File

@@ -9,8 +9,8 @@ const { isFullscreen, toggle } = useFullscreen()
<n-tooltip trigger="hover">
<template #trigger>
<n-icon mr-20 cursor-pointer size="20" @click="toggle">
<icon-ant-design:fullscreen-exit-outlined v-if="isFullscreen" />
<icon-ant-design:fullscreen-outlined v-else />
<i-mdi-fullscreen-exit v-if="isFullscreen" />
<i-mdi-fullscreen v-else />
</n-icon>
</template>
{{ $gettext('Fullscreen Display') }}

View File

@@ -10,8 +10,8 @@ const themeStore = useThemeStore()
<n-tooltip trigger="hover">
<template #trigger>
<n-icon cursor-pointer size="22" @click="themeStore.toggleCollapsed()">
<icon-mdi:format-indent-increase v-if="themeStore.sider.collapsed" />
<icon-mdi:format-indent-decrease v-else />
<i-mdi-format-indent-increase v-if="themeStore.sider.collapsed" />
<i-mdi-format-indent-decrease v-else />
</n-icon>
</template>
{{ $gettext('Menu Zoom') }}

View File

@@ -14,7 +14,7 @@ const handleReloadPage = () => {
<n-tooltip trigger="hover">
<template #trigger>
<n-icon mr-20 cursor-pointer size="20" @click="handleReloadPage">
<icon-mdi-refresh />
<i-mdi-refresh />
</n-icon>
</template>
{{ $gettext('Refresh Tab') }}

View File

@@ -10,8 +10,8 @@ const theme = useThemeStore()
<n-tooltip trigger="hover">
<template #trigger>
<n-icon mr-20 cursor-pointer size="20" @click="theme.toggleDarkMode">
<icon-mdi-moon-waning-crescent v-if="theme.darkMode" />
<icon-mdi-white-balance-sunny v-else />
<i-mdi-weather-sunny v-if="theme.darkMode" />
<i-mdi-weather-night v-else />
</n-icon>
</template>
{{ $gettext('Switch Theme') }}

View File

@@ -12,11 +12,11 @@ const toHome = () => {
</script>
<template>
<div class="h-60 f-c-c cursor-pointer" @click="toHome">
<n-image :src="logo" height="32" preview-disabled />
<div class="f-c-c h-60 cursor-pointer" @click="toHome">
<n-image :src="logo" preview-disabled class="h-36" />
<h2
v-show="!themeStore.sider.collapsed"
class="ml-10 max-w-140 flex-shrink-0 text-18 font-bold"
class="text-18 font-bold ml-10 flex-shrink-0 max-w-140"
>
{{ themeStore.name }}
</h2>

View File

@@ -52,14 +52,14 @@ const menus = computed<TreeSelectOption[]>(() => {
</script>
<template>
<div h-40 flex justify-between px-20>
<div px-20 flex h-40 justify-between>
<menu-collapse />
<n-tooltip trigger="hover">
<template #trigger>
<the-icon
v-show="!themeStore.sider.collapsed"
:size="22"
icon="material-symbols:settings"
icon="mdi:settings-outline"
@click="settingModal = true"
/>
</template>

View File

@@ -1,9 +1,11 @@
import { addAPIProvider, Icon } from '@iconify/vue'
import { icons as mdi } from '@iconify-json/mdi'
import { icons as simpleIcons } from '@iconify-json/simple-icons'
import { addCollection, Icon } from '@iconify/vue'
import { NIcon } from 'naive-ui'
addAPIProvider('', {
resources: ['https://iconify.cdn.haozi.net']
})
addCollection(mdi)
addCollection(simpleIcons)
interface Props {
size?: number

View File

@@ -15,22 +15,29 @@ const getBase = (filename: string) => {
}
const getIconByExt = (ext: string) => {
switch (ext) {
switch (ext.toLowerCase()) {
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
return 'bi:file-earmark-image'
case 'webp':
case 'svg':
return 'mdi-file-image-outline'
case 'mp4':
case 'avi':
case 'mkv':
case 'rmvb':
return 'bi:file-earmark-play'
case 'mov':
return 'mdi-file-video-outline'
case 'mp3':
case 'flac':
case 'wav':
case 'ape':
return 'bi:file-earmark-music'
case 'ogg':
return 'mdi-file-music-outline'
case 'zip':
case 'bz2':
case 'tar':
@@ -38,17 +45,20 @@ const getIconByExt = (ext: string) => {
case 'tgz':
case 'xz':
case '7z':
return 'bi:file-earmark-zip'
case 'rar':
return 'mdi-archive-outline'
case 'doc':
case 'docx':
case 'xls':
case 'xlsx':
return 'bi:file-earmark-word'
case 'ppt':
case 'pptx':
return 'bi:file-earmark-ppt'
return 'mdi-file-document-outline'
case 'pdf':
return 'bi:file-earmark-pdf'
return 'mdi-file-pdf-box'
case 'txt':
case 'md':
case 'log':
@@ -56,7 +66,8 @@ const getIconByExt = (ext: string) => {
case 'ini':
case 'yaml':
case 'yml':
return 'bi:file-earmark-text'
return 'mdi-file-document-outline'
case 'html':
case 'htm':
case 'xml':
@@ -73,11 +84,13 @@ const getIconByExt = (ext: string) => {
case 'go':
case 'rb':
case 'sh':
return 'bi:file-earmark-code'
return 'mdi-file-code-outline'
case '':
return 'bi:file-earmark-binary'
return 'mdi-file-outline'
default:
return 'bi:file-earmark'
return 'mdi-file-outline'
}
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Docker',
icon: 'logos:docker-icon',
icon: 'simple-icons:docker',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Frp Manager',
icon: 'icon-park-outline:connection-box',
icon: 'mdi:swap-horizontal',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Memcached',
icon: 'logos:memcached',
icon: 'mdi:memory',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Percona (MySQL)',
icon: 'logos:percona',
icon: 'simple-icons:mysql',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'OpenResty (Nginx)',
icon: 'logos:nginx',
icon: 'simple-icons:nginx',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 7.4',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.0',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.1',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.2',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.3',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PHP 8.4',
icon: 'logos:php',
icon: 'simple-icons:php',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Podman',
icon: 'devicon:podman',
icon: 'simple-icons:podman',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'PostgreSQL',
icon: 'logos:postgresql',
icon: 'simple-icons:postgresql',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Redis',
icon: 'logos:redis',
icon: 'simple-icons:redis',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'Rsync Manager',
icon: 'file-icons:rsync',
icon: 'mdi:folder-sync-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -14,7 +14,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'S3fs Manager',
icon: 'logos:aws',
icon: 'mdi:dns-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -46,7 +46,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<the-icon :size="48" icon="bi:arrow-up-square" />
<the-icon :size="60" icon="mdi:arrow-up-bold-box-outline" />
</div>
<NText text-18>{{ $gettext('Click or drag files to this area to upload') }}</NText>
<NP depth="3" m-10>{{

View File

@@ -108,9 +108,9 @@ const columns: DataTableColumns<RowData> = [
defaultSortOrder: false,
sorter: 'default',
render(row) {
let icon = 'bi:file-earmark'
let icon = 'mdi:file-outline'
if (row.dir) {
icon = 'bi:folder'
icon = 'mdi:folder-outline'
} else {
icon = getIconByExt(getExt(row.name))
}

View File

@@ -110,16 +110,16 @@ onUnmounted(() => {
<template>
<n-flex>
<n-button @click="handleBack">
<icon-bi-arrow-left />
<i-mdi-arrow-left :size="16" />
</n-button>
<n-button @click="handleForward">
<icon-bi-arrow-right />
<i-mdi-arrow-right :size="16" />
</n-button>
<n-button @click="handleUp">
<icon-bi-arrow-up />
<i-mdi-arrow-up :size="16" />
</n-button>
<n-button @click="handleRefresh">
<icon-bi-arrow-clockwise />
<i-mdi-refresh :size="16" />
</n-button>
<n-input-group flex-1>
<n-tag size="large" v-if="!isInput" flex-1 @click="handleInput">
@@ -154,7 +154,7 @@ onUnmounted(() => {
</template>
</n-input>
<n-button type="primary" @click="handleSearch">
<icon-bi-search />
<i-mdi-search :size="16" />
</n-button>
</n-input-group>
</n-flex>

View File

@@ -45,7 +45,7 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
<n-upload ref="upload" multiple directory-dnd :custom-request="uploadRequest">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<the-icon :size="48" icon="bi:arrow-up-square" />
<the-icon :size="60" icon="mdi:arrow-up-bold-box-outline" />
</div>
<NText text-18> {{ $gettext('Click or drag files to this area to upload') }}</NText>
<NP depth="3" m-10>

View File

@@ -112,7 +112,7 @@ const handleTest = async () => {
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress type="circle" :percentage="100" :stroke-width="3">
<the-icon :size="50" icon="bi:cpu" />
<the-icon :size="50" icon="mdi:cpu-64-bit" />
</n-progress>
{{ $gettext('CPU') }}
</n-flex>
@@ -172,7 +172,7 @@ const handleTest = async () => {
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress type="circle" :percentage="100" :stroke-width="3">
<the-icon :size="50" icon="bi:memory" />
<the-icon :size="50" icon="mdi:memory" />
</n-progress>
{{ $gettext('Memory') }}
</n-flex>
@@ -198,7 +198,7 @@ const handleTest = async () => {
</div>
<div v-else>{{ $gettext('Pending benchmark') }}</div>
<n-progress type="circle" :percentage="100" :stroke-width="3">
<the-icon :size="50" icon="bi:hdd-stack" />
<the-icon :size="50" icon="mdi:harddisk" />
</n-progress>
{{ $gettext('Disk') }}
</n-flex>

View File

@@ -1,5 +1,5 @@
import type { UserConfig } from 'unocss'
import { defineConfig, presetAttributify, presetWind3 } from 'unocss'
import { defineConfig, presetAttributify, presetWind4 } from 'unocss'
const config: UserConfig = {
content: {
@@ -7,7 +7,7 @@ const config: UserConfig = {
exclude: ['node_modules', '.git', '.github', '.vscode', 'build', 'dist', 'public', 'types']
}
},
presets: [presetWind3({ dark: 'class' }), presetAttributify()],
presets: [presetWind4({ dark: 'class' }), presetAttributify()],
shortcuts: [
['wh-full', 'w-full h-full'],
['f-c-c', 'flex justify-center items-center'],