index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <template>
  2. <div class="icon-selector w100 h100">
  3. <el-input v-model="state.fontIconSearch" :placeholder="state.fontIconPlaceholder" :clearable="clearable"
  4. :disabled="disabled" :size="size" ref="inputWidthRef" @clear="onClearFontIcon" @focus="onIconFocus"
  5. @blur="onIconBlur">
  6. <template #prepend>
  7. <SvgIcon :name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"
  8. v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1" />
  9. <i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
  10. </template>
  11. </el-input>
  12. <el-popover placement="bottom" :width="state.fontIconWidth" transition="el-zoom-in-top"
  13. popper-class="icon-selector-popper" trigger="click" :virtual-ref="inputWidthRef" virtual-triggering>
  14. <template #default>
  15. <div class="icon-selector-warp">
  16. <div class="icon-selector-warp-title">{{ title }}</div>
  17. <el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
  18. <el-tab-pane lazy label="ali" name="ali">
  19. <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription"
  20. :prefix="state.fontIconPrefix" @get-icon="onColClick" />
  21. </el-tab-pane>
  22. <el-tab-pane lazy label="ele" name="ele">
  23. <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription"
  24. :prefix="state.fontIconPrefix" @get-icon="onColClick" />
  25. </el-tab-pane>
  26. <!-- <el-tab-pane lazy label="awe" name="awe">
  27. <IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
  28. </el-tab-pane> -->
  29. </el-tabs>
  30. </div>
  31. </template>
  32. </el-popover>
  33. </div>
  34. </template>
  35. <script setup lang="ts" name="iconSelector">
  36. import {defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch, PropType, onBeforeUnmount} from 'vue';
  37. import type { TabsPaneContext } from 'element-plus';
  38. import initIconfont from '@/utils/getStyleSheets';
  39. import '@/theme/iconSelector.scss';
  40. type inputSize = "default" | "small" | "large";
  41. // 定义父组件传过来的值
  42. const props = defineProps({
  43. // 输入框前置内容
  44. prepend: {
  45. type: String,
  46. default: () => 'ele-Pointer',
  47. },
  48. // 输入框占位文本
  49. placeholder: {
  50. type: String,
  51. default: () => '请输入内容搜索图标或者选择图标',
  52. },
  53. // 输入框占位文本
  54. size: {
  55. type:String as unknown as PropType<inputSize>,
  56. default: () => 'default',
  57. },
  58. // 弹窗标题
  59. title: {
  60. type: String,
  61. default: () => '请选择图标',
  62. },
  63. // 禁用
  64. disabled: {
  65. type: Boolean,
  66. default: () => false,
  67. },
  68. // 是否可清空
  69. clearable: {
  70. type: Boolean,
  71. default: () => true,
  72. },
  73. // 自定义空状态描述文字
  74. emptyDescription: {
  75. type: String,
  76. default: () => '无相关图标',
  77. },
  78. // 双向绑定值,默认为 modelValue,
  79. // 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
  80. // 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
  81. modelValue: String,
  82. });
  83. // 定义子组件向父组件传值/事件
  84. const emit = defineEmits(['update:modelValue', 'get', 'clear']);
  85. // 引入组件
  86. const IconList = defineAsyncComponent(() => import('@/components/IconSelector/list.vue'));
  87. // 定义变量内容
  88. const inputWidthRef = ref<RefType>();
  89. const state = reactive({
  90. fontIconPrefix: '',
  91. fontIconWidth: 0,
  92. fontIconSearch: '',
  93. fontIconPlaceholder: '',
  94. fontIconTabActive: 'ali',
  95. fontIconList: {
  96. ali: [],
  97. ele: [],
  98. awe: [],
  99. },
  100. });
  101. // 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
  102. const onIconFocus = () => {
  103. if (!props.modelValue) return false;
  104. state.fontIconSearch = '';
  105. state.fontIconPlaceholder = props.modelValue;
  106. };
  107. // 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
  108. const onIconBlur = () => {
  109. const list = fontIconTabNameList();
  110. setTimeout(() => {
  111. const icon = list.filter((icon: string) => icon === state.fontIconSearch);
  112. if (icon.length <= 0) state.fontIconSearch = '';
  113. }, 300);
  114. };
  115. // 图标搜索及图标数据显示
  116. const fontIconSheetsFilterList = computed(() => {
  117. const list = fontIconTabNameList();
  118. if (!state.fontIconSearch) return list;
  119. let search = state.fontIconSearch.trim().toLowerCase();
  120. return list.filter((item: string) => {
  121. if (item.toLowerCase().indexOf(search) !== -1) return item;
  122. });
  123. });
  124. // 根据 tab name 类型设置图标
  125. const fontIconTabNameList = () => {
  126. let iconList: any = [];
  127. if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
  128. else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
  129. else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
  130. return iconList;
  131. };
  132. // 处理 icon 双向绑定数值回显
  133. const initModeValueEcho = () => {
  134. if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
  135. (<string | undefined>state.fontIconPlaceholder) = props.modelValue;
  136. (<string | undefined>state.fontIconPrefix) = props.modelValue;
  137. };
  138. // 处理 icon 类型,用于回显时,tab 高亮与初始化数据
  139. const initFontIconName = () => {
  140. let name = 'ali';
  141. if (props.modelValue) {
  142. if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
  143. else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
  144. else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
  145. }
  146. // 初始化 tab 高亮回显
  147. state.fontIconTabActive = name;
  148. return name;
  149. };
  150. // 初始化数据
  151. const initFontIconData = async (name: string) => {
  152. if (name === 'ali') {
  153. // 阿里字体图标使用 `iconfont xxx`
  154. if (state.fontIconList.ali.length > 0) return;
  155. await initIconfont.ali().then((res: any) => {
  156. state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
  157. });
  158. } else if (name === 'ele') {
  159. // element plus 图标
  160. if (state.fontIconList.ele.length > 0) return;
  161. await initIconfont.ele().then((res: any) => {
  162. state.fontIconList.ele = res;
  163. });
  164. }
  165. // 初始化 input 的 placeholder
  166. // 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
  167. state.fontIconPlaceholder = props.placeholder;
  168. // 初始化双向绑定回显
  169. initModeValueEcho();
  170. };
  171. // 图标点击切换
  172. const onIconClick = (pane: TabsPaneContext) => {
  173. initFontIconData(pane.paneName as string);
  174. inputWidthRef.value.focus();
  175. };
  176. // 获取当前点击的 icon 图标
  177. const onColClick = (v: string) => {
  178. state.fontIconPlaceholder = v;
  179. state.fontIconPrefix = v;
  180. emit('get', state.fontIconPrefix);
  181. emit('update:modelValue', state.fontIconPrefix);
  182. inputWidthRef.value.focus();
  183. };
  184. // 清空当前点击的 icon 图标
  185. const onClearFontIcon = () => {
  186. state.fontIconPrefix = '';
  187. emit('clear', state.fontIconPrefix);
  188. emit('update:modelValue', state.fontIconPrefix);
  189. };
  190. // 获取 input 的宽度
  191. const getInputWidth = () => {
  192. nextTick(() => {
  193. state.fontIconWidth = inputWidthRef.value?.$el?.offsetWidth;
  194. });
  195. };
  196. // 监听页面宽度改变
  197. const initResize = () => {
  198. window.addEventListener('resize', () => {
  199. getInputWidth();
  200. });
  201. };
  202. // 页面加载时
  203. onMounted(() => {
  204. initFontIconData(initFontIconName());
  205. initResize();
  206. getInputWidth();
  207. });
  208. onBeforeUnmount(() => {
  209. window.removeEventListener('resize', () => {getInputWidth();},true);
  210. });
  211. // 监听双向绑定 modelValue 的变化
  212. watch(
  213. () => props.modelValue,
  214. () => {
  215. initModeValueEcho();
  216. initFontIconName();
  217. }
  218. );
  219. </script>