| | |
| | | <template> |
| | | <a-input |
| | | disabled |
| | | <Input |
| | | :style="{ width }" |
| | | :placeholder="t('component.icon.placeholder')" |
| | | :class="prefixCls" |
| | | v-model:value="currentSelect" |
| | | @click="triggerPopover" |
| | | :allowClear="props.allowClear" |
| | | :readonly="props.readonly" |
| | | > |
| | | <template #addonAfter> |
| | | <a-popover |
| | | <Popover |
| | | placement="bottomLeft" |
| | | trigger="click" |
| | | v-model="visible" |
| | |
| | | > |
| | | <template #title> |
| | | <div class="flex justify-between"> |
| | | <a-input |
| | | <Input |
| | | :placeholder="t('component.icon.search')" |
| | | @change="debounceHandleSearchChange" |
| | | allowClear |
| | |
| | | @click="handleClick(icon)" |
| | | :title="icon" |
| | | > |
| | | <!-- <Icon :icon="icon" :prefix="prefix" /> --> |
| | | <SvgIcon v-if="isSvgMode" :name="icon" /> |
| | | <Icon :icon="icon" v-else /> |
| | | </li> |
| | | </ul> |
| | | </ScrollContainer> |
| | | <div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize"> |
| | | <a-pagination |
| | | <Pagination |
| | | showLessItems |
| | | size="small" |
| | | :pageSize="pageSize" |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | <template v-else |
| | | ><div class="p-5"><a-empty /></div> |
| | | <template v-else> |
| | | <div class="p-5"><Empty /> </div> |
| | | </template> |
| | | </template> |
| | | |
| | | <span class="cursor-pointer px-2 py-1 flex items-center" v-if="isSvgMode && currentSelect"> |
| | | <SvgIcon :name="currentSelect" /> |
| | | </span> |
| | | <Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else /> |
| | | </a-popover> |
| | | <div ref="trigger"> |
| | | <span |
| | | class="cursor-pointer px-2 py-1 flex items-center" |
| | | v-if="isSvgMode && currentSelect" |
| | | > |
| | | <SvgIcon :name="currentSelect" /> |
| | | </span> |
| | | <Icon |
| | | :icon="currentSelect || 'ion:apps-outline'" |
| | | class="cursor-pointer px-2 py-1" |
| | | v-else |
| | | /> |
| | | </div> |
| | | </Popover> |
| | | </template> |
| | | </a-input> |
| | | </Input> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import { ref, watchEffect, watch, unref } from 'vue'; |
| | | import { useDesign } from '/@/hooks/web/useDesign'; |
| | | import { ScrollContainer } from '/@/components/Container'; |
| | | import { ref, watchEffect, watch } from 'vue'; |
| | | import { useDesign } from '@/hooks/web/useDesign'; |
| | | import { ScrollContainer } from '@/components/Container'; |
| | | import { Input, Popover, Pagination, Empty } from 'ant-design-vue'; |
| | | import Icon from '../Icon.vue'; |
| | | import SvgIcon from './SvgIcon.vue'; |
| | | |
| | | import iconsData from '../data/icons.data'; |
| | | import { propTypes } from '/@/utils/propTypes'; |
| | | import { usePagination } from '/@/hooks/web/usePagination'; |
| | | import { usePagination } from '@/hooks/web/usePagination'; |
| | | import { useDebounceFn } from '@vueuse/core'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | import { useI18n } from '@/hooks/web/useI18n'; |
| | | import svgIcons from 'virtual:svg-icons-names'; |
| | | |
| | | // 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告 |
| | | const AInput = Input; |
| | | const APopover = Popover; |
| | | const APagination = Pagination; |
| | | const AEmpty = Empty; |
| | | import { copyText } from '@/utils/copyTextToClipboard'; |
| | | |
| | | function getIcons() { |
| | | const data = iconsData as any; |
| | | const prefix: string = data?.prefix ?? ''; |
| | | let result: string[] = []; |
| | | if (prefix) { |
| | | result = (data?.icons ?? []).map((item) => `${prefix}:${item}`); |
| | | } else if (Array.isArray(iconsData)) { |
| | | result = iconsData as string[]; |
| | | } |
| | | return result; |
| | | const prefix = iconsData.prefix; |
| | | return iconsData.icons.map((icon) => `${prefix}:${icon}`); |
| | | } |
| | | |
| | | function getSvgIcons() { |
| | | return svgIcons.map((icon) => icon.replace('icon-', '')); |
| | | return svgIcons.map((icon: string) => icon.replace('icon-', '')); |
| | | } |
| | | |
| | | const props = defineProps({ |
| | | value: propTypes.string, |
| | | width: propTypes.string.def('100%'), |
| | | pageSize: propTypes.number.def(140), |
| | | copy: propTypes.bool.def(false), |
| | | mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'), |
| | | export interface Props { |
| | | value?: string; |
| | | width?: string; |
| | | pageSize?: number; |
| | | copy?: boolean; |
| | | mode?: 'svg' | 'iconify'; |
| | | allowClear?: boolean; |
| | | readonly?: boolean; |
| | | } |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | value: '', |
| | | width: '100%', |
| | | pageSize: 140, |
| | | copy: false, |
| | | mode: 'iconify', |
| | | allowClear: true, |
| | | readonly: false, |
| | | }); |
| | | |
| | | // Don't inherit FormItem disabled、placeholder... |
| | | defineOptions({ |
| | | inheritAttrs: false, |
| | | }); |
| | | |
| | | const emit = defineEmits(['change', 'update:value']); |
| | |
| | | const currentSelect = ref(''); |
| | | const visible = ref(false); |
| | | const currentList = ref(icons); |
| | | const trigger = ref<HTMLDivElement>(); |
| | | |
| | | const triggerPopover = () => { |
| | | if (trigger.value) { |
| | | trigger.value.click(); |
| | | } |
| | | }; |
| | | |
| | | const { t } = useI18n(); |
| | | const { prefixCls } = useDesign('icon-picker'); |
| | | |
| | | const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100); |
| | | |
| | | let clipboardRef; |
| | | let isSuccessRef; |
| | | |
| | | if (props.copy) { |
| | | const clipboard = useCopyToClipboard(props.value); |
| | | clipboardRef = clipboard?.clipboardRef; |
| | | isSuccessRef = clipboard?.isSuccessRef; |
| | | } |
| | | |
| | | const { createMessage } = useMessage(); |
| | | |
| | | const { getPaginationList, getTotal, setCurrentPage } = usePagination( |
| | | currentList, |
| | |
| | | () => currentSelect.value, |
| | | (v) => { |
| | | emit('update:value', v); |
| | | return emit('change', v); |
| | | emit('change', v); |
| | | }, |
| | | ); |
| | | |
| | | function handlePageChange(page: number) { |
| | | setCurrentPage(page); |
| | | } |
| | |
| | | function handleClick(icon: string) { |
| | | currentSelect.value = icon; |
| | | if (props.copy) { |
| | | clipboardRef.value = icon; |
| | | if (unref(isSuccessRef)) { |
| | | createMessage.success(t('component.icon.copy')); |
| | | } |
| | | copyText(icon, t('component.icon.copy')); |
| | | } |
| | | } |
| | | |
| | | function handleSearchChange(e: Event) { |
| | | const value = e.target.value; |
| | | const value = (e.target as HTMLInputElement).value; |
| | | |
| | | if (!value) { |
| | | setCurrentPage(1); |
| | | currentList.value = icons; |
| | |
| | | padding: 0; |
| | | } |
| | | |
| | | .ant-input { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | &-popover { |
| | | width: 300px; |
| | | |