222 lines
7.8 KiB
Vue
222 lines
7.8 KiB
Vue
<template>
|
|
<div class="aksrt-fa-icon-picker">
|
|
<n-popover trigger="click" placement="bottom-start" :width="360" :style="{ padding: 0 }">
|
|
<template #trigger>
|
|
<n-input
|
|
:value="modelValue"
|
|
:placeholder="placeholder"
|
|
readonly
|
|
clearable
|
|
@clear="$emit('update:modelValue', '')"
|
|
>
|
|
<template #prefix>
|
|
<FaIcon v-if="modelValue" :icon="modelValue" />
|
|
</template>
|
|
<template #suffix>
|
|
<FaIcon icon="fas fa-chevron-down" :size="12" />
|
|
</template>
|
|
</n-input>
|
|
</template>
|
|
|
|
<div class="aksrt-icon-picker__panel">
|
|
<div class="aksrt-icon-picker__search">
|
|
<n-input
|
|
v-model:value="search"
|
|
placeholder="搜索图标..."
|
|
size="small"
|
|
clearable
|
|
>
|
|
<template #prefix>
|
|
<FaIcon icon="fas fa-search" :size="12" />
|
|
</template>
|
|
</n-input>
|
|
</div>
|
|
|
|
<div class="aksrt-icon-picker__tabs">
|
|
<n-radio-group v-model:value="activePrefix" size="small">
|
|
<n-radio-button value="fas">Solid</n-radio-button>
|
|
<n-radio-button value="far">Regular</n-radio-button>
|
|
<n-radio-button value="fab">Brands</n-radio-button>
|
|
</n-radio-group>
|
|
</div>
|
|
|
|
<div class="aksrt-icon-picker__grid" ref="gridRef">
|
|
<div
|
|
v-for="icon in filteredIcons"
|
|
:key="icon"
|
|
class="aksrt-icon-picker__item"
|
|
:class="{ 'is-active': modelValue === `${activePrefix} ${icon}` }"
|
|
:title="icon"
|
|
@click="selectIcon(icon)"
|
|
>
|
|
<i :class="[activePrefix, icon]"></i>
|
|
</div>
|
|
<div v-if="filteredIcons.length === 0" class="aksrt-icon-picker__empty">
|
|
未找到匹配图标
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</n-popover>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
/**
|
|
* FaIconPicker — searchable FA5 icon picker.
|
|
* Renders an input that opens a popover grid of FA5 icons.
|
|
*/
|
|
import { ref, computed } from 'vue'
|
|
import FaIcon from './FaIcon.vue'
|
|
|
|
const props = defineProps({
|
|
modelValue: { type: String, default: '' },
|
|
placeholder: { type: String, default: '选择图标' },
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const search = ref('')
|
|
const activePrefix = ref('fas')
|
|
const gridRef = ref(null)
|
|
|
|
// Common FA5 icons subset — a curated list covering most admin UI needs.
|
|
// Full FA5 has 1600+ icons; this subset has ~200 of the most useful ones.
|
|
const SOLID_ICONS = [
|
|
'fa-home', 'fa-cog', 'fa-cogs', 'fa-wrench', 'fa-sliders-h', 'fa-tools',
|
|
'fa-search', 'fa-filter', 'fa-sort', 'fa-sync', 'fa-redo', 'fa-undo',
|
|
'fa-plus', 'fa-minus', 'fa-times', 'fa-check', 'fa-exclamation', 'fa-info',
|
|
'fa-question', 'fa-bell', 'fa-envelope', 'fa-comment', 'fa-comments',
|
|
'fa-user', 'fa-users', 'fa-user-plus', 'fa-user-shield', 'fa-user-circle',
|
|
'fa-lock', 'fa-unlock', 'fa-key', 'fa-shield-alt', 'fa-fingerprint',
|
|
'fa-globe', 'fa-globe-americas', 'fa-link', 'fa-external-link-alt', 'fa-anchor',
|
|
'fa-image', 'fa-images', 'fa-camera', 'fa-video', 'fa-music', 'fa-file',
|
|
'fa-folder', 'fa-folder-open', 'fa-download', 'upload', 'fa-cloud',
|
|
'fa-database', 'fa-server', 'fa-hdd', 'fa-memory', 'fa-microchip',
|
|
'fa-desktop', 'fa-laptop', 'fa-mobile-alt', 'fa-tablet-alt',
|
|
'fa-chart-bar', 'fa-chart-line', 'fa-chart-pie', 'fa-chart-area',
|
|
'fa-tachometer-alt', 'fa-tasks', 'fa-clipboard', 'fa-clipboard-list',
|
|
'fa-list', 'fa-list-alt', 'fa-list-ol', 'fa-list-ul', 'fa-th', 'fa-th-large',
|
|
'fa-tags', 'fa-tag', 'fa-bookmark', 'fa-star', 'fa-heart', 'fa-thumbs-up',
|
|
'fa-share', 'fa-share-alt', 'fa-external-link-alt', 'fa-clipboard-check',
|
|
'fa-eye', 'fa-eye-slash', 'fa-lightbulb', 'fa-magic', 'fa-palette',
|
|
'fa-paint-brush', 'fa-font', 'fa-text-height', 'fa-bold', 'fa-italic',
|
|
'fa-code', 'fa-terminal', 'fa-bug', 'fa-exclamation-triangle',
|
|
'fa-exclamation-circle', 'fa-check-circle', 'fa-info-circle',
|
|
'fa-spinner', 'fa-circle-notch', 'fa-hourglass-half', 'fa-clock',
|
|
'fa-calendar', 'fa-calendar-alt', 'fa-history', 'fa-hourglass',
|
|
'fa-map-marker-alt', 'fa-map', 'fa-directions', 'fa-compass',
|
|
'fa-shopping-cart', 'fa-shopping-bag', 'fa-credit-card', 'fa-money-bill',
|
|
'fa-coins', 'fa-dollar-sign', 'fa-wallet', 'fa-receipt',
|
|
'fa-newspaper', 'fa-rss', 'fa-podcast', 'fa-megaphone',
|
|
'fa-flag', 'fa-trophy', 'fa-award', 'fa-medal', 'fa-gift',
|
|
'fa-fire', 'fa-bolt', 'fa-sun', 'fa-moon', 'fa-cloud-sun',
|
|
'fa-male', 'fa-female', 'fa-child', 'fa-id-card',
|
|
'fa-paper-plane', 'fa-paperclip', 'fa-at', 'fa-hashtag',
|
|
'fa-ellipsis-h', 'fa-ellipsis-v', 'fa-bars', 'fa-columns',
|
|
'fa-window-maximize', 'fa-window-minimize', 'fa-window-close',
|
|
'fa-expand', 'fa-compress', 'fa-arrows-alt', 'fa-crosshairs',
|
|
'fa-sign-in-alt', 'fa-sign-out-alt', 'fa-door-open',
|
|
'fa-plug', 'fa-power-off', 'fa-redo-alt', 'fa-sync-alt',
|
|
]
|
|
|
|
const REGULAR_ICONS = [
|
|
'fa-heart', 'fa-star', 'fa-bookmark', 'fa-user', 'fa-envelope',
|
|
'fa-comment', 'fa-comments', 'fa-bell', 'fa-clock', 'fa-calendar',
|
|
'fa-clipboard', 'fa-file', 'fa-folder', 'fa-image', 'fa-eye',
|
|
'fa-thumbs-up', 'fa-flag', 'fa-share-square', 'fa-hand-point-right',
|
|
'fa-life-ring', 'fa-lightbulb', 'fa-question-circle', 'fa-check-circle',
|
|
'fa-times-circle', 'fa-dot-circle', 'fa-circle', 'fa-square',
|
|
'fa-moon', 'fa-sun', 'fa-snowflake', 'fa-lemon', 'fa-gem',
|
|
'fa-copy', 'fa-edit', 'fa-trash-alt', 'fa-save', 'fa-window-maximize',
|
|
'fa-window-minimize', 'fa-window-close', 'fa-caret-square-right',
|
|
'fa-address-book', 'fa-address-card', 'fa-id-badge',
|
|
'fa-building', 'fa-chart-bar', 'fa-newspaper', 'fa-map',
|
|
]
|
|
|
|
const BRAND_ICONS = [
|
|
'fa-github', 'fa-gitlab', 'fa-bitbucket', 'fa-google', 'fa-google-plus',
|
|
'fa-facebook', 'fa-facebook-f', 'fa-twitter', 'fa-x-twitter',
|
|
'fa-instagram', 'fa-linkedin', 'fa-linkedin-in', 'fa-youtube',
|
|
'fa-weixin', 'fa-qq', 'fa-weibo', 'fa-telegram', 'fa-telegram-plane',
|
|
'fa-discord', 'fa-slack', 'fa-whatsapp', 'fa-skype',
|
|
'fa-apple', 'fa-windows', 'fa-linux', 'fa-android', 'fa-chrome',
|
|
'fa-firefox', 'fa-edge', 'fa-safari', 'fa-opera',
|
|
'fa-html5', 'fa-css3-alt', 'fa-js', 'fa-js-square', 'fa-node',
|
|
'fa-react', 'fa-vuejs', 'fa-angular', 'fa-php', 'fa-python',
|
|
'fa-java', 'fa-rust', 'fa-golang', 'fa-swift',
|
|
'fa-docker', 'fa-aws', 'fa-digital-ocean', 'fa-cloudflare',
|
|
'fa-wordpress', 'fa-joomla', 'fa-drupal', 'fa-shopify',
|
|
'fa-paypal', 'fa-stripe', 'fa-alipay', 'fa-weixin',
|
|
'fa-tiktok', 'fa-snapchat', 'fa-pinterest', 'fa-reddit',
|
|
'fa-stack-overflow', 'fa-codepen', 'fa-npm', 'fa-yarn',
|
|
'fa-twitch', 'fa-spotify', 'fa-soundcloud', 'fa-music',
|
|
'fa-bilibili',
|
|
]
|
|
|
|
const iconSets = { fas: SOLID_ICONS, far: REGULAR_ICONS, fab: BRAND_ICONS }
|
|
|
|
const filteredIcons = computed(() => {
|
|
const set = iconSets[activePrefix.value] || SOLID_ICONS
|
|
const q = search.value.toLowerCase().replace(/\s+/g, '')
|
|
if (!q) return set
|
|
return set.filter(i => i.includes(q))
|
|
})
|
|
|
|
function selectIcon(icon) {
|
|
emit('update:modelValue', `${activePrefix.value} ${icon}`)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.aksrt-icon-picker__panel {
|
|
padding: 12px;
|
|
}
|
|
|
|
.aksrt-icon-picker__search {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.aksrt-icon-picker__tabs {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.aksrt-icon-picker__grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(8, 1fr);
|
|
gap: 4px;
|
|
max-height: 260px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.aksrt-icon-picker__item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
color: #555;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.aksrt-icon-picker__item:hover {
|
|
background: #f0f5ff;
|
|
color: #1890ff;
|
|
}
|
|
|
|
.aksrt-icon-picker__item.is-active {
|
|
background: #1890ff;
|
|
color: #fff;
|
|
}
|
|
|
|
.aksrt-icon-picker__empty {
|
|
grid-column: 1 / -1;
|
|
text-align: center;
|
|
padding: 20px;
|
|
color: #999;
|
|
font-size: 13px;
|
|
}
|
|
</style>
|