在构建可视化编辑器时,图标的选择和配置往往是提升用户体验的关键因素之一。作为前端开发者,我深知在设计界面时,如何高效、直观地管理图标资源是一个不小的挑战。今天,我将分享如何基于 GrapesJS、Vue3 以及 Ant Design Icons 实现一个功能强大的 Icon Trait 设置,帮助开发者轻松实现按钮和输入框图标的选择、设置,并支持搜索与清除功能。
背景故事:追寻高效图标管理的解决方案
回想起我刚开始接触 GrapesJS 时,遇到的第一个难题就是在组件中添加和管理图标。尽管 GrapesJS 提供了强大的定制能力,但在图标选择和配置方面却显得有些力不从心。尤其是在需要支持大量图标的情况下,如何实现高效的搜索和选择功能成为了亟待解决的问题。
于是,我决定结合 Vue3 的响应式特性和 Ant Design Icons 丰富的图标库,打造一个可视化、易用的 Icon Trait 解决方案。这个过程不仅提升了我的开发效率,也大大优化了最终用户的体验。
方案价值:解决开发与使用中的痛点
在传统的图标管理中,开发者往往需要手动编码或依赖较为简单的选择器,这不仅效率低下,还容易出错。而本方案通过以下几个方面解决了这些痛点:
- 可视化搜索与选择:用户可以直观地通过搜索框快速找到所需图标,无需记忆所有图标名称。
- 实时预览:选择过程中,用户可以实时查看所选图标在按钮或输入框中的展示效果,提升设计准确性。
- 高扩展性:基于 Vue3 和 Ant Design Icons 的组件化设计,使得该方案易于扩展和维护。
核心实现:TraitIcon 的开发
TraitIcon.ts
首先,我们需要在 GrapesJS 中添加一个新的 Trait 类型,用于管理图标的选择和设置。以下是核心代码:
export const initIconTrait = (editor: Editor) => {
loadVueAndAntd().then(() => {
editor.TraitManager.addType('icon', {
noLabel: false,
createInput({ trait }) {
const el = document.createElement('div');
el.style.marginRight = '1px';
const icon = trait.attributes['value']
const onUpdateIconDataVue = (newValue) => {
trait.target.set(String(trait.id), newValue);
if (!trait.target.attributes) {
trait.target.attributes = {};
}
if (!trait.target.attributes.attributes) {
trait.target.attributes.attributes = {};
}
trait.target.attributes.attributes[trait.id] = newValue;
trait.target.set('timestamp', Date.now());
}
mountVueComponent({
el,
componentImporter: () => import('@/views/system/function/editor/traits/Icon.vue'),
props: {
icon, onUpdateIconData: onUpdateIconDataVue
}
})
.then((app) => {
el.addEventListener('destroy', () => {
app.unmount();
});
})
.catch((err) => {
console.error(`Failed to mount icon trait component:`, err);
});
return el;
},
});
});
};
TraitIcon.vue
接下来,我们编写 Vue 组件,用于图标的搜索、选择和清除功能:
<template>
<div class="icon-selector">
<!-- 搜索框、清除按钮和展开/缩起按钮 -->
<a-row justify="space-between" :gutter="[0, 2]" :wrap="false">
<a-col :span="3">
<a-flex style="padding-left: 5px;">
<a-tooltip :title="isCollapsed ? '显示图标列表' : '隐藏图标列表'" placement="top">
<a-button type="default" size="small" :icon="h(Icons[isCollapsed ? 'EyeOutlined' : 'EyeInvisibleOutlined'])"
@click="toggleCollapse" />
</a-tooltip>
</a-flex>
</a-col>
<a-col :span="17">
<a-input class="input-search" v-model:value="searchKeyword" placeholder="输入图标" :bordered="true"
@focus="showIconGrid" />
</a-col>
<a-col :span="3">
<a-flex justify="end" style="padding-right: 5px;">
<a-tooltip title="清除选择" placement="top">
<a-button type="primary" danger size="small" :icon="h(Icons.DeleteOutlined)" @click="clearSelection" />
</a-tooltip>
</a-flex>
</a-col>
</a-row>
<!-- 图标网格展示区域 -->
<div class="icon-grid" v-show="!isCollapsed">
<div v-for="icon in filteredIcons" :key="icon.name" class="icon-item"
:class="{ selected: selectedIcon === icon.name }" @click="selectIcon(icon)">
<component :is="icon.component" two-tone-color="#1890ff" style="font-size: 24px;" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineEmits, defineProps, h } from 'vue';
import * as Icons from '@ant-design/icons-vue';
const emit = defineEmits(['updateIconData']);
defineProps<{
label: string;
}>();
// 收集所有图标数据
const iconData = Object.keys(Icons).map((name) => ({
name,
component: Icons[name]
}));
// 排除的不需要显示的图标
const EXCLUDED_ICONS = [
'default',
'createFromIconfontCN',
'getTwoToneColor',
'setTwoToneColor'
];
const searchKeyword = ref('');
const selectedIcon = ref<string | null>(null);
const selectedIconComponent = ref<any>(null);
// 过滤后的图标列表
const filteredIcons = computed(() =>
iconData.filter((icon) => {
const matchesSearch = icon.name.toLowerCase().includes(searchKeyword.value.toLowerCase());
const isNotExcluded = !EXCLUDED_ICONS.includes(icon.name);
return matchesSearch && isNotExcluded;
})
);
// 是否折叠图标列表
const isCollapsed = ref(true);
function selectIcon(icon: { name: string; component: any }) {
selectedIcon.value = icon.name;
selectedIconComponent.value = icon.component;
emit('updateIconData', icon.name);
}
function clearSelection() {
selectedIcon.value = null;
selectedIconComponent.value = null;
emit('updateIconData', undefined);
}
function toggleCollapse() {
isCollapsed.value = !isCollapsed.value;
}
// 当输入框聚焦时显示图标网格
function showIconGrid() {
isCollapsed.value = false;
}
</script>
<style scoped>
.icon-selector {
width: 100%;
padding-top: 8px;
padding-bottom: 8px;
max-width: 100%;
background-color: #fff;
border: 1px solid #f0f0f0;
border-radius: 4px;
overflow-x: hidden;
}
/* 输入框样式 */
.input-search {
height: 24px;
border: 1px solid #d9d9d9;
color: black;
border-radius: 3px;
}
/* 图标网格布局 */
.icon-grid {
width: 100%;
display: grid;
margin-top: 5px;
padding-left: 5px;
padding-right: 5px;
max-height: 200px;
overflow-y: auto;
grid-template-columns: repeat(8, 1fr);
gap: 4px;
}
/* 单个图标项 */
.icon-item {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #e8e8e8;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
overflow: hidden;
min-width: 0;
color: black;
}
.icon-item:hover {
border-color: #1890ff;
}
.icon-item.selected {
border-color: #1890ff;
}
</style>
使用示例
在实际使用中,可以通过以下方式将图标 Trait 集成到按钮组件中:
<template>
<a-button v-bind="filteredInputProps" @click="handleClick">{
{ filteredInputProps.text }}</a-button>
</template>
<script lang="ts" setup>
import { computed, h } from 'vue';
import * as Icons from '@ant-design/icons-vue';
const props = defineProps<{
buttonProps: ButtonProps;
model?: any;
}>();
// 过滤掉 undefined 的属性
const filteredButtonProps = computed(() => {
const entries = Object.entries(props.buttonProps)
.filter(([, val]) => val !== undefined)
.map(([key, val]) => {
if (key === 'iconName' && val) {
return ['icon', h(Icons[val])];
}
return [key, val];
});
return Object.fromEntries(entries);
});
</script>
深入解析:核心代码详解
TraitIcon.ts 的作用
TraitIcon.ts
文件的主要职责是在 GrapesJS 中定义一个新的 Trait 类型,命名为 icon
。该 Trait 类型负责加载 Vue 组件,并将其挂载到 GrapesJS 的 Trait 面板中。通过这种方式,开发者可以在 GrapesJS 的组件属性面板中,直观地进行图标的搜索与选择。
TraitIcon.vue 的功能
TraitIcon.vue
组件是整个图标选择功能的核心。它包含以下几个关键部分:
- 搜索与清除功能:通过
a-input
组件,用户可以输入关键词快速搜索所需图标。同时,清除按钮允许用户快速取消选择。 - 图标网格展示:使用 CSS Grid 布局,将所有可选图标整齐地排列在网格中,支持滚动查看和点击选择。
- 状态管理:通过 Vue3 的
ref
和computed
,实现图标列表的动态过滤和选中状态的管理。
使用示例的作用
使用示例代码展示了如何在实际的按钮组件中集成图标 Trait。通过 filteredInputProps
,我们可以动态过滤掉未定义的属性,并将选中的图标渲染到按钮上,实现图标与按钮文本的无缝结合。
总结与展望
通过本文的介绍,我们探讨了如何基于 GrapesJS、Vue3 以及 Ant Design Icons 构建一个高效、直观的 Icon Trait 设置。这不仅提升了开发者的工作效率,也为最终用户提供了更友好的操作体验。在未来的开发中,可以进一步扩展该方案,支持更多图标库或自定义图标的导入,满足更广泛的需求。
希望这篇文章能够为需要在 GrapesJS 中集成图标选择功能的开发者提供有价值的参考。如果你有任何疑问或建议,欢迎在评论区交流讨论!