Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/typings",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/composables",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/preferences",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/form-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/layout-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/menu-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shadcn-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"#main": "./dist/index.mjs",
|
||||
"#module": "./dist/index.mjs",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/tabs-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/constants",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/access",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/common-ui",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { VNode } from 'vue';
|
||||
|
||||
import { computed, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
import { usePagination } from '@vben/hooks';
|
||||
import { EmptyIcon, Grip, listIcons } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Pagination,
|
||||
PaginationEllipsis,
|
||||
PaginationFirst,
|
||||
@@ -18,12 +22,17 @@ import {
|
||||
VbenIconButton,
|
||||
VbenPopover,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { refDebounced } from '@vueuse/core';
|
||||
import { computed, h, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
import { refDebounced, watchDebounced } from '@vueuse/core';
|
||||
|
||||
import { fetchIconsData } from './icons';
|
||||
|
||||
interface Props {
|
||||
pageSize?: number;
|
||||
/** 图标集的名字 */
|
||||
prefix?: string;
|
||||
/** 是否自动请求API以获得图标集的数据.提供prefix时有效 */
|
||||
autoFetchApi?: boolean;
|
||||
/**
|
||||
* 图标列表
|
||||
*/
|
||||
@@ -36,16 +45,19 @@ interface Props {
|
||||
modelValueProp?: string;
|
||||
/** 图标样式 */
|
||||
iconClass?: string;
|
||||
type?: 'icon' | 'input';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
prefix: 'ant-design',
|
||||
pageSize: 36,
|
||||
icons: () => [],
|
||||
inputComponent: () => h('div'),
|
||||
iconSlot: 'default',
|
||||
iconClass: 'size-4',
|
||||
modelValueProp: 'value',
|
||||
autoFetchApi: true,
|
||||
modelValueProp: 'modelValue',
|
||||
inputComponent: undefined,
|
||||
type: 'input',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -59,9 +71,28 @@ const currentSelect = ref('');
|
||||
const currentPage = ref(1);
|
||||
const keyword = ref('');
|
||||
const keywordDebounce = refDebounced(keyword, 300);
|
||||
const innerIcons = ref<string[]>([]);
|
||||
|
||||
watchDebounced(
|
||||
() => props.prefix,
|
||||
async (prefix) => {
|
||||
if (prefix && prefix !== 'svg' && props.autoFetchApi) {
|
||||
innerIcons.value = await fetchIconsData(prefix);
|
||||
}
|
||||
},
|
||||
{ immediate: true, debounce: 500, maxWait: 1000 },
|
||||
);
|
||||
|
||||
const currentList = computed(() => {
|
||||
try {
|
||||
if (props.prefix) {
|
||||
if (
|
||||
props.prefix !== 'svg' &&
|
||||
props.autoFetchApi &&
|
||||
props.icons.length === 0
|
||||
) {
|
||||
return innerIcons.value;
|
||||
}
|
||||
const icons = listIcons('', props.prefix);
|
||||
if (icons.length === 0) {
|
||||
console.warn(`No icons found for prefix: ${props.prefix}`);
|
||||
@@ -143,18 +174,61 @@ defineExpose({ toggleOpenState, open, close });
|
||||
content-class="p-0 pt-3"
|
||||
>
|
||||
<template #trigger>
|
||||
<component
|
||||
:is="inputComponent"
|
||||
:[modelValueProp]="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
>
|
||||
<template #[iconSlot]>
|
||||
<VbenIcon :icon="currentSelect || Grip" class="size-4" />
|
||||
</template>
|
||||
</component>
|
||||
<template v-if="props.type === 'input'">
|
||||
<component
|
||||
v-if="props.inputComponent"
|
||||
:is="inputComponent"
|
||||
:[modelValueProp]="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
aria-expanded="visible"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #[iconSlot]>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
class="size-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
<div class="relative w-full" v-else>
|
||||
<Input
|
||||
v-bind="$attrs"
|
||||
v-model="currentSelect"
|
||||
:placeholder="$t('ui.iconPicker.placeholder')"
|
||||
class="h-8 w-full pr-8"
|
||||
role="combobox"
|
||||
:aria-label="$t('ui.iconPicker.placeholder')"
|
||||
aria-expanded="visible"
|
||||
/>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
class="absolute right-1 top-1 size-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<VbenIcon
|
||||
:icon="currentSelect || Grip"
|
||||
v-else
|
||||
class="size-4"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</template>
|
||||
<div class="mb-2 flex w-full">
|
||||
<component :is="inputComponent" v-bind="searchInputProps" />
|
||||
<component
|
||||
v-if="inputComponent"
|
||||
:is="inputComponent"
|
||||
v-bind="searchInputProps"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
class="mx-2 h-8 w-full"
|
||||
:placeholder="$t('ui.iconPicker.search')"
|
||||
v-model="keyword"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-if="paginationList.length > 0">
|
||||
|
@@ -0,0 +1,56 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
/**
|
||||
* 一个缓存对象,在不刷新页面时,无需重复请求远程接口
|
||||
*/
|
||||
export const ICONS_MAP: Recordable<string[]> = {};
|
||||
|
||||
interface IconifyResponse {
|
||||
prefix: string;
|
||||
total: number;
|
||||
title: string;
|
||||
uncategorized?: string[];
|
||||
categories?: Recordable<string[]>;
|
||||
aliases?: Recordable<string>;
|
||||
}
|
||||
|
||||
const PENDING_REQUESTS: Recordable<Promise<string[]>> = {};
|
||||
|
||||
/**
|
||||
* 通过Iconify接口获取图标集数据。
|
||||
* 同一时间多个图标选择器同时请求同一个图标集时,实际上只会发起一次请求(所有请求共享同一份结果)。
|
||||
* 请求结果会被缓存,刷新页面前同一个图标集不会再次请求
|
||||
* @param prefix 图标集名称
|
||||
* @returns 图标集中包含的所有图标名称
|
||||
*/
|
||||
export async function fetchIconsData(prefix: string): Promise<string[]> {
|
||||
if (Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) {
|
||||
return ICONS_MAP[prefix];
|
||||
}
|
||||
if (Reflect.has(PENDING_REQUESTS, prefix) && PENDING_REQUESTS[prefix]) {
|
||||
return PENDING_REQUESTS[prefix];
|
||||
}
|
||||
PENDING_REQUESTS[prefix] = (async () => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 1000 * 10);
|
||||
const response: IconifyResponse = await fetch(
|
||||
`https://api.iconify.design/collection?prefix=${prefix}`,
|
||||
{ signal: controller.signal },
|
||||
).then((res) => res.json());
|
||||
clearTimeout(timeoutId);
|
||||
const list = response.uncategorized || [];
|
||||
if (response.categories) {
|
||||
for (const category in response.categories) {
|
||||
list.push(...(response.categories[category] || []));
|
||||
}
|
||||
}
|
||||
ICONS_MAP[prefix] = list.map((v) => `${prefix}:${v}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch icons for prefix ${prefix}:`, error);
|
||||
return [] as string[];
|
||||
}
|
||||
return ICONS_MAP[prefix];
|
||||
})();
|
||||
return PENDING_REQUESTS[prefix];
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/hooks",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/layouts",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/plugins",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/request",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -31,6 +31,7 @@ describe('fileDownloader', () => {
|
||||
expect(result).toEqual(mockBlob);
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, {
|
||||
responseType: 'blob',
|
||||
responseReturn: 'body',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,6 +52,7 @@ describe('fileDownloader', () => {
|
||||
expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, {
|
||||
...customConfig,
|
||||
responseType: 'blob',
|
||||
responseReturn: 'body',
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -1,7 +1,14 @@
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
|
||||
import type { RequestClient } from '../request-client';
|
||||
import type { RequestResponse } from '../types';
|
||||
import type { RequestClientConfig } from '../types';
|
||||
|
||||
type DownloadRequestConfig = {
|
||||
/**
|
||||
* 定义期望获得的数据类型。
|
||||
* raw: 原始的AxiosResponse,包括headers、status等。
|
||||
* body: 只返回响应数据的BODY部分(Blob)
|
||||
*/
|
||||
responseReturn?: 'body' | 'raw';
|
||||
} & Omit<RequestClientConfig, 'responseReturn'>;
|
||||
|
||||
class FileDownloader {
|
||||
private client: RequestClient;
|
||||
@@ -9,20 +16,23 @@ class FileDownloader {
|
||||
constructor(client: RequestClient) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async download(
|
||||
/**
|
||||
* 下载文件
|
||||
* @param url 文件的完整链接
|
||||
* @param config 配置信息,可选。
|
||||
* @returns 如果config.responseReturn为'body',则返回Blob(默认),否则返回RequestResponse<Blob>
|
||||
*/
|
||||
public async download<T = Blob>(
|
||||
url: string,
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<RequestResponse<Blob>> {
|
||||
const finalConfig: AxiosRequestConfig = {
|
||||
config?: DownloadRequestConfig,
|
||||
): Promise<T> {
|
||||
const finalConfig: DownloadRequestConfig = {
|
||||
responseReturn: 'body',
|
||||
...config,
|
||||
responseType: 'blob',
|
||||
};
|
||||
|
||||
const response = await this.client.get<RequestResponse<Blob>>(
|
||||
url,
|
||||
finalConfig,
|
||||
);
|
||||
const response = await this.client.get<T>(url, finalConfig);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
|
||||
import type { RequestClient } from '../request-client';
|
||||
import type { RequestClientConfig } from '../types';
|
||||
|
||||
class FileUploader {
|
||||
private client: RequestClient;
|
||||
@@ -9,18 +8,18 @@ class FileUploader {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async upload(
|
||||
public async upload<T = any>(
|
||||
url: string,
|
||||
data: { file: Blob | File } & Record<string, any>,
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<AxiosResponse> {
|
||||
data: Record<string, any> & { file: Blob | File },
|
||||
config?: RequestClientConfig,
|
||||
): Promise<T> {
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
|
||||
const finalConfig: AxiosRequestConfig = {
|
||||
const finalConfig: RequestClientConfig = {
|
||||
...config,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
|
@@ -25,15 +25,15 @@ export const defaultResponseInterceptor = ({
|
||||
if (config.responseReturn === 'raw') {
|
||||
return response;
|
||||
}
|
||||
const code = responseData[codeField];
|
||||
if (
|
||||
status >= 200 && status < 400 && isFunction(successCode)
|
||||
? successCode(code)
|
||||
: code === successCode
|
||||
) {
|
||||
|
||||
if (status >= 200 && status < 400) {
|
||||
if (config.responseReturn === 'body') {
|
||||
return responseData;
|
||||
} else {
|
||||
} else if (
|
||||
isFunction(successCode)
|
||||
? successCode(responseData[codeField])
|
||||
: responseData[codeField] === successCode
|
||||
) {
|
||||
return isFunction(dataField)
|
||||
? dataField(responseData)
|
||||
: responseData[dataField];
|
||||
|
@@ -7,9 +7,9 @@ import type {
|
||||
|
||||
type ExtendOptions = {
|
||||
/** 响应数据的返回方式。
|
||||
* raw: 原始的AxiosResponse,包括headers、status等。
|
||||
* body: 返回响应数据的BODY部分。
|
||||
* data: 解构响应的BODY数据,只返回其中的data节点数据。
|
||||
* raw: 原始的AxiosResponse,包括headers、status等,不做是否成功请求的检查。
|
||||
* body: 返回响应数据的BODY部分(只会根据status检查请求是否成功,忽略对code的判断,这种情况下应由调用方检查请求是否成功)。
|
||||
* data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。
|
||||
*/
|
||||
responseReturn?: 'body' | 'data' | 'raw';
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/icons",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/locales",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/preferences",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/stores",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/styles",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/types",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/utils",
|
||||
"version": "5.5.2",
|
||||
"version": "5.5.3",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
Reference in New Issue
Block a user