2024-11-09 14:10:17 +08:00
|
|
|
<script setup lang="ts">
|
2024-12-04 21:42:21 +08:00
|
|
|
import { computed, ref, watch, watchEffect } from 'vue';
|
2024-11-09 14:10:17 +08:00
|
|
|
|
|
|
|
import { usePagination } from '@vben/hooks';
|
2024-12-04 21:42:21 +08:00
|
|
|
import { EmptyIcon, Grip, listIcons } from '@vben/icons';
|
|
|
|
import { $t } from '@vben/locales';
|
2024-11-09 14:10:17 +08:00
|
|
|
import {
|
|
|
|
Button,
|
2024-12-04 21:42:21 +08:00
|
|
|
Input,
|
2024-11-09 14:10:17 +08:00
|
|
|
Pagination,
|
|
|
|
PaginationEllipsis,
|
|
|
|
PaginationFirst,
|
|
|
|
PaginationLast,
|
|
|
|
PaginationList,
|
|
|
|
PaginationListItem,
|
|
|
|
PaginationNext,
|
|
|
|
PaginationPrev,
|
|
|
|
VbenIcon,
|
|
|
|
VbenIconButton,
|
|
|
|
VbenPopover,
|
|
|
|
} from '@vben-core/shadcn-ui';
|
|
|
|
|
2024-12-04 21:42:21 +08:00
|
|
|
import { refDebounced } from '@vueuse/core';
|
|
|
|
|
2024-11-09 14:10:17 +08:00
|
|
|
interface Props {
|
|
|
|
pageSize?: number;
|
2024-12-04 21:42:21 +08:00
|
|
|
prefix?: string;
|
2024-11-09 14:10:17 +08:00
|
|
|
/**
|
|
|
|
* 图标列表
|
|
|
|
*/
|
|
|
|
icons?: string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
2024-12-04 21:42:21 +08:00
|
|
|
prefix: 'ant-design',
|
2024-11-09 14:10:17 +08:00
|
|
|
pageSize: 36,
|
|
|
|
icons: () => [],
|
|
|
|
});
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
change: [string];
|
|
|
|
}>();
|
|
|
|
|
2024-12-04 21:42:21 +08:00
|
|
|
const modelValue = defineModel({ default: '', type: String });
|
|
|
|
|
|
|
|
const visible = ref(false);
|
2024-11-09 14:10:17 +08:00
|
|
|
const currentSelect = ref('');
|
2024-11-14 22:11:32 +08:00
|
|
|
const currentPage = ref(1);
|
2024-12-04 21:42:21 +08:00
|
|
|
const keyword = ref('');
|
|
|
|
const keywordDebounce = refDebounced(keyword, 300);
|
|
|
|
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 {
|
|
|
|
return props.icons;
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to load icons:', error);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
});
|
2024-11-09 14:10:17 +08:00
|
|
|
|
2024-12-04 21:42:21 +08:00
|
|
|
const showList = computed(() => {
|
|
|
|
return currentList.value.filter((item) =>
|
|
|
|
item.includes(keywordDebounce.value),
|
|
|
|
);
|
|
|
|
});
|
2024-11-09 14:10:17 +08:00
|
|
|
|
2024-11-09 15:53:17 +08:00
|
|
|
const { paginationList, total, setCurrentPage } = usePagination(
|
2024-12-04 21:42:21 +08:00
|
|
|
showList,
|
2024-11-09 14:10:17 +08:00
|
|
|
props.pageSize,
|
|
|
|
);
|
|
|
|
|
|
|
|
watchEffect(() => {
|
2024-12-04 21:42:21 +08:00
|
|
|
currentSelect.value = modelValue.value;
|
2024-11-09 14:10:17 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => currentSelect.value,
|
|
|
|
(v) => {
|
|
|
|
emit('change', v);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const handleClick = (icon: string) => {
|
|
|
|
currentSelect.value = icon;
|
2024-12-04 21:42:21 +08:00
|
|
|
modelValue.value = icon;
|
|
|
|
close();
|
2024-11-09 14:10:17 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const handlePageChange = (page: number) => {
|
2024-11-14 22:11:32 +08:00
|
|
|
currentPage.value = page;
|
2024-11-09 14:10:17 +08:00
|
|
|
setCurrentPage(page);
|
|
|
|
};
|
|
|
|
|
2024-12-04 21:42:21 +08:00
|
|
|
function toggleOpenState() {
|
|
|
|
visible.value = !visible.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function open() {
|
|
|
|
visible.value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function close() {
|
|
|
|
visible.value = false;
|
2024-11-09 15:53:17 +08:00
|
|
|
}
|
2024-11-09 14:10:17 +08:00
|
|
|
|
2024-12-04 21:42:21 +08:00
|
|
|
defineExpose({ toggleOpenState, open, close });
|
2024-11-09 14:10:17 +08:00
|
|
|
</script>
|
|
|
|
<template>
|
|
|
|
<VbenPopover
|
2024-12-04 21:42:21 +08:00
|
|
|
v-model:open="visible"
|
2024-11-09 14:10:17 +08:00
|
|
|
:content-props="{ align: 'end', alignOffset: -11, sideOffset: 8 }"
|
2024-11-09 15:53:17 +08:00
|
|
|
content-class="p-0 pt-3"
|
2024-11-09 14:10:17 +08:00
|
|
|
>
|
|
|
|
<template #trigger>
|
2024-12-04 21:42:21 +08:00
|
|
|
<slot :close="close" :icon="currentSelect" :open="open" name="trigger">
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
<Input
|
|
|
|
:value="currentSelect"
|
|
|
|
class="flex-1 cursor-pointer"
|
|
|
|
v-bind="$attrs"
|
|
|
|
:placeholder="$t('ui.iconPicker.placeholder')"
|
|
|
|
/>
|
|
|
|
<VbenIcon :icon="currentSelect || Grip" class="size-8" />
|
|
|
|
</div>
|
|
|
|
</slot>
|
2024-11-09 14:10:17 +08:00
|
|
|
</template>
|
2024-12-04 21:42:21 +08:00
|
|
|
<div class="mb-2 flex w-full">
|
|
|
|
<Input
|
|
|
|
v-model="keyword"
|
|
|
|
:placeholder="$t('ui.iconPicker.search')"
|
|
|
|
class="mx-2"
|
|
|
|
/>
|
|
|
|
</div>
|
2024-11-09 14:10:17 +08:00
|
|
|
|
2024-11-09 15:53:17 +08:00
|
|
|
<template v-if="paginationList.length > 0">
|
2024-11-09 14:10:17 +08:00
|
|
|
<div class="grid max-h-[360px] w-full grid-cols-6 justify-items-center">
|
|
|
|
<VbenIconButton
|
2024-11-09 15:53:17 +08:00
|
|
|
v-for="(item, index) in paginationList"
|
2024-11-09 14:10:17 +08:00
|
|
|
:key="index"
|
|
|
|
:tooltip="item"
|
|
|
|
tooltip-side="top"
|
|
|
|
@click="handleClick(item)"
|
|
|
|
>
|
2024-11-09 15:53:17 +08:00
|
|
|
<VbenIcon
|
|
|
|
:class="{
|
|
|
|
'text-primary transition-all': currentSelect === item,
|
|
|
|
}"
|
|
|
|
:icon="item"
|
|
|
|
/>
|
2024-11-09 14:10:17 +08:00
|
|
|
</VbenIconButton>
|
|
|
|
</div>
|
2024-11-09 15:53:17 +08:00
|
|
|
<div
|
|
|
|
v-if="total >= pageSize"
|
|
|
|
class="flex-center flex justify-end overflow-hidden border-t py-2 pr-3"
|
|
|
|
>
|
2024-11-09 14:10:17 +08:00
|
|
|
<Pagination
|
|
|
|
:items-per-page="36"
|
|
|
|
:sibling-count="1"
|
2024-11-09 15:53:17 +08:00
|
|
|
:total="total"
|
2024-11-09 14:10:17 +08:00
|
|
|
show-edges
|
2024-11-09 15:53:17 +08:00
|
|
|
size="small"
|
2024-11-09 14:10:17 +08:00
|
|
|
@update:page="handlePageChange"
|
|
|
|
>
|
2024-11-09 15:53:17 +08:00
|
|
|
<PaginationList
|
|
|
|
v-slot="{ items }"
|
|
|
|
class="flex w-full items-center gap-1"
|
|
|
|
>
|
2024-11-09 14:10:17 +08:00
|
|
|
<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
|
2024-11-14 22:11:32 +08:00
|
|
|
:variant="item.value === currentPage ? 'default' : 'outline'"
|
2024-11-09 14:10:17 +08:00
|
|
|
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>
|
2024-11-09 15:53:17 +08:00
|
|
|
</template>
|
2024-11-09 14:10:17 +08:00
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
<div class="flex-col-center text-muted-foreground min-h-[150px] w-full">
|
2024-11-09 15:53:17 +08:00
|
|
|
<EmptyIcon class="size-10" />
|
|
|
|
<div class="mt-1 text-sm">{{ $t('common.noData') }}</div>
|
2024-11-09 14:10:17 +08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</VbenPopover>
|
|
|
|
</template>
|