Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
467d337515
@ -323,6 +323,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
||||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||||
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
||||||
|
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
|
||||||
|
|
||||||
::: tip fieldMappingTime
|
::: tip fieldMappingTime
|
||||||
|
|
||||||
@ -365,13 +366,6 @@ export interface FormCommonConfig {
|
|||||||
* 所有表单项的props
|
* 所有表单项的props
|
||||||
*/
|
*/
|
||||||
componentProps?: ComponentProps;
|
componentProps?: ComponentProps;
|
||||||
/**
|
|
||||||
* 是否紧凑模式(移除表单底部为显示校验错误信息所预留的空间)。
|
|
||||||
* 在有设置校验规则的场景下,建议不要将其设置为true
|
|
||||||
* 默认为false。但用作表格的搜索表单时,默认为true
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
compact?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* 所有表单项的控件样式
|
* 所有表单项的控件样式
|
||||||
*/
|
*/
|
||||||
|
@ -110,6 +110,36 @@ VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
|||||||
|
|
||||||
项目中默认自带了基于 `axios` 封装的基础的请求配置,核心由 `@vben/request` 包提供。项目没有过多的封装,只是简单的封装了一些常用的配置,如有其他需求,可以自行增加或者调整配置。针对不同的app,可能是用到了不同的组件库以及`store`,所以在应用目录下的`src/api/request.ts`文件夹下,有对应的请求配置文件,如`web-antd`项目下的`src/api/request.ts`文件,可以根据自己的需求进行配置。
|
项目中默认自带了基于 `axios` 封装的基础的请求配置,核心由 `@vben/request` 包提供。项目没有过多的封装,只是简单的封装了一些常用的配置,如有其他需求,可以自行增加或者调整配置。针对不同的app,可能是用到了不同的组件库以及`store`,所以在应用目录下的`src/api/request.ts`文件夹下,有对应的请求配置文件,如`web-antd`项目下的`src/api/request.ts`文件,可以根据自己的需求进行配置。
|
||||||
|
|
||||||
|
### 扩展的配置
|
||||||
|
|
||||||
|
除了基础的Axios配置外,扩展了部分配置。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ExtendOptions<T = any> = {
|
||||||
|
/**
|
||||||
|
* 参数序列化方式。预置了几种针对数组的序列化类型
|
||||||
|
* - brackets: ids[]=1&ids[]=2&ids[]=3
|
||||||
|
* - comma: ids=1,2,3
|
||||||
|
* - indices: ids[0]=1&ids[1]=2&ids[2]=3
|
||||||
|
* - repeat: ids=1&ids=2&ids=3
|
||||||
|
* @default 'brackets'
|
||||||
|
*/
|
||||||
|
paramsSerializer?:
|
||||||
|
| 'brackets'
|
||||||
|
| 'comma'
|
||||||
|
| 'indices'
|
||||||
|
| 'repeat'
|
||||||
|
| AxiosRequestConfig<T>['paramsSerializer'];
|
||||||
|
/**
|
||||||
|
* 响应数据的返回方式。
|
||||||
|
* - raw: 原始的AxiosResponse,包括headers、status等,不做是否成功请求的检查。
|
||||||
|
* - body: 返回响应数据的BODY部分(只会根据status检查请求是否成功,忽略对code的判断,这种情况下应由调用方检查请求是否成功)。
|
||||||
|
* - data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。
|
||||||
|
*/
|
||||||
|
responseReturn?: 'body' | 'data' | 'raw';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### 请求示例
|
### 请求示例
|
||||||
|
|
||||||
#### GET 请求
|
#### GET 请求
|
||||||
|
@ -210,7 +210,7 @@ defineExpose({
|
|||||||
v-slot="{ flattenItems }"
|
v-slot="{ flattenItems }"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'text-blackA11 select-none list-none rounded-lg p-2 text-sm font-medium',
|
'text-blackA11 container select-none list-none rounded-lg p-2 text-sm font-medium',
|
||||||
$attrs.class as unknown as ClassType,
|
$attrs.class as unknown as ClassType,
|
||||||
bordered ? 'border' : '',
|
bordered ? 'border' : '',
|
||||||
)
|
)
|
||||||
@ -219,11 +219,7 @@ defineExpose({
|
|||||||
<div class="w-full" v-if="$slots.header">
|
<div class="w-full" v-if="$slots.header">
|
||||||
<slot name="header"> </slot>
|
<slot name="header"> </slot>
|
||||||
</div>
|
</div>
|
||||||
<TransitionGroup
|
<TransitionGroup :name="transition ? 'fade' : ''">
|
||||||
:name="transition ? 'fade' : ''"
|
|
||||||
mode="out-in"
|
|
||||||
class="container"
|
|
||||||
>
|
|
||||||
<TreeItem
|
<TreeItem
|
||||||
v-for="item in flattenItems"
|
v-for="item in flattenItems"
|
||||||
v-slot="{
|
v-slot="{
|
||||||
|
@ -31,6 +31,9 @@ async function generateAccessible(
|
|||||||
|
|
||||||
const root = router.getRoutes().find((item) => item.path === '/');
|
const root = router.getRoutes().find((item) => item.path === '/');
|
||||||
|
|
||||||
|
// 获取已有的路由名称列表
|
||||||
|
const names = root?.children?.map((item) => item.name) ?? [];
|
||||||
|
|
||||||
// 动态添加到router实例内
|
// 动态添加到router实例内
|
||||||
accessibleRoutes.forEach((route) => {
|
accessibleRoutes.forEach((route) => {
|
||||||
/**
|
/**
|
||||||
@ -46,7 +49,10 @@ async function generateAccessible(
|
|||||||
if (route.children && route.children.length > 0) {
|
if (route.children && route.children.length > 0) {
|
||||||
delete route.component;
|
delete route.component;
|
||||||
}
|
}
|
||||||
root.children?.push(route);
|
// 根据router name判断,如果路由已经存在,则不再添加
|
||||||
|
if (!names?.includes(route.name)) {
|
||||||
|
root.children?.push(route);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
"@vben/locales": "workspace:*",
|
"@vben/locales": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"axios": "catalog:",
|
"axios": "catalog:",
|
||||||
"qs": "^6.13.1"
|
"qs": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/qs": "^6.9.17",
|
"@types/qs": "catalog:",
|
||||||
"axios-mock-adapter": "catalog:"
|
"axios-mock-adapter": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,39 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|||||||
|
|
||||||
import type { RequestClientConfig, RequestClientOptions } from './types';
|
import type { RequestClientConfig, RequestClientOptions } from './types';
|
||||||
|
|
||||||
import { bindMethods, merge } from '@vben/utils';
|
import { bindMethods, isString, merge } from '@vben/utils';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
import { FileDownloader } from './modules/downloader';
|
import { FileDownloader } from './modules/downloader';
|
||||||
import { InterceptorManager } from './modules/interceptor';
|
import { InterceptorManager } from './modules/interceptor';
|
||||||
import { FileUploader } from './modules/uploader';
|
import { FileUploader } from './modules/uploader';
|
||||||
|
|
||||||
|
function getParamsSerializer(
|
||||||
|
paramsSerializer: RequestClientOptions['paramsSerializer'],
|
||||||
|
) {
|
||||||
|
if (isString(paramsSerializer)) {
|
||||||
|
switch (paramsSerializer) {
|
||||||
|
case 'brackets': {
|
||||||
|
return (params: any) =>
|
||||||
|
qs.stringify(params, { arrayFormat: 'brackets' });
|
||||||
|
}
|
||||||
|
case 'comma': {
|
||||||
|
return (params: any) => qs.stringify(params, { arrayFormat: 'comma' });
|
||||||
|
}
|
||||||
|
case 'indices': {
|
||||||
|
return (params: any) =>
|
||||||
|
qs.stringify(params, { arrayFormat: 'indices' });
|
||||||
|
}
|
||||||
|
case 'repeat': {
|
||||||
|
return (params: any) => qs.stringify(params, { arrayFormat: 'repeat' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paramsSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
class RequestClient {
|
class RequestClient {
|
||||||
public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
|
public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
|
||||||
|
|
||||||
@ -39,6 +64,9 @@ class RequestClient {
|
|||||||
};
|
};
|
||||||
const { ...axiosConfig } = options;
|
const { ...axiosConfig } = options;
|
||||||
const requestConfig = merge(axiosConfig, defaultConfig);
|
const requestConfig = merge(axiosConfig, defaultConfig);
|
||||||
|
requestConfig.paramsSerializer = getParamsSerializer(
|
||||||
|
requestConfig.paramsSerializer,
|
||||||
|
);
|
||||||
this.instance = axios.create(requestConfig);
|
this.instance = axios.create(requestConfig);
|
||||||
|
|
||||||
bindMethods(this);
|
bindMethods(this);
|
||||||
@ -154,6 +182,9 @@ class RequestClient {
|
|||||||
const response: AxiosResponse<T> = await this.instance({
|
const response: AxiosResponse<T> = await this.instance({
|
||||||
url,
|
url,
|
||||||
...config,
|
...config,
|
||||||
|
...(config.paramsSerializer
|
||||||
|
? { paramsSerializer: getParamsSerializer(config.paramsSerializer) }
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
return response as T;
|
return response as T;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -5,15 +5,29 @@ import type {
|
|||||||
InternalAxiosRequestConfig,
|
InternalAxiosRequestConfig,
|
||||||
} from 'axios';
|
} from 'axios';
|
||||||
|
|
||||||
type ExtendOptions = {
|
type ExtendOptions<T = any> = {
|
||||||
/** 响应数据的返回方式。
|
/**
|
||||||
* raw: 原始的AxiosResponse,包括headers、status等,不做是否成功请求的检查。
|
* 参数序列化方式。预置的有
|
||||||
* body: 返回响应数据的BODY部分(只会根据status检查请求是否成功,忽略对code的判断,这种情况下应由调用方检查请求是否成功)。
|
* - brackets: ids[]=1&ids[]=2&ids[]=3
|
||||||
* data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。
|
* - comma: ids=1,2,3
|
||||||
|
* - indices: ids[0]=1&ids[1]=2&ids[2]=3
|
||||||
|
* - repeat: ids=1&ids=2&ids=3
|
||||||
|
*/
|
||||||
|
paramsSerializer?:
|
||||||
|
| 'brackets'
|
||||||
|
| 'comma'
|
||||||
|
| 'indices'
|
||||||
|
| 'repeat'
|
||||||
|
| AxiosRequestConfig<T>['paramsSerializer'];
|
||||||
|
/**
|
||||||
|
* 响应数据的返回方式。
|
||||||
|
* - raw: 原始的AxiosResponse,包括headers、status等,不做是否成功请求的检查。
|
||||||
|
* - body: 返回响应数据的BODY部分(只会根据status检查请求是否成功,忽略对code的判断,这种情况下应由调用方检查请求是否成功)。
|
||||||
|
* - data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。
|
||||||
*/
|
*/
|
||||||
responseReturn?: 'body' | 'data' | 'raw';
|
responseReturn?: 'body' | 'data' | 'raw';
|
||||||
};
|
};
|
||||||
type RequestClientConfig<T = any> = AxiosRequestConfig<T> & ExtendOptions;
|
type RequestClientConfig<T = any> = AxiosRequestConfig<T> & ExtendOptions<T>;
|
||||||
|
|
||||||
type RequestResponse<T = any> = AxiosResponse<T> & {
|
type RequestResponse<T = any> = AxiosResponse<T> & {
|
||||||
config: RequestClientConfig<T>;
|
config: RequestClientConfig<T>;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export * from './iconify/index.js';
|
export * from './iconify';
|
||||||
export * from './iconify-offline/index.js';
|
export * from './iconify-offline';
|
||||||
export { default as EmptyIcon } from './icons/empty-icon.vue';
|
export { default as EmptyIcon } from './icons/empty-icon.vue';
|
||||||
export * from './svg/index.js';
|
export * from './svg';
|
||||||
export { VbenIcon } from '@vben-core/shadcn-ui';
|
export { VbenIcon } from '@vben-core/shadcn-ui';
|
||||||
|
19
playground/src/api/examples/params.ts
Normal file
19
playground/src/api/examples/params.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起数组请求
|
||||||
|
*/
|
||||||
|
async function getParamsData(
|
||||||
|
params: Recordable<any>,
|
||||||
|
type: 'brackets' | 'comma' | 'indices' | 'repeat',
|
||||||
|
) {
|
||||||
|
return requestClient.get('/status', {
|
||||||
|
params,
|
||||||
|
paramsSerializer: type,
|
||||||
|
responseReturn: 'raw',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getParamsData };
|
@ -50,7 +50,8 @@
|
|||||||
"clipboard": "剪贴板",
|
"clipboard": "剪贴板",
|
||||||
"menuWithQuery": "带参菜单",
|
"menuWithQuery": "带参菜单",
|
||||||
"openInNewWindow": "新窗口打开",
|
"openInNewWindow": "新窗口打开",
|
||||||
"fileDownload": "文件下载"
|
"fileDownload": "文件下载",
|
||||||
|
"requestParamsSerializer": "参数序列化"
|
||||||
},
|
},
|
||||||
"breadcrumb": {
|
"breadcrumb": {
|
||||||
"navigation": "面包屑导航",
|
"navigation": "面包屑导航",
|
||||||
|
@ -243,6 +243,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: 'Tanstack Query',
|
title: 'Tanstack Query',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'RequestParamsSerializerDemo',
|
||||||
|
path: '/demos/features/request-params-serializer',
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
'#/views/demos/features/request-params-serializer/index.vue'
|
||||||
|
),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:git-pull-request-arrow',
|
||||||
|
title: $t('demos.features.requestParamsSerializer'),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 面包屑导航
|
// 面包屑导航
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, Radio, RadioGroup } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getParamsData } from '#/api/examples/params';
|
||||||
|
|
||||||
|
const params = { ids: [2512, 3241, 4255] };
|
||||||
|
const paramsSerializer = ref<'brackets' | 'comma' | 'indices' | 'repeat'>(
|
||||||
|
'brackets',
|
||||||
|
);
|
||||||
|
const response = ref('');
|
||||||
|
const paramsStr = computed(() => {
|
||||||
|
// 写一段代码,从完整的URL中提取参数部分
|
||||||
|
const url = response.value;
|
||||||
|
return new URL(url).searchParams.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
getParamsData(params, paramsSerializer.value).then((res) => {
|
||||||
|
response.value = res.request.responseURL;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
title="请求参数序列化"
|
||||||
|
description="不同的后台接口可能对数组类型的GET参数的解析方式不同,我们预置了几种数组序列化方式,通过配置 paramsSerializer 来实现不同的序列化方式"
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<RadioGroup v-model:value="paramsSerializer" name="paramsSerializer">
|
||||||
|
<Radio value="brackets">brackets</Radio>
|
||||||
|
<Radio value="comma">comma</Radio>
|
||||||
|
<Radio value="indices">indices</Radio>
|
||||||
|
<Radio value="repeat">repeat</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<div class="mt-4 flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<h3>需要提交的参数</h3>
|
||||||
|
<div>{{ JSON.stringify(params, null, 2) }}</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="response">
|
||||||
|
<div>
|
||||||
|
<h3>访问地址</h3>
|
||||||
|
<pre>{{ response }}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>参数字符串</h3>
|
||||||
|
<pre>{{ paramsStr }}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>参数解码</h3>
|
||||||
|
<pre>{{ decodeURIComponent(paramsStr) }}</pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -50,6 +50,7 @@ catalog:
|
|||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
|
'@types/qs': ^6.9.18
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.26.0
|
'@typescript-eslint/eslint-plugin': ^8.26.0
|
||||||
'@typescript-eslint/parser': ^8.26.0
|
'@typescript-eslint/parser': ^8.26.0
|
||||||
@ -139,6 +140,7 @@ catalog:
|
|||||||
prettier-plugin-tailwindcss: ^0.6.11
|
prettier-plugin-tailwindcss: ^0.6.11
|
||||||
publint: ^0.2.12
|
publint: ^0.2.12
|
||||||
qrcode: ^1.5.4
|
qrcode: ^1.5.4
|
||||||
|
qs: ^6.14.0
|
||||||
radix-vue: ^1.9.17
|
radix-vue: ^1.9.17
|
||||||
resolve.exports: ^2.0.3
|
resolve.exports: ^2.0.3
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
|
Loading…
Reference in New Issue
Block a user