This commit is contained in:
dap 2025-01-22 14:48:53 +08:00
commit 379778412b
46 changed files with 291 additions and 87 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@vben/docs",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"scripts": {
"build": "vitepress build",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/commitlint-config",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/stylelint-config",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/node-utils",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/tailwind-config",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/tsconfig",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/vite-config",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

View File

@ -1,6 +1,6 @@
{
"name": "vben-admin-monorepo",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"keywords": [
"monorepo",

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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",

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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">

View File

@ -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];
}

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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',
});
});

View File

@ -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: 原始的AxiosResponseheadersstatus等
* 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;
}

View File

@ -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',

View File

@ -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];

View File

@ -7,9 +7,9 @@ import type {
type ExtendOptions = {
/**
* raw: 原始的AxiosResponseheadersstatus等
* body: 返回响应数据的BODY部分
* data: 解构响应的BODY数据data节点数据
* raw: 原始的AxiosResponseheadersstatus等
* body: 返回响应数据的BODY部分status检查请求是否成功code的判断
* data: 解构响应的BODY数据data节点数据status和code是否为成功状态
*/
responseReturn?: 'body' | 'data' | 'raw';
};

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -1,6 +1,6 @@
{
"name": "@vben/playground",
"version": "5.5.2",
"version": "5.5.3",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {

View File

@ -0,0 +1,28 @@
import type { RequestResponse } from '@vben/request';
import { requestClient } from '../request';
/**
* Blob
* @returns Blob
*/
async function downloadFile1() {
return requestClient.download<Blob>(
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
);
}
/**
* Response
* @returns RequestResponse<Blob>
*/
async function downloadFile2() {
return requestClient.download<RequestResponse<Blob>>(
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
{
responseReturn: 'raw',
},
);
}
export { downloadFile1, downloadFile2 };

View File

@ -1,4 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import {
downloadFileFromBase64,
@ -9,7 +11,23 @@ import {
import { Button, Card } from 'ant-design-vue';
import { downloadFile1, downloadFile2 } from '#/api/examples/download';
import imageBase64 from './base64';
const downloadResult = ref('');
function getBlob() {
downloadFile1().then((res) => {
downloadResult.value = `获取Blob成功长度${res.size}`;
});
}
function getResponse() {
downloadFile2().then((res) => {
downloadResult.value = `获取Response成功headers${JSON.stringify(res.headers)},长度:${res.data.size}`;
});
}
</script>
<template>
@ -70,5 +88,13 @@ import imageBase64 from './base64';
Download TxT
</Button>
</Card>
<Card class="my-5" title="Request download">
<Button type="primary" @click="getBlob"> 获取Blob </Button>
<Button type="primary" class="ml-4" @click="getResponse">
获取Response
</Button>
<div class="mt-4">{{ downloadResult }}</div>
</Card>
</Page>
</template>

View File

@ -20,7 +20,10 @@ import {
import { Card, Input } from 'ant-design-vue';
const iconValue = ref('ant-design:trademark-outlined');
const iconValue1 = ref('ant-design:trademark-outlined');
const iconValue2 = ref('svg:avatar-1');
const iconValue3 = ref('mdi:alien-outline');
const iconValue4 = ref('mdi-light:book-multiple');
const inputComponent = h(Input);
</script>
@ -78,26 +81,32 @@ const inputComponent = h(Input);
<Card class="mb-5" title="图标选择器">
<div class="mb-5 flex items-center gap-5">
<span>原始样式(Iconify):</span>
<IconPicker class="w-[200px]" />
<IconPicker v-model="iconValue1" class="w-[200px]" />
</div>
<div class="mb-5 flex items-center gap-5">
<span>原始样式(svg):</span>
<IconPicker class="w-[200px]" prefix="svg" />
<IconPicker v-model="iconValue2" class="w-[200px]" prefix="svg" />
</div>
<div class="mb-5 flex items-center gap-5">
<span>使用Input:</span>
<IconPicker :input-component="inputComponent" icon-slot="addonAfter" />
<span>自定义Input:</span>
<IconPicker
:input-component="inputComponent"
v-model="iconValue3"
icon-slot="addonAfter"
model-value-prop="value"
prefix="mdi"
/>
</div>
<div class="flex items-center gap-5">
<span>可手动输入只能点击图标打开弹窗:</span>
<span>显示为一个Icon:</span>
<Input
v-model:value="iconValue"
v-model:value="iconValue4"
allow-clear
placeholder="点击这里选择图标"
style="width: 300px"
>
<template #addonAfter>
<IconPicker v-model="iconValue" class="w-[200px]" />
<IconPicker v-model="iconValue4" prefix="mdi-light" type="icon" />
</template>
</Input>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "@vben/turbo-run",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"license": "MIT",
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@vben/vsh",
"version": "5.5.2",
"version": "5.5.3",
"private": true,
"license": "MIT",
"type": "module",