From 9fa93f3d3d43234bb19c675a192cdc38b350eaaf Mon Sep 17 00:00:00 2001 From: ihuangxiaomin <1219549841@qq.com> Date: Thu, 7 Nov 2024 17:27:52 +0800 Subject: [PATCH 1/3] feat: add icon-picker component --- packages/@core/base/icons/src/index.ts | 7 +- packages/@core/base/icons/src/lucide.ts | 2 + .../src/components/button/icon-button.vue | 8 +- packages/effects/common-ui/package.json | 1 + .../components/icon-picker/icon-picker.vue | 148 ++++ .../src/components/icon-picker/index.ts | 1 + .../effects/common-ui/src/components/index.ts | 1 + packages/effects/hooks/src/index.ts | 1 + packages/effects/hooks/src/use-pagination.ts | 34 + .../demos/features/icons/icon-picker.vue | 82 ++ .../views/demos/features/icons/icons.data.ts | 793 ++++++++++++++++++ .../src/views/demos/features/icons/index.vue | 16 +- pnpm-lock.yaml | 3 + 13 files changed, 1094 insertions(+), 3 deletions(-) create mode 100644 packages/effects/common-ui/src/components/icon-picker/icon-picker.vue create mode 100644 packages/effects/common-ui/src/components/icon-picker/index.ts create mode 100644 packages/effects/hooks/src/use-pagination.ts create mode 100644 playground/src/views/demos/features/icons/icon-picker.vue create mode 100644 playground/src/views/demos/features/icons/icons.data.ts diff --git a/packages/@core/base/icons/src/index.ts b/packages/@core/base/icons/src/index.ts index 1e0fe56446f..85a47d27a2f 100644 --- a/packages/@core/base/icons/src/index.ts +++ b/packages/@core/base/icons/src/index.ts @@ -4,4 +4,9 @@ export * from './create-icon'; export * from './lucide'; export type { IconifyIcon as IconifyIconStructure } from '@iconify/vue'; -export { addCollection, addIcon, Icon as IconifyIcon } from '@iconify/vue'; +export { + addCollection, + addIcon, + Icon as IconifyIcon, + listIcons, +} from '@iconify/vue'; diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index 592bd73c647..152dc5cd205 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -27,6 +27,7 @@ export { FoldHorizontal, Fullscreen, Github, + Grip, Info, InspectionPanel, Languages, @@ -40,6 +41,7 @@ export { Minimize, Minimize2, MoonStar, + Package2, Palette, PanelLeft, PanelRight, diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue b/packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue index 7c123a91dbd..ac626c1b4fa 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/button/icon-button.vue @@ -14,6 +14,7 @@ interface Props extends VbenButtonProps { disabled?: boolean; onClick?: () => void; tooltip?: string; + tooltipDelayDuration?: number; tooltipSide?: 'bottom' | 'left' | 'right' | 'top'; variant?: ButtonVariants; } @@ -21,6 +22,7 @@ interface Props extends VbenButtonProps { const props = withDefaults(defineProps<Props>(), { disabled: false, onClick: () => {}, + tooltipDelayDuration: 200, tooltipSide: 'bottom', variant: 'icon', }); @@ -42,7 +44,11 @@ const showTooltip = computed(() => !!slots.tooltip || !!props.tooltip); <slot></slot> </VbenButton> - <VbenTooltip v-else :side="tooltipSide"> + <VbenTooltip + v-else + :delay-duration="tooltipDelayDuration" + :side="tooltipSide" + > <template #trigger> <VbenButton :class="cn('rounded-full', props.class)" diff --git a/packages/effects/common-ui/package.json b/packages/effects/common-ui/package.json index 9d18a0710e2..aa757d283d4 100644 --- a/packages/effects/common-ui/package.json +++ b/packages/effects/common-ui/package.json @@ -28,6 +28,7 @@ "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", "@vben/types": "workspace:*", + "@vben/hooks": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", "qrcode": "catalog:", diff --git a/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue b/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue new file mode 100644 index 00000000000..6bbbc7559a6 --- /dev/null +++ b/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue @@ -0,0 +1,148 @@ +<script setup lang="ts"> +import { ref, watch, watchEffect } from 'vue'; + +import { usePagination } from '@vben/hooks'; +import { Grip, Package2 } from '@vben/icons'; +import { + Button, + Pagination, + PaginationEllipsis, + PaginationFirst, + PaginationLast, + PaginationList, + PaginationListItem, + PaginationNext, + PaginationPrev, + VbenIcon, + VbenIconButton, + VbenPopover, +} from '@vben-core/shadcn-ui'; + +interface Props { + value?: string; + pageSize?: number; + /** + * 图标列表 + */ + icons?: string[]; +} + +const props = withDefaults(defineProps<Props>(), { + value: '', + pageSize: 36, + icons: () => [], +}); + +const emit = defineEmits<{ + change: [string]; + 'update:value': [string]; +}>(); + +const currentSelect = ref(''); +const currentList = ref(props.icons); +const refTrigger = ref<HTMLDivElement>(); + +const { getPaginationList, getTotal, setCurrentPage } = usePagination( + currentList, + props.pageSize, +); + +watchEffect(() => { + currentSelect.value = props.value; +}); + +watch( + () => currentSelect.value, + (v) => { + emit('update:value', v); + emit('change', v); + }, +); + +const handleClick = (icon: string) => { + currentSelect.value = icon; +}; + +const handlePageChange = (page: number) => { + setCurrentPage(page); +}; + +const changeOpenState = () => { + if (refTrigger.value) { + refTrigger.value.click(); + } +}; + +defineExpose({ changeOpenState }); +</script> +<template> + <VbenPopover + :content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }" + content-class="p-0 py-4" + > + <template #trigger> + <div ref="refTrigger"> + <VbenIcon :icon="currentSelect || Grip" class="size-6" /> + </div> + </template> + + <div v-if="getPaginationList.length > 0"> + <div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center"> + <VbenIconButton + v-for="(item, index) in getPaginationList" + :key="index" + :tooltip="item" + tooltip-side="top" + @click="handleClick(item)" + > + <VbenIcon :icon="item" /> + </VbenIconButton> + </div> + <div v-if="getTotal >= pageSize" class="flex-center pt-1"> + <Pagination + v-slot="{ page }" + :items-per-page="36" + :sibling-count="1" + :total="getTotal" + show-edges + @update:page="handlePageChange" + > + <PaginationList v-slot="{ items }" class="flex items-center gap-1"> + <PaginationFirst class="size-5" /> + <PaginationPrev class="size-5" /> + <template v-for="(item, index) in items"> + <PaginationListItem + v-if="item.type === 'page'" + :key="index" + :value="item.value" + as-child + > + <Button + :variant="item.value === page ? 'default' : 'outline'" + class="size-5 p-0 text-sm" + > + {{ item.value }} + </Button> + </PaginationListItem> + <PaginationEllipsis + v-else + :key="item.type" + :index="index" + class="size-5" + /> + </template> + <PaginationNext class="size-5" /> + <PaginationLast class="size-5" /> + </PaginationList> + </Pagination> + </div> + </div> + + <template v-else> + <div class="flex-col-center text-muted-foreground min-h-[150px] w-full"> + <Package2 /> + <div>{{ $t('common.noData') }}</div> + </div> + </template> + </VbenPopover> +</template> diff --git a/packages/effects/common-ui/src/components/icon-picker/index.ts b/packages/effects/common-ui/src/components/icon-picker/index.ts new file mode 100644 index 00000000000..3dabc86a927 --- /dev/null +++ b/packages/effects/common-ui/src/components/icon-picker/index.ts @@ -0,0 +1 @@ +export { default as IconPicker } from './icon-picker.vue'; diff --git a/packages/effects/common-ui/src/components/index.ts b/packages/effects/common-ui/src/components/index.ts index 12d1caceb73..f5e3c7a9b49 100644 --- a/packages/effects/common-ui/src/components/index.ts +++ b/packages/effects/common-ui/src/components/index.ts @@ -1,5 +1,6 @@ export * from './captcha'; export * from './ellipsis-text'; +export * from './icon-picker'; export * from './page'; export * from '@vben-core/form-ui'; export * from '@vben-core/popup-ui'; diff --git a/packages/effects/hooks/src/index.ts b/packages/effects/hooks/src/index.ts index 865c903e2f4..51f640602fd 100644 --- a/packages/effects/hooks/src/index.ts +++ b/packages/effects/hooks/src/index.ts @@ -1,6 +1,7 @@ export * from './use-app-config'; export * from './use-content-maximize'; export * from './use-design-tokens'; +export * from './use-pagination'; export * from './use-refresh'; export * from './use-tabs'; export * from './use-watermark'; diff --git a/packages/effects/hooks/src/use-pagination.ts b/packages/effects/hooks/src/use-pagination.ts new file mode 100644 index 00000000000..5422e87f268 --- /dev/null +++ b/packages/effects/hooks/src/use-pagination.ts @@ -0,0 +1,34 @@ +import type { Ref } from 'vue'; +import { computed, ref, unref } from 'vue'; + +function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] { + const offset = (pageNo - 1) * Number(pageSize); + const ret = + offset + Number(pageSize) >= list.length + ? list.slice(offset) + : list.slice(offset, offset + Number(pageSize)); + return ret; +} + +export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) { + const currentPage = ref(1); + const pageSizeRef = ref(pageSize); + + const getPaginationList = computed(() => { + return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); + }); + + const getTotal = computed(() => { + return unref(list).length; + }); + + function setCurrentPage(page: number) { + currentPage.value = page; + } + + function setPageSize(pageSize: number) { + pageSizeRef.value = pageSize; + } + + return { setCurrentPage, getTotal, setPageSize, getPaginationList }; +} diff --git a/playground/src/views/demos/features/icons/icon-picker.vue b/playground/src/views/demos/features/icons/icon-picker.vue new file mode 100644 index 00000000000..6e05637f10e --- /dev/null +++ b/playground/src/views/demos/features/icons/icon-picker.vue @@ -0,0 +1,82 @@ +<script lang="ts" setup> +import { ref } from 'vue'; + +import { IconPicker } from '@vben/common-ui'; +import { listIcons } from '@vben/icons'; + +import { Input } from 'ant-design-vue'; + +import iconsData from './icons.data'; + +export interface Props { + allowClear?: boolean; + pageSize?: number; + /** + * 可以通过prefix获取系统中使用的图标集 + */ + prefix?: string; + readonly?: boolean; + value?: string; + width?: string; +} + +// Don't inherit FormItem disabled、placeholder... +defineOptions({ + inheritAttrs: false, +}); + +const props = withDefaults(defineProps<Props>(), { + allowClear: true, + pageSize: 36, + prefix: '', + readonly: false, + value: '', + width: '100%', +}); + +const refIconPicker = ref(); +const currentSelect = ref(''); + +function getIcons() { + if (props.prefix) { + return listIcons('', props.prefix); + } else { + const prefix = iconsData.prefix; + return iconsData.icons.map((icon) => `${prefix}:${icon}`); + } +} + +const currentList = ref(getIcons()); + +const triggerPopover = () => { + if (refIconPicker.value) { + refIconPicker.value.changeOpenState(); + } +}; + +const handleChange = (icon: string) => { + currentSelect.value = icon; +}; +</script> + +<template> + <Input + v-model:value="currentSelect" + :allow-clear="props.allowClear" + :readonly="props.readonly" + :style="{ width }" + class="cursor-pointer" + placeholder="点击选中图标" + @click="triggerPopover" + > + <template #addonAfter> + <IconPicker + ref="refIconPicker" + :icons="currentList" + :page-size="pageSize" + :value="currentSelect" + @change="handleChange" + /> + </template> + </Input> +</template> diff --git a/playground/src/views/demos/features/icons/icons.data.ts b/playground/src/views/demos/features/icons/icons.data.ts new file mode 100644 index 00000000000..833f051979b --- /dev/null +++ b/playground/src/views/demos/features/icons/icons.data.ts @@ -0,0 +1,793 @@ +export default { + icons: [ + 'account-book-filled', + 'account-book-outlined', + 'account-book-twotone', + 'aim-outlined', + 'alert-filled', + 'alert-outlined', + 'alert-twotone', + 'alibaba-outlined', + 'align-center-outlined', + 'align-left-outlined', + 'align-right-outlined', + 'alipay-circle-filled', + 'alipay-circle-outlined', + 'alipay-outlined', + 'alipay-square-filled', + 'aliwangwang-filled', + 'aliwangwang-outlined', + 'aliyun-outlined', + 'amazon-circle-filled', + 'amazon-outlined', + 'amazon-square-filled', + 'android-filled', + 'android-outlined', + 'ant-cloud-outlined', + 'ant-design-outlined', + 'apartment-outlined', + 'api-filled', + 'api-outlined', + 'api-twotone', + 'apple-filled', + 'apple-outlined', + 'appstore-add-outlined', + 'appstore-filled', + 'appstore-outlined', + 'appstore-twotone', + 'area-chart-outlined', + 'arrow-down-outlined', + 'arrow-left-outlined', + 'arrow-right-outlined', + 'arrow-up-outlined', + 'arrows-alt-outlined', + 'audio-filled', + 'audio-muted-outlined', + 'audio-outlined', + 'audio-twotone', + 'audit-outlined', + 'backward-filled', + 'backward-outlined', + 'bank-filled', + 'bank-outlined', + 'bank-twotone', + 'bar-chart-outlined', + 'barcode-outlined', + 'bars-outlined', + 'behance-circle-filled', + 'behance-outlined', + 'behance-square-filled', + 'behance-square-outlined', + 'bell-filled', + 'bell-outlined', + 'bell-twotone', + 'bg-colors-outlined', + 'block-outlined', + 'bold-outlined', + 'book-filled', + 'book-outlined', + 'book-twotone', + 'border-bottom-outlined', + 'border-horizontal-outlined', + 'border-inner-outlined', + 'border-left-outlined', + 'border-outer-outlined', + 'border-outlined', + 'border-right-outlined', + 'border-top-outlined', + 'border-verticle-outlined', + 'borderless-table-outlined', + 'box-plot-filled', + 'box-plot-outlined', + 'box-plot-twotone', + 'branches-outlined', + 'bug-filled', + 'bug-outlined', + 'bug-twotone', + 'build-filled', + 'build-outlined', + 'build-twotone', + 'bulb-filled', + 'bulb-outlined', + 'bulb-twotone', + 'calculator-filled', + 'calculator-outlined', + 'calculator-twotone', + 'calendar-filled', + 'calendar-outlined', + 'calendar-twotone', + 'camera-filled', + 'camera-outlined', + 'camera-twotone', + 'car-filled', + 'car-outlined', + 'car-twotone', + 'caret-down-filled', + 'caret-down-outlined', + 'caret-left-filled', + 'caret-left-outlined', + 'caret-right-filled', + 'caret-right-outlined', + 'caret-up-filled', + 'caret-up-outlined', + 'carry-out-filled', + 'carry-out-outlined', + 'carry-out-twotone', + 'check-circle-filled', + 'check-circle-outlined', + 'check-circle-twotone', + 'check-outlined', + 'check-square-filled', + 'check-square-outlined', + 'check-square-twotone', + 'chrome-filled', + 'chrome-outlined', + 'ci-circle-filled', + 'ci-circle-outlined', + 'ci-circle-twotone', + 'ci-outlined', + 'ci-twotone', + 'clear-outlined', + 'clock-circle-filled', + 'clock-circle-outlined', + 'clock-circle-twotone', + 'close-circle-filled', + 'close-circle-outlined', + 'close-circle-twotone', + 'close-outlined', + 'close-square-filled', + 'close-square-outlined', + 'close-square-twotone', + 'cloud-download-outlined', + 'cloud-filled', + 'cloud-outlined', + 'cloud-server-outlined', + 'cloud-sync-outlined', + 'cloud-twotone', + 'cloud-upload-outlined', + 'cluster-outlined', + 'code-filled', + 'code-outlined', + 'code-sandbox-circle-filled', + 'code-sandbox-outlined', + 'code-sandbox-square-filled', + 'code-twotone', + 'codepen-circle-filled', + 'codepen-circle-outlined', + 'codepen-outlined', + 'codepen-square-filled', + 'coffee-outlined', + 'column-height-outlined', + 'column-width-outlined', + 'comment-outlined', + 'compass-filled', + 'compass-outlined', + 'compass-twotone', + 'compress-outlined', + 'console-sql-outlined', + 'contacts-filled', + 'contacts-outlined', + 'contacts-twotone', + 'container-filled', + 'container-outlined', + 'container-twotone', + 'control-filled', + 'control-outlined', + 'control-twotone', + 'copy-filled', + 'copy-outlined', + 'copy-twotone', + 'copyright-circle-filled', + 'copyright-circle-outlined', + 'copyright-circle-twotone', + 'copyright-outlined', + 'copyright-twotone', + 'credit-card-filled', + 'credit-card-outlined', + 'credit-card-twotone', + 'crown-filled', + 'crown-outlined', + 'crown-twotone', + 'customer-service-filled', + 'customer-service-outlined', + 'customer-service-twotone', + 'dash-outlined', + 'dashboard-filled', + 'dashboard-outlined', + 'dashboard-twotone', + 'database-filled', + 'database-outlined', + 'database-twotone', + 'delete-column-outlined', + 'delete-filled', + 'delete-outlined', + 'delete-row-outlined', + 'delete-twotone', + 'delivered-procedure-outlined', + 'deployment-unit-outlined', + 'desktop-outlined', + 'diff-filled', + 'diff-outlined', + 'diff-twotone', + 'dingding-outlined', + 'dingtalk-circle-filled', + 'dingtalk-outlined', + 'dingtalk-square-filled', + 'disconnect-outlined', + 'dislike-filled', + 'dislike-outlined', + 'dislike-twotone', + 'dollar-circle-filled', + 'dollar-circle-outlined', + 'dollar-circle-twotone', + 'dollar-outlined', + 'dollar-twotone', + 'dot-chart-outlined', + 'double-left-outlined', + 'double-right-outlined', + 'down-circle-filled', + 'down-circle-outlined', + 'down-circle-twotone', + 'down-outlined', + 'down-square-filled', + 'down-square-outlined', + 'down-square-twotone', + 'download-outlined', + 'drag-outlined', + 'dribbble-circle-filled', + 'dribbble-outlined', + 'dribbble-square-filled', + 'dribbble-square-outlined', + 'dropbox-circle-filled', + 'dropbox-outlined', + 'dropbox-square-filled', + 'edit-filled', + 'edit-outlined', + 'edit-twotone', + 'ellipsis-outlined', + 'enter-outlined', + 'environment-filled', + 'environment-outlined', + 'environment-twotone', + 'euro-circle-filled', + 'euro-circle-outlined', + 'euro-circle-twotone', + 'euro-outlined', + 'euro-twotone', + 'exception-outlined', + 'exclamation-circle-filled', + 'exclamation-circle-outlined', + 'exclamation-circle-twotone', + 'exclamation-outlined', + 'expand-alt-outlined', + 'expand-outlined', + 'experiment-filled', + 'experiment-outlined', + 'experiment-twotone', + 'export-outlined', + 'eye-filled', + 'eye-invisible-filled', + 'eye-invisible-outlined', + 'eye-invisible-twotone', + 'eye-outlined', + 'eye-twotone', + 'facebook-filled', + 'facebook-outlined', + 'fall-outlined', + 'fast-backward-filled', + 'fast-backward-outlined', + 'fast-forward-filled', + 'fast-forward-outlined', + 'field-binary-outlined', + 'field-number-outlined', + 'field-string-outlined', + 'field-time-outlined', + 'file-add-filled', + 'file-add-outlined', + 'file-add-twotone', + 'file-done-outlined', + 'file-excel-filled', + 'file-excel-outlined', + 'file-excel-twotone', + 'file-exclamation-filled', + 'file-exclamation-outlined', + 'file-exclamation-twotone', + 'file-filled', + 'file-gif-outlined', + 'file-image-filled', + 'file-image-outlined', + 'file-image-twotone', + 'file-jpg-outlined', + 'file-markdown-filled', + 'file-markdown-outlined', + 'file-markdown-twotone', + 'file-outlined', + 'file-pdf-filled', + 'file-pdf-outlined', + 'file-pdf-twotone', + 'file-ppt-filled', + 'file-ppt-outlined', + 'file-ppt-twotone', + 'file-protect-outlined', + 'file-search-outlined', + 'file-sync-outlined', + 'file-text-filled', + 'file-text-outlined', + 'file-text-twotone', + 'file-twotone', + 'file-unknown-filled', + 'file-unknown-outlined', + 'file-unknown-twotone', + 'file-word-filled', + 'file-word-outlined', + 'file-word-twotone', + 'file-zip-filled', + 'file-zip-outlined', + 'file-zip-twotone', + 'filter-filled', + 'filter-outlined', + 'filter-twotone', + 'fire-filled', + 'fire-outlined', + 'fire-twotone', + 'flag-filled', + 'flag-outlined', + 'flag-twotone', + 'folder-add-filled', + 'folder-add-outlined', + 'folder-add-twotone', + 'folder-filled', + 'folder-open-filled', + 'folder-open-outlined', + 'folder-open-twotone', + 'folder-outlined', + 'folder-twotone', + 'folder-view-outlined', + 'font-colors-outlined', + 'font-size-outlined', + 'fork-outlined', + 'form-outlined', + 'format-painter-filled', + 'format-painter-outlined', + 'forward-filled', + 'forward-outlined', + 'frown-filled', + 'frown-outlined', + 'frown-twotone', + 'fullscreen-exit-outlined', + 'fullscreen-outlined', + 'function-outlined', + 'fund-filled', + 'fund-outlined', + 'fund-projection-screen-outlined', + 'fund-twotone', + 'fund-view-outlined', + 'funnel-plot-filled', + 'funnel-plot-outlined', + 'funnel-plot-twotone', + 'gateway-outlined', + 'gif-outlined', + 'gift-filled', + 'gift-outlined', + 'gift-twotone', + 'github-filled', + 'github-outlined', + 'gitlab-filled', + 'gitlab-outlined', + 'global-outlined', + 'gold-filled', + 'gold-outlined', + 'gold-twotone', + 'golden-filled', + 'google-circle-filled', + 'google-outlined', + 'google-plus-circle-filled', + 'google-plus-outlined', + 'google-plus-square-filled', + 'google-square-filled', + 'group-outlined', + 'hdd-filled', + 'hdd-outlined', + 'hdd-twotone', + 'heart-filled', + 'heart-outlined', + 'heart-twotone', + 'heat-map-outlined', + 'highlight-filled', + 'highlight-outlined', + 'highlight-twotone', + 'history-outlined', + 'home-filled', + 'home-outlined', + 'home-twotone', + 'hourglass-filled', + 'hourglass-outlined', + 'hourglass-twotone', + 'html5-filled', + 'html5-outlined', + 'html5-twotone', + 'idcard-filled', + 'idcard-outlined', + 'idcard-twotone', + 'ie-circle-filled', + 'ie-outlined', + 'ie-square-filled', + 'import-outlined', + 'inbox-outlined', + 'info-circle-filled', + 'info-circle-outlined', + 'info-circle-twotone', + 'info-outlined', + 'insert-row-above-outlined', + 'insert-row-below-outlined', + 'insert-row-left-outlined', + 'insert-row-right-outlined', + 'instagram-filled', + 'instagram-outlined', + 'insurance-filled', + 'insurance-outlined', + 'insurance-twotone', + 'interaction-filled', + 'interaction-outlined', + 'interaction-twotone', + 'issues-close-outlined', + 'italic-outlined', + 'key-outlined', + 'laptop-outlined', + 'layout-filled', + 'layout-outlined', + 'layout-twotone', + 'left-circle-filled', + 'left-circle-outlined', + 'left-circle-twotone', + 'left-outlined', + 'left-square-filled', + 'left-square-outlined', + 'left-square-twotone', + 'like-filled', + 'like-outlined', + 'like-twotone', + 'line-chart-outlined', + 'line-height-outlined', + 'line-outlined', + 'link-outlined', + 'linkedin-filled', + 'linkedin-outlined', + 'loading-3-quarters-outlined', + 'loading-outlined', + 'lock-filled', + 'lock-outlined', + 'lock-twotone', + 'login-outlined', + 'logout-outlined', + 'mac-command-filled', + 'mac-command-outlined', + 'mail-filled', + 'mail-outlined', + 'mail-twotone', + 'man-outlined', + 'medicine-box-filled', + 'medicine-box-outlined', + 'medicine-box-twotone', + 'medium-circle-filled', + 'medium-outlined', + 'medium-square-filled', + 'medium-workmark-outlined', + 'meh-filled', + 'meh-outlined', + 'meh-twotone', + 'menu-fold-outlined', + 'menu-outlined', + 'menu-unfold-outlined', + 'merge-cells-outlined', + 'message-filled', + 'message-outlined', + 'message-twotone', + 'minus-circle-filled', + 'minus-circle-outlined', + 'minus-circle-twotone', + 'minus-outlined', + 'minus-square-filled', + 'minus-square-outlined', + 'minus-square-twotone', + 'mobile-filled', + 'mobile-outlined', + 'mobile-twotone', + 'money-collect-filled', + 'money-collect-outlined', + 'money-collect-twotone', + 'monitor-outlined', + 'more-outlined', + 'node-collapse-outlined', + 'node-expand-outlined', + 'node-index-outlined', + 'notification-filled', + 'notification-outlined', + 'notification-twotone', + 'number-outlined', + 'one-to-one-outlined', + 'ordered-list-outlined', + 'paper-clip-outlined', + 'partition-outlined', + 'pause-circle-filled', + 'pause-circle-outlined', + 'pause-circle-twotone', + 'pause-outlined', + 'pay-circle-filled', + 'pay-circle-outlined', + 'percentage-outlined', + 'phone-filled', + 'phone-outlined', + 'phone-twotone', + 'pic-center-outlined', + 'pic-left-outlined', + 'pic-right-outlined', + 'picture-filled', + 'picture-outlined', + 'picture-twotone', + 'pie-chart-filled', + 'pie-chart-outlined', + 'pie-chart-twotone', + 'play-circle-filled', + 'play-circle-outlined', + 'play-circle-twotone', + 'play-square-filled', + 'play-square-outlined', + 'play-square-twotone', + 'plus-circle-filled', + 'plus-circle-outlined', + 'plus-circle-twotone', + 'plus-outlined', + 'plus-square-filled', + 'plus-square-outlined', + 'plus-square-twotone', + 'pound-circle-filled', + 'pound-circle-outlined', + 'pound-circle-twotone', + 'pound-outlined', + 'poweroff-outlined', + 'printer-filled', + 'printer-outlined', + 'printer-twotone', + 'profile-filled', + 'profile-outlined', + 'profile-twotone', + 'project-filled', + 'project-outlined', + 'project-twotone', + 'property-safety-filled', + 'property-safety-outlined', + 'property-safety-twotone', + 'pull-request-outlined', + 'pushpin-filled', + 'pushpin-outlined', + 'pushpin-twotone', + 'qq-circle-filled', + 'qq-outlined', + 'qq-square-filled', + 'qrcode-outlined', + 'question-circle-filled', + 'question-circle-outlined', + 'question-circle-twotone', + 'question-outlined', + 'radar-chart-outlined', + 'radius-bottomleft-outlined', + 'radius-bottomright-outlined', + 'radius-setting-outlined', + 'radius-upleft-outlined', + 'radius-upright-outlined', + 'read-filled', + 'read-outlined', + 'reconciliation-filled', + 'reconciliation-outlined', + 'reconciliation-twotone', + 'red-envelope-filled', + 'red-envelope-outlined', + 'red-envelope-twotone', + 'reddit-circle-filled', + 'reddit-outlined', + 'reddit-square-filled', + 'redo-outlined', + 'reload-outlined', + 'rest-filled', + 'rest-outlined', + 'rest-twotone', + 'retweet-outlined', + 'right-circle-filled', + 'right-circle-outlined', + 'right-circle-twotone', + 'right-outlined', + 'right-square-filled', + 'right-square-outlined', + 'right-square-twotone', + 'rise-outlined', + 'robot-filled', + 'robot-outlined', + 'rocket-filled', + 'rocket-outlined', + 'rocket-twotone', + 'rollback-outlined', + 'rotate-left-outlined', + 'rotate-right-outlined', + 'safety-certificate-filled', + 'safety-certificate-outlined', + 'safety-certificate-twotone', + 'safety-outlined', + 'save-filled', + 'save-outlined', + 'save-twotone', + 'scan-outlined', + 'schedule-filled', + 'schedule-outlined', + 'schedule-twotone', + 'scissor-outlined', + 'search-outlined', + 'security-scan-filled', + 'security-scan-outlined', + 'security-scan-twotone', + 'select-outlined', + 'send-outlined', + 'setting-filled', + 'setting-outlined', + 'setting-twotone', + 'shake-outlined', + 'share-alt-outlined', + 'shop-filled', + 'shop-outlined', + 'shop-twotone', + 'shopping-cart-outlined', + 'shopping-filled', + 'shopping-outlined', + 'shopping-twotone', + 'shrink-outlined', + 'signal-filled', + 'sisternode-outlined', + 'sketch-circle-filled', + 'sketch-outlined', + 'sketch-square-filled', + 'skin-filled', + 'skin-outlined', + 'skin-twotone', + 'skype-filled', + 'skype-outlined', + 'slack-circle-filled', + 'slack-outlined', + 'slack-square-filled', + 'slack-square-outlined', + 'sliders-filled', + 'sliders-outlined', + 'sliders-twotone', + 'small-dash-outlined', + 'smile-filled', + 'smile-outlined', + 'smile-twotone', + 'snippets-filled', + 'snippets-outlined', + 'snippets-twotone', + 'solution-outlined', + 'sort-ascending-outlined', + 'sort-descending-outlined', + 'sound-filled', + 'sound-outlined', + 'sound-twotone', + 'split-cells-outlined', + 'star-filled', + 'star-outlined', + 'star-twotone', + 'step-backward-filled', + 'step-backward-outlined', + 'step-forward-filled', + 'step-forward-outlined', + 'stock-outlined', + 'stop-filled', + 'stop-outlined', + 'stop-twotone', + 'strikethrough-outlined', + 'subnode-outlined', + 'swap-left-outlined', + 'swap-outlined', + 'swap-right-outlined', + 'switcher-filled', + 'switcher-outlined', + 'switcher-twotone', + 'sync-outlined', + 'table-outlined', + 'tablet-filled', + 'tablet-outlined', + 'tablet-twotone', + 'tag-filled', + 'tag-outlined', + 'tag-twotone', + 'tags-filled', + 'tags-outlined', + 'tags-twotone', + 'taobao-circle-filled', + 'taobao-circle-outlined', + 'taobao-outlined', + 'taobao-square-filled', + 'team-outlined', + 'thunderbolt-filled', + 'thunderbolt-outlined', + 'thunderbolt-twotone', + 'to-top-outlined', + 'tool-filled', + 'tool-outlined', + 'tool-twotone', + 'trademark-circle-filled', + 'trademark-circle-outlined', + 'trademark-circle-twotone', + 'trademark-outlined', + 'transaction-outlined', + 'translation-outlined', + 'trophy-filled', + 'trophy-outlined', + 'trophy-twotone', + 'twitter-circle-filled', + 'twitter-outlined', + 'twitter-square-filled', + 'underline-outlined', + 'undo-outlined', + 'ungroup-outlined', + 'unlock-filled', + 'unlock-outlined', + 'unlock-twotone', + 'unordered-list-outlined', + 'up-circle-filled', + 'up-circle-outlined', + 'up-circle-twotone', + 'up-outlined', + 'up-square-filled', + 'up-square-outlined', + 'up-square-twotone', + 'upload-outlined', + 'usb-filled', + 'usb-outlined', + 'usb-twotone', + 'user-add-outlined', + 'user-delete-outlined', + 'user-outlined', + 'user-switch-outlined', + 'usergroup-add-outlined', + 'usergroup-delete-outlined', + 'verified-outlined', + 'vertical-align-bottom-outlined', + 'vertical-align-middle-outlined', + 'vertical-align-top-outlined', + 'vertical-left-outlined', + 'vertical-right-outlined', + 'video-camera-add-outlined', + 'video-camera-filled', + 'video-camera-outlined', + 'video-camera-twotone', + 'wallet-filled', + 'wallet-outlined', + 'wallet-twotone', + 'warning-filled', + 'warning-outlined', + 'warning-twotone', + 'wechat-filled', + 'wechat-outlined', + 'weibo-circle-filled', + 'weibo-circle-outlined', + 'weibo-outlined', + 'weibo-square-filled', + 'weibo-square-outlined', + 'whats-app-outlined', + 'wifi-outlined', + 'windows-filled', + 'windows-outlined', + 'woman-outlined', + 'yahoo-filled', + 'yahoo-outlined', + 'youtube-filled', + 'youtube-outlined', + 'yuque-filled', + 'yuque-outlined', + 'zhihu-circle-filled', + 'zhihu-outlined', + 'zhihu-square-filled', + 'zoom-in-outlined', + 'zoom-out-outlined', + ], + prefix: 'ant-design', +}; diff --git a/playground/src/views/demos/features/icons/index.vue b/playground/src/views/demos/features/icons/index.vue index 0e5c28b2349..ce958b16df9 100644 --- a/playground/src/views/demos/features/icons/index.vue +++ b/playground/src/views/demos/features/icons/index.vue @@ -17,6 +17,8 @@ import { } from '@vben/icons'; import { Card } from 'ant-design-vue'; + +import IconPicker from './icon-picker.vue'; </script> <template> @@ -45,7 +47,7 @@ import { Card } from 'ant-design-vue'; </div> </Card> - <Card title="Svg Icons"> + <Card class="mb-5" title="Svg Icons"> <div class="flex items-center gap-5"> <SvgAvatar1Icon class="size-8" /> <SvgAvatar2Icon class="size-8 text-red-500" /> @@ -57,5 +59,17 @@ import { Card } from 'ant-design-vue'; <SvgDownloadIcon class="size-8" /> </div> </Card> + + <Card class="mb-5" title="图标选择器(Iconify)"> + <div class="flex items-center gap-5"> + <IconPicker width="300px" /> + </div> + </Card> + + <Card title="图标选择器(Svg)"> + <div class="flex items-center gap-5"> + <IconPicker prefix="svg" width="300px" /> + </div> + </Card> </Page> </template> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d341e8bbddc..c4ee2ec6bee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1472,6 +1472,9 @@ importers: '@vben/constants': specifier: workspace:* version: link:../../constants + '@vben/hooks': + specifier: workspace:* + version: link:../hooks '@vben/icons': specifier: workspace:* version: link:../../icons From 2a631de845e57af2aeb6ede5d1aa2b5a8a0f7819 Mon Sep 17 00:00:00 2001 From: ihuangxiaomin <1219549841@qq.com> Date: Thu, 7 Nov 2024 18:12:59 +0800 Subject: [PATCH 2/3] fix: resolve conversations --- packages/effects/common-ui/package.json | 2 +- .../components/icon-picker/icon-picker.vue | 8 +++++ packages/effects/hooks/src/use-pagination.ts | 27 ++++++++++++++-- .../demos/features/icons/icon-picker.vue | 31 +++++++++++-------- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/effects/common-ui/package.json b/packages/effects/common-ui/package.json index aa757d283d4..766b370e8cc 100644 --- a/packages/effects/common-ui/package.json +++ b/packages/effects/common-ui/package.json @@ -27,8 +27,8 @@ "@vben/constants": "workspace:*", "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", - "@vben/types": "workspace:*", "@vben/hooks": "workspace:*", + "@vben/types": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", "qrcode": "catalog:", diff --git a/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue b/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue index 6bbbc7559a6..2d4f2eb137d 100644 --- a/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue +++ b/packages/effects/common-ui/src/components/icon-picker/icon-picker.vue @@ -42,6 +42,14 @@ const currentSelect = ref(''); const currentList = ref(props.icons); const refTrigger = ref<HTMLDivElement>(); +watch( + () => props.icons, + (newIcons) => { + currentList.value = newIcons; + }, + { immediate: true }, +); + const { getPaginationList, getTotal, setCurrentPage } = usePagination( currentList, props.pageSize, diff --git a/packages/effects/hooks/src/use-pagination.ts b/packages/effects/hooks/src/use-pagination.ts index 5422e87f268..ba3e269333e 100644 --- a/packages/effects/hooks/src/use-pagination.ts +++ b/packages/effects/hooks/src/use-pagination.ts @@ -1,12 +1,23 @@ import type { Ref } from 'vue'; import { computed, ref, unref } from 'vue'; +/** + * Paginates an array of items + * @param list The array to paginate + * @param pageNo The current page number (1-based) + * @param pageSize Number of items per page + * @returns Paginated array slice + * @throws {Error} If pageNo or pageSize are invalid + */ function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] { + if (pageNo < 1) throw new Error('Page number must be positive'); + if (pageSize < 1) throw new Error('Page size must be positive'); + const offset = (pageNo - 1) * Number(pageSize); const ret = - offset + Number(pageSize) >= list.length + offset + pageSize >= list.length ? list.slice(offset) - : list.slice(offset, offset + Number(pageSize)); + : list.slice(offset, offset + pageSize); return ret; } @@ -14,6 +25,10 @@ export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) { const currentPage = ref(1); const pageSizeRef = ref(pageSize); + const totalPages = computed(() => + Math.ceil(unref(list).length / unref(pageSizeRef)), + ); + const getPaginationList = computed(() => { return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); }); @@ -23,11 +38,19 @@ export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) { }); function setCurrentPage(page: number) { + if (page < 1 || page > unref(totalPages)) { + throw new Error('Invalid page number'); + } currentPage.value = page; } function setPageSize(pageSize: number) { + if (pageSize < 1) { + throw new Error('Page size must be positive'); + } pageSizeRef.value = pageSize; + // Reset to first page to prevent invalid state + currentPage.value = 1; } return { setCurrentPage, getTotal, setPageSize, getPaginationList }; diff --git a/playground/src/views/demos/features/icons/icon-picker.vue b/playground/src/views/demos/features/icons/icon-picker.vue index 6e05637f10e..699ebec6733 100644 --- a/playground/src/views/demos/features/icons/icon-picker.vue +++ b/playground/src/views/demos/features/icons/icon-picker.vue @@ -1,5 +1,5 @@ <script lang="ts" setup> -import { ref } from 'vue'; +import { computed, ref } from 'vue'; import { IconPicker } from '@vben/common-ui'; import { listIcons } from '@vben/icons'; @@ -37,21 +37,26 @@ const props = withDefaults(defineProps<Props>(), { const refIconPicker = ref(); const currentSelect = ref(''); -function getIcons() { - if (props.prefix) { - return listIcons('', props.prefix); - } else { - const prefix = iconsData.prefix; - return iconsData.icons.map((icon) => `${prefix}:${icon}`); +const currentList = computed(() => { + try { + if (props.prefix) { + const icons = listIcons('', props.prefix); + if (icons.length === 0) { + console.warn(`No icons found for prefix: ${props.prefix}`); + } + return icons; + } else { + const prefix = iconsData.prefix; + return iconsData.icons.map((icon) => `${prefix}:${icon}`); + } + } catch (error) { + console.error('Failed to load icons:', error); + return []; } -} - -const currentList = ref(getIcons()); +}); const triggerPopover = () => { - if (refIconPicker.value) { - refIconPicker.value.changeOpenState(); - } + refIconPicker.value?.changeOpenState?.(); }; const handleChange = (icon: string) => { From 32df443ad8eceb9952b16e7dbce060a010cd6938 Mon Sep 17 00:00:00 2001 From: ihuangxiaomin <1219549841@qq.com> Date: Thu, 7 Nov 2024 18:19:29 +0800 Subject: [PATCH 3/3] refactor: resort @vben/hooks --- packages/effects/common-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/effects/common-ui/package.json b/packages/effects/common-ui/package.json index 766b370e8cc..d741cbbcd1e 100644 --- a/packages/effects/common-ui/package.json +++ b/packages/effects/common-ui/package.json @@ -25,9 +25,9 @@ "@vben-core/shadcn-ui": "workspace:*", "@vben-core/shared": "workspace:*", "@vben/constants": "workspace:*", + "@vben/hooks": "workspace:*", "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", - "@vben/hooks": "workspace:*", "@vben/types": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:",