Luminous.X in the house  
Enjoy my whole life!

最近在做自己的开发平台,发现目前Antd和Element都没有提供图标选择器,所以自己写一个,跟el-input一样可以双向绑定

弹窗组件使用的Element-Plus的Dialog组件,图标感觉还是Antd的好看一些,所以这里引用的Antd的图标,当然可以引用其他家的图标

1. main.ts中全局注册所有图标组件

import * as antIcons from '@ant-design/icons-vue'

const app = createApp(App);
// 全局注册图标
Object.keys(antIcons).forEach((key: string) => {
  app.component(key, antIcons[key as keyof typeof antIcons])
})

2. 封装IconPicker.vue图标选择器组件

<template>
    <div class="icon-picker" @click="openPicker">
        <div class="icon-picker-icon-name">{{ modelValue == '' || modelValue == null || modelValue == undefined ? '点击选择图标' : modelValue }}</div>
        <div class="icon-picker-icon">
            <component v-if="modelValue != '' && modelValue != null && modelValue != undefined" :is="modelValue" />
        </div>
    </div>
    <el-dialog v-model="pickerVisible" title="图标选择器" destroy-on-close width="970">
        <el-input class="icon-picker-search" v-model="searchValue" placeholder="搜索图标" @change="searchIcon"></el-input>
        <el-tabs v-model="typeTab" type="border-card">
            <el-tab-pane class="icon-picker-tab" label="全部" name="all">
                <div class="icon-content">
                    <div v-for="icon in all" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="方向性图标" name="direction">
                <div class="icon-content">
                    <div v-for="icon in direction" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="提示建议性图标" name="prompt">
                <div class="icon-content">
                    <div v-for="icon in prompt" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="编辑类图标" name="edit">
                <div class="icon-content">
                    <div v-for="icon in edit" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="数据类图标" name="data">
                <div class="icon-content">
                    <div v-for="icon in data" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="品牌和标识" name="brand">
                <div class="icon-content">
                    <div v-for="icon in brand" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
            <el-tab-pane class="icon-picker-tab" label="网站通用图标" name="common">
                <div class="icon-content">
                    <div v-for="icon in common" :key="icon" class="icon-item" @click="checkIcon(icon)">
                        <component :is="icon"></component>
                    </div>
                </div>
            </el-tab-pane>
        </el-tabs>
    </el-dialog>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

// 双向绑定属性
const props = defineProps({
    modelValue: {
        type: String,
        default: ''
    }
})
const emit = defineEmits(['update:modelValue'])

// 弹窗显示
const pickerVisible = ref(false);
// 查询值
const searchValue = ref();

// 方向性图标
const directionIcon = ['StepBackwardOutlined', 'StepForwardOutlined', 'FastBackwardOutlined', 'FastForwardOutlined', 'ShrinkOutlined', 'ArrowsAltOutlined', 'DownOutlined', 'UpOutlined', 'LeftOutlined', 'RightOutlined', 'CaretUpOutlined', 'CaretDownOutlined', 'CaretLeftOutlined', 'CaretRightOutlined', 'UpCircleOutlined', 'DownCircleOutlined', 'LeftCircleOutlined', 'RightCircleOutlined', 'DoubleRightOutlined', 'DoubleLeftOutlined', 'VerticalLeftOutlined', 'VerticalRightOutlined', 'VerticalAlignTopOutlined', 'VerticalAlignMiddleOutlined', 'VerticalAlignBottomOutlined', 'ForwardOutlined', 'BackwardOutlined', 'RollbackOutlined', 'EnterOutlined', 'RetweetOutlined', 'SwapOutlined', 'SwapLeftOutlined', 'SwapRightOutlined', 'ArrowUpOutlined', 'ArrowDownOutlined', 'ArrowLeftOutlined', 'ArrowRightOutlined', 'PlayCircleOutlined', 'UpSquareOutlined', 'DownSquareOutlined', 'LeftSquareOutlined', 'RightSquareOutlined', 'LoginOutlined', 'LogoutOutlined', 'MenuFoldOutlined', 'MenuUnfoldOutlined', 'BorderBottomOutlined', 'BorderHorizontalOutlined', 'BorderInnerOutlined', 'BorderOuterOutlined', 'BorderLeftOutlined', 'BorderRightOutlined', 'BorderTopOutlined', 'BorderVerticleOutlined', 'PicCenterOutlined', 'PicLeftOutlined', 'PicRightOutlined', 'RadiusBottomleftOutlined', 'RadiusBottomrightOutlined', 'RadiusUpleftOutlined', 'RadiusUprightOutlined', 'FullscreenOutlined', 'FullscreenExitOutlined']
// 提示建议性图标
const promptIcon = ['QuestionOutlined', 'QuestionCircleOutlined', 'PlusOutlined', 'PlusCircleOutlined', 'PauseOutlined', 'PauseCircleOutlined', 'MinusOutlined', 'MinusCircleOutlined', 'PlusSquareOutlined', 'MinusSquareOutlined', 'InfoOutlined', 'InfoCircleOutlined', 'ExclamationOutlined', 'ExclamationCircleOutlined', 'CloseOutlined', 'CloseCircleOutlined', 'CloseSquareOutlined', 'CheckOutlined', 'CheckCircleOutlined', 'CheckSquareOutlined', 'ClockCircleOutlined', 'WarningOutlined', 'IssuesCloseOutlined', 'StopOutlined']
// 编辑类图标
const editIcon = ['EditOutlined', 'FormOutlined', 'CopyOutlined', 'ScissorOutlined', 'DeleteOutlined', 'SnippetsOutlined', 'DiffOutlined', 'HighlightOutlined', 'AlignCenterOutlined', 'AlignLeftOutlined', 'AlignRightOutlined', 'BgColorsOutlined', 'BoldOutlined', 'ItalicOutlined', 'UnderlineOutlined', 'StrikethroughOutlined', 'RedoOutlined', 'UndoOutlined', 'ZoomInOutlined', 'ZoomOutOutlined', 'FontColorsOutlined', 'FontSizeOutlined', 'LineHeightOutlined', 'DashOutlined', 'SmallDashOutlined', 'SortAscendingOutlined', 'SortDescendingOutlined', 'DragOutlined', 'OrderedListOutlined', 'UnorderedListOutlined', 'RadiusSettingOutlined', 'ColumnWidthOutlined', 'ColumnHeightOutlined']
// 数据类图标
const dataIcon = ['AreaChartOutlined', 'PieChartOutlined', 'BarChartOutlined', 'DotChartOutlined', 'LineChartOutlined', 'RadarChartOutlined', 'HeatMapOutlined', 'FallOutlined', 'RiseOutlined', 'StockOutlined', 'BoxPlotOutlined', 'FundOutlined', 'SlidersOutlined']
// 品牌和标识
const brandIcon = ['AndroidOutlined', 'AppleOutlined', 'WindowsOutlined', 'IeOutlined', 'ChromeOutlined', 'GithubOutlined', 'AliwangwangOutlined', 'DingdingOutlined', 'WeiboSquareOutlined', 'WeiboCircleOutlined', 'TaobaoCircleOutlined', 'Html5Outlined', 'WeiboOutlined', 'TwitterOutlined', 'WechatOutlined', 'YoutubeOutlined', 'AlipayCircleOutlined', 'TaobaoOutlined', 'SkypeOutlined', 'QqOutlined', 'MediumWorkmarkOutlined', 'GitlabOutlined', 'MediumOutlined', 'LinkedinOutlined', 'GooglePlusOutlined', 'DropboxOutlined', 'FacebookOutlined', 'CodepenOutlined', 'CodeSandboxOutlined', 'AmazonOutlined', 'GoogleOutlined', 'CodepenCircleOutlined', 'AlipayOutlined', 'AntDesignOutlined', 'AntCloudOutlined', 'AliyunOutlined', 'ZhihuOutlined', 'SlackOutlined', 'SlackSquareOutlined', 'BehanceOutlined', 'BehanceSquareOutlined', 'DribbbleOutlined', 'DribbbleSquareOutlined', 'InstagramOutlined', 'YuqueOutlined', 'AlibabaOutlined', 'YahooOutlined', 'RedditOutlined', 'SketchOutlined']
// 网站通用图标
const commonIcon = ['AccountBookOutlined', 'AimOutlined', 'AlertOutlined', 'ApartmentOutlined', 'ApiOutlined', 'AppstoreAddOutlined', 'AppstoreOutlined', 'AudioOutlined', 'AudioMutedOutlined', 'AuditOutlined', 'BankOutlined', 'BarcodeOutlined', 'BarsOutlined', 'BellOutlined', 'BlockOutlined', 'BookOutlined', 'BorderOutlined', 'BorderlessTableOutlined', 'BranchesOutlined', 'BugOutlined', 'BuildOutlined', 'BulbOutlined', 'CalculatorOutlined', 'CalendarOutlined', 'CameraOutlined', 'CarOutlined', 'CarryOutOutlined', 'CiCircleOutlined', 'CiOutlined', 'ClearOutlined', 'CloudDownloadOutlined', 'CloudOutlined', 'CloudServerOutlined', 'CloudSyncOutlined', 'CloudUploadOutlined', 'ClusterOutlined', 'CodeOutlined', 'CoffeeOutlined', 'CommentOutlined', 'CompassOutlined', 'CompressOutlined', 'ConsoleSqlOutlined', 'ContactsOutlined', 'ContainerOutlined', 'ControlOutlined', 'CopyrightCircleOutlined', 'CopyrightOutlined', 'CreditCardOutlined', 'CrownOutlined', 'CustomerServiceOutlined', 'DashboardOutlined', 'DatabaseOutlined', 'DeleteColumnOutlined', 'DeleteRowOutlined', 'DeliveredProcedureOutlined', 'DeploymentUnitOutlined', 'DesktopOutlined', 'DingtalkOutlined', 'DisconnectOutlined', 'DislikeOutlined', 'DollarCircleOutlined', 'DollarOutlined', 'DownloadOutlined', 'EllipsisOutlined', 'EnvironmentOutlined', 'EuroCircleOutlined', 'EuroOutlined', 'ExceptionOutlined', 'ExpandAltOutlined', 'ExpandOutlined', 'ExperimentOutlined', 'ExportOutlined', 'EyeOutlined', 'EyeInvisibleOutlined', 'FieldBinaryOutlined', 'FieldNumberOutlined', 'FieldStringOutlined', 'FieldTimeOutlined', 'FileAddOutlined', 'FileDoneOutlined', 'FileExcelOutlined', 'FileExclamationOutlined', 'FileOutlined', 'FileGifOutlined', 'FileImageOutlined', 'FileJpgOutlined', 'FileMarkdownOutlined', 'FilePdfOutlined', 'FilePptOutlined', 'FileProtectOutlined', 'FileSearchOutlined', 'FileSyncOutlined', 'FileTextOutlined', 'FileUnknownOutlined', 'FileWordOutlined', 'FileZipOutlined', 'FilterOutlined', 'FireOutlined', 'FlagOutlined', 'FolderAddOutlined', 'FolderOutlined', 'FolderOpenOutlined', 'FolderViewOutlined', 'ForkOutlined', 'FormatPainterOutlined', 'FrownOutlined', 'FunctionOutlined', 'FundProjectionScreenOutlined', 'FundViewOutlined', 'FunnelPlotOutlined', 'GatewayOutlined', 'GifOutlined', 'GiftOutlined', 'GlobalOutlined', 'HolderOutlined', 'HistoryOutlined', 'HeartOutlined', 'HddOutlined', 'GroupOutlined', 'GoldOutlined', 'HomeOutlined', 'HourglassOutlined', 'IdcardOutlined', 'ImportOutlined', 'InboxOutlined', 'InsertRowAboveOutlined', 'InsertRowBelowOutlined', 'InsertRowLeftOutlined', 'InsertRowRightOutlined', 'InsuranceOutlined', 'InteractionOutlined', 'KeyOutlined', 'LaptopOutlined', 'LayoutOutlined', 'LikeOutlined', 'LineOutlined', 'LinkOutlined', 'Loading3QuartersOutlined', 'LoadingOutlined', 'LockOutlined', 'MacCommandOutlined', 'MailOutlined', 'ManOutlined', 'MedicineBoxOutlined', 'MehOutlined', 'MenuOutlined', 'MergeCellsOutlined', 'MessageOutlined', 'MobileOutlined', 'MoneyCollectOutlined', 'MonitorOutlined', 'MoreOutlined', 'NodeCollapseOutlined', 'NodeExpandOutlined', 'NodeIndexOutlined', 'NotificationOutlined', 'NumberOutlined', 'OneToOneOutlined', 'PaperClipOutlined', 'PartitionOutlined', 'PayCircleOutlined', 'PercentageOutlined', 'PhoneOutlined', 'PictureOutlined', 'PlaySquareOutlined', 'PoundCircleOutlined', 'PoundOutlined', 'PoweroffOutlined', 'PrinterOutlined', 'ProfileOutlined', 'ProjectOutlined', 'PropertySafetyOutlined', 'PullRequestOutlined', 'PushpinOutlined', 'QrcodeOutlined', 'ReadOutlined', 'ReconciliationOutlined', 'RedEnvelopeOutlined', 'ReloadOutlined', 'RestOutlined', 'RobotOutlined', 'RocketOutlined', 'RotateLeftOutlined', 'RotateRightOutlined', 'SafetyCertificateOutlined', 'SafetyOutlined', 'SaveOutlined', 'ScanOutlined', 'ScheduleOutlined', 'SearchOutlined', 'SecurityScanOutlined', 'SelectOutlined', 'SendOutlined', 'SettingOutlined', 'ShakeOutlined', 'ShareAltOutlined', 'ShopOutlined', 'ShoppingCartOutlined', 'ShoppingOutlined', 'SisternodeOutlined', 'SkinOutlined', 'SmileOutlined', 'SolutionOutlined', 'SoundOutlined', 'TableOutlined', 'SyncOutlined', 'SwitcherOutlined', 'SubnodeOutlined', 'StarOutlined', 'SplitCellsOutlined', 'TabletOutlined', 'TagOutlined', 'TagsOutlined', 'TeamOutlined', 'ThunderboltOutlined', 'ToTopOutlined', 'TrophyOutlined', 'TranslationOutlined', 'TransactionOutlined', 'TrademarkOutlined', 'TrademarkCircleOutlined', 'ToolOutlined', 'UngroupOutlined', 'UnlockOutlined', 'UploadOutlined', 'UsbOutlined', 'UserAddOutlined', 'UserDeleteOutlined', 'VideoCameraAddOutlined', 'VerifiedOutlined', 'UsergroupDeleteOutlined', 'UsergroupAddOutlined', 'UserSwitchOutlined', 'UserOutlined', 'VideoCameraOutlined', 'WalletOutlined', 'WhatsAppOutlined', 'WifiOutlined', 'WomanOutlined']
// 全部图标
const allIcon = [...directionIcon, ...promptIcon, ...editIcon, ...dataIcon, ...brandIcon, ...commonIcon]

// 初始化,加载所有图标
const direction = ref(directionIcon)
const prompt = ref(promptIcon)
const edit = ref(editIcon)
const data = ref(dataIcon)
const brand = ref(brandIcon)
const common = ref(commonIcon)
const all = ref(allIcon)

// 加载所有图标
const setAll = () => {
    direction.value = directionIcon
    prompt.value = promptIcon
    edit.value = editIcon
    data.value = dataIcon
    brand.value = brandIcon
    common.value = commonIcon
    all.value = allIcon
}

// 图标类型Tab
const typeTab = ref('all')
const checkIcon = (icon: string) => {
    emit('update:modelValue', icon)
    pickerVisible.value = false
}

// 快速查询定位图标
const searchIcon = (value: string) => {
    if (value == '' || value == undefined || value == null) {
        setAll()
    } else {
        direction.value = directionIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        prompt.value = promptIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        edit.value = editIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        data.value = dataIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        brand.value = brandIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        common.value = commonIcon.filter(icon => icon.toLowerCase().includes(value.toLowerCase()))
        all.value = [...direction.value, ...prompt.value, ...edit.value, ...data.value, ...brand.value, ...common.value]
    }
}

// 打开图标选择器
const openPicker = () => {
    pickerVisible.value = true
}

</script>

<style lang="less" scoped>
.icon-picker {
    width: 280px;
    height: 35px;
    border: solid 1px #409eff;
    border-radius: 5px;
    background-color: rgb(64, 158, 255);
    cursor: pointer;
    transition: background-color 0.3s ease;
    color: rgb(255, 255, 255);
    display: flex;
    align-items: center;

    .icon-picker-icon-name {
        width: 75%;
        text-align: center;
        font-size: 14px;
        border-right: solid 1px rgb(235, 234, 234);
    }

    .icon-picker-icon {
        width: 25%;
        font-size: 25px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
}

.icon-picker:hover {
    background-color: rgb(121, 187, 255);
}

.icon-picker-search {
    margin-bottom: 15px;
}

.icon-picker-tab {
    height: 50vh;
    overflow-y: auto;
}

.icon-content {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;

    .icon-item {
        width: 65px;
        height: 60px;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        font-size: 35px;
        border-radius: 5px;
        transition: background-color 0.2s ease;
        border: solid 1px rgb(235, 235, 235);
    }

    .icon-item:hover {
        background-color: rgb(233, 233, 233);
    }
}
</style>

3. main.ts中全局注册IconPicker组件

// 导入图标选择器
import IconPicker from '@/components/IconPicker.vue'

const app = createApp(App);

// 全局注册图标选择器
app.component('IconPicker', IconPicker)

4. 表单中使用示例

<el-form-item label="导航图标:">
       <icon-picker v-model="data.NAV_ICON"></icon-picker>
</el-form-item>

 

组件效果(未选择图标):

 点击选择图标弹出选择器:可以分类/快速查找

 点击图标选中效果:

效果还不赖= =

posted on 2025-07-02 01:56  Luminous_X  阅读(227)  评论(0)    收藏  举报