index.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <!-- 图标选择器 -->
  2. <template>
  3. <div ref="iconSelectRef" :style="{ width: props.width }">
  4. <el-popover :visible="popoverVisible" :width="props.width" placement="bottom-end">
  5. <template #reference>
  6. <div @click="popoverVisible = !popoverVisible">
  7. <slot>
  8. <el-input v-model="selectedIcon" readonly placeholder="点击选择图标" class="reference">
  9. <template #prepend>
  10. <!-- 根据图标类型展示 -->
  11. <el-icon v-if="isElementIcon">
  12. <component :is="selectedIcon.replace('el-icon-', '')" />
  13. </el-icon>
  14. <template v-else>
  15. <div :class="`i-svg:${selectedIcon}`" />
  16. </template>
  17. </template>
  18. <template #suffix>
  19. <!-- 清空按钮 -->
  20. <el-icon
  21. v-if="selectedIcon"
  22. style="margin-right: 8px"
  23. @click.stop="clearSelectedIcon"
  24. >
  25. <CircleClose />
  26. </el-icon>
  27. <el-icon
  28. :style="{
  29. transform: popoverVisible ? 'rotate(180deg)' : 'rotate(0)',
  30. transition: 'transform .5s',
  31. }"
  32. >
  33. <ArrowDown @click.stop="togglePopover" />
  34. </el-icon>
  35. </template>
  36. </el-input>
  37. </slot>
  38. </div>
  39. </template>
  40. <!-- 图标选择弹窗 -->
  41. <div ref="popoverContentRef">
  42. <el-input v-model="filterText" placeholder="搜索图标" clearable @input="filterIcons" />
  43. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  44. <el-tab-pane label="SVG 图标" name="svg">
  45. <el-scrollbar height="300px">
  46. <ul class="icon-grid">
  47. <li
  48. v-for="icon in filteredSvgIcons"
  49. :key="'svg-' + icon"
  50. class="icon-grid-item"
  51. @click="selectIcon(icon)"
  52. >
  53. <el-tooltip :content="icon" placement="bottom" effect="light">
  54. <div :class="`i-svg:${icon}`" />
  55. </el-tooltip>
  56. </li>
  57. </ul>
  58. </el-scrollbar>
  59. </el-tab-pane>
  60. <el-tab-pane label="Element 图标" name="element">
  61. <el-scrollbar height="300px">
  62. <ul class="icon-grid">
  63. <li
  64. v-for="icon in filteredElementIcons"
  65. :key="icon"
  66. class="icon-grid-item"
  67. @click="selectIcon(icon)"
  68. >
  69. <el-icon>
  70. <component :is="icon" />
  71. </el-icon>
  72. </li>
  73. </ul>
  74. </el-scrollbar>
  75. </el-tab-pane>
  76. </el-tabs>
  77. </div>
  78. </el-popover>
  79. </div>
  80. </template>
  81. <script setup lang="ts">
  82. import * as ElementPlusIconsVue from "@element-plus/icons-vue";
  83. const props = defineProps({
  84. modelValue: {
  85. type: String,
  86. default: "",
  87. },
  88. width: {
  89. type: String,
  90. default: "500px",
  91. },
  92. });
  93. const emit = defineEmits(["update:modelValue"]);
  94. const iconSelectRef = ref();
  95. const popoverContentRef = ref();
  96. const popoverVisible = ref(false);
  97. const activeTab = ref("svg");
  98. const svgIcons = ref<string[]>([]);
  99. const elementIcons = ref<string[]>(Object.keys(ElementPlusIconsVue));
  100. const selectedIcon = defineModel("modelValue", {
  101. type: String,
  102. required: true,
  103. default: "",
  104. });
  105. const filterText = ref("");
  106. const filteredSvgIcons = ref<string[]>([]);
  107. const filteredElementIcons = ref<string[]>(elementIcons.value);
  108. const isElementIcon = computed(() => {
  109. return selectedIcon.value && selectedIcon.value.startsWith("el-icon");
  110. });
  111. function loadIcons() {
  112. const icons = import.meta.glob("../../assets/icons/*.svg");
  113. for (const path in icons) {
  114. const iconName = path.replace(/.*\/(.*)\.svg$/, "$1");
  115. svgIcons.value.push(iconName);
  116. }
  117. filteredSvgIcons.value = svgIcons.value;
  118. }
  119. function handleTabClick(tabPane: any) {
  120. activeTab.value = tabPane.props.name;
  121. filterIcons();
  122. }
  123. function filterIcons() {
  124. if (activeTab.value === "svg") {
  125. filteredSvgIcons.value = filterText.value
  126. ? svgIcons.value.filter((icon) => icon.toLowerCase().includes(filterText.value.toLowerCase()))
  127. : svgIcons.value;
  128. } else {
  129. filteredElementIcons.value = filterText.value
  130. ? elementIcons.value.filter((icon) =>
  131. icon.toLowerCase().includes(filterText.value.toLowerCase())
  132. )
  133. : elementIcons.value;
  134. }
  135. }
  136. function selectIcon(icon: string) {
  137. const iconName = activeTab.value === "element" ? "el-icon-" + icon : icon;
  138. emit("update:modelValue", iconName);
  139. popoverVisible.value = false;
  140. }
  141. function togglePopover() {
  142. popoverVisible.value = !popoverVisible.value;
  143. }
  144. onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
  145. ignore: [popoverContentRef],
  146. });
  147. /**
  148. * 清空已选图标
  149. */
  150. function clearSelectedIcon() {
  151. selectedIcon.value = "";
  152. }
  153. onMounted(() => {
  154. loadIcons();
  155. if (selectedIcon.value) {
  156. if (elementIcons.value.includes(selectedIcon.value.replace("el-icon-", ""))) {
  157. activeTab.value = "element";
  158. } else {
  159. activeTab.value = "svg";
  160. }
  161. }
  162. });
  163. </script>
  164. <style scoped lang="scss">
  165. .reference :deep(.el-input__wrapper),
  166. .reference :deep(.el-input__inner) {
  167. cursor: pointer;
  168. }
  169. .icon-grid {
  170. display: flex;
  171. flex-wrap: wrap;
  172. }
  173. .icon-grid-item {
  174. display: flex;
  175. align-items: center;
  176. justify-content: center;
  177. padding: 8px;
  178. margin: 4px;
  179. cursor: pointer;
  180. border: 1px solid var(--el-border-color);
  181. border-radius: 4px;
  182. transition: all 0.3s;
  183. }
  184. .icon-grid-item:hover {
  185. border-color: var(--el-color-primary);
  186. transform: scale(1.2);
  187. }
  188. </style>