Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
c3edbec3f0
28
apps/backend-mock/api/demo/bigint.ts
Normal file
28
apps/backend-mock/api/demo/bigint.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const userinfo = verifyAccessToken(event);
|
||||||
|
if (!userinfo) {
|
||||||
|
return unAuthorizedResponse(event);
|
||||||
|
}
|
||||||
|
const data = `
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 123456789012345678901234567890123456789012345678901234567890,
|
||||||
|
"name": "John Doe",
|
||||||
|
"age": 30,
|
||||||
|
"email": "john-doe@demo.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 987654321098765432109876543210987654321098765432109876543210,
|
||||||
|
"name": "Jane Smith",
|
||||||
|
"age": 25,
|
||||||
|
"email": "jane@demo.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
setHeader(event, 'Content-Type', 'application/json');
|
||||||
|
return data;
|
||||||
|
});
|
@ -10,6 +10,7 @@ import { $t } from '@vben/locales';
|
|||||||
|
|
||||||
import { isArray } from 'lodash-es';
|
import { isArray } from 'lodash-es';
|
||||||
|
|
||||||
|
async function initSetupVbenForm() {
|
||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
@ -44,10 +45,11 @@ setupVbenForm<ComponentType>({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const useVbenForm = useForm<ComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { initSetupVbenForm, useVbenForm, z };
|
||||||
|
|
||||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
@ -13,6 +13,7 @@ import { setupGlobalComponent } from '#/components/global';
|
|||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component';
|
||||||
|
import { initSetupVbenForm } from './adapter/form';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
@ -20,6 +21,9 @@ async function bootstrap(namespace: string) {
|
|||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
||||||
|
// 初始化表单组件
|
||||||
|
await initSetupVbenForm();
|
||||||
|
|
||||||
// // 设置弹窗的默认配置
|
// // 设置弹窗的默认配置
|
||||||
// setDefaultModalProps({
|
// setDefaultModalProps({
|
||||||
// fullscreenButton: false,
|
// fullscreenButton: false,
|
||||||
|
@ -26,6 +26,12 @@ outline: deep
|
|||||||
|
|
||||||
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
|
<DemoPreview dir="demos/vben-ellipsis-text/tooltip" />
|
||||||
|
|
||||||
|
## 自动显示 tooltip
|
||||||
|
|
||||||
|
通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-ellipsis-text/auto-display" />
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
@ -37,6 +43,8 @@ outline: deep
|
|||||||
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
| maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` |
|
||||||
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
|
| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` |
|
||||||
| tooltip | 启用文本提示 | `boolean` | `true` |
|
| tooltip | 启用文本提示 | `boolean` | `true` |
|
||||||
|
| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` |
|
||||||
|
| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` |
|
||||||
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
|
| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - |
|
||||||
| tooltipColor | 提示文本的颜色 | `string` | - |
|
| tooltipColor | 提示文本的颜色 | `string` | - |
|
||||||
| tooltipFontSize | 提示文本的大小 | `string` | - |
|
| tooltipFontSize | 提示文本的大小 | `string` | - |
|
||||||
|
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
16
docs/src/demos/vben-ellipsis-text/auto-display/index.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { EllipsisText } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const text = `
|
||||||
|
Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。
|
||||||
|
`;
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<EllipsisText :line="2" :tooltip-when-ellipsis="true">
|
||||||
|
{{ text }}
|
||||||
|
</EllipsisText>
|
||||||
|
|
||||||
|
<EllipsisText :line="3" :tooltip-when-ellipsis="true">
|
||||||
|
{{ text }}
|
||||||
|
</EllipsisText>
|
||||||
|
</template>
|
@ -238,6 +238,7 @@ const defaultPreferences: Preferences = {
|
|||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
fit: 'contain',
|
||||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
@ -431,6 +432,8 @@ interface HeaderPreferences {
|
|||||||
interface LogoPreferences {
|
interface LogoPreferences {
|
||||||
/** Whether the logo is visible */
|
/** Whether the logo is visible */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** Logo image fitting method */
|
||||||
|
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
/** Logo URL */
|
/** Logo URL */
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
@ -237,6 +237,7 @@ const defaultPreferences: Preferences = {
|
|||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
fit: 'contain',
|
||||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
@ -431,6 +432,8 @@ interface HeaderPreferences {
|
|||||||
interface LogoPreferences {
|
interface LogoPreferences {
|
||||||
/** logo是否可见 */
|
/** logo是否可见 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** logo图片适应方式 */
|
||||||
|
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
/** logo地址 */
|
/** logo地址 */
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
},
|
},
|
||||||
"logo": {
|
"logo": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
|
"fit": "contain",
|
||||||
"source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp",
|
"source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp",
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
|
@ -62,6 +62,7 @@ const defaultPreferences: Preferences = {
|
|||||||
|
|
||||||
logo: {
|
logo: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
fit: 'contain',
|
||||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
|
@ -134,6 +134,8 @@ interface HeaderPreferences {
|
|||||||
interface LogoPreferences {
|
interface LogoPreferences {
|
||||||
/** logo是否可见 */
|
/** logo是否可见 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** logo图片适应方式 */
|
||||||
|
fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
/** logo地址 */
|
/** logo地址 */
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { createContext } from '@vben-core/shadcn-ui';
|
|||||||
import { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';
|
import { isString, mergeWithArrayOverride, set } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { useForm } from 'vee-validate';
|
import { useForm } from 'vee-validate';
|
||||||
import { object } from 'zod';
|
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
|
||||||
import { getDefaultsForSchema } from 'zod-defaults';
|
import { getDefaultsForSchema } from 'zod-defaults';
|
||||||
|
|
||||||
type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi };
|
type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi };
|
||||||
@ -52,7 +52,12 @@ export function useFormInitial(
|
|||||||
if (Reflect.has(item, 'defaultValue')) {
|
if (Reflect.has(item, 'defaultValue')) {
|
||||||
set(initialValues, item.fieldName, item.defaultValue);
|
set(initialValues, item.fieldName, item.defaultValue);
|
||||||
} else if (item.rules && !isString(item.rules)) {
|
} else if (item.rules && !isString(item.rules)) {
|
||||||
|
// 检查规则是否适合提取默认值
|
||||||
|
const customDefaultValue = getCustomDefaultValue(item.rules);
|
||||||
zodObject[item.fieldName] = item.rules;
|
zodObject[item.fieldName] = item.rules;
|
||||||
|
if (customDefaultValue !== undefined) {
|
||||||
|
initialValues[item.fieldName] = customDefaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,6 +69,38 @@ export function useFormInitial(
|
|||||||
}
|
}
|
||||||
return mergeWithArrayOverride(initialValues, zodDefaults);
|
return mergeWithArrayOverride(initialValues, zodDefaults);
|
||||||
}
|
}
|
||||||
|
// 自定义默认值提取逻辑
|
||||||
|
function getCustomDefaultValue(rule: any): any {
|
||||||
|
if (rule instanceof ZodString) {
|
||||||
|
return ''; // 默认为空字符串
|
||||||
|
} else if (rule instanceof ZodNumber) {
|
||||||
|
return null; // 默认为 null(避免显示 0)
|
||||||
|
} else if (rule instanceof ZodObject) {
|
||||||
|
// 递归提取嵌套对象的默认值
|
||||||
|
const defaultValues: Record<string, any> = {};
|
||||||
|
for (const [key, valueSchema] of Object.entries(rule.shape)) {
|
||||||
|
defaultValues[key] = getCustomDefaultValue(valueSchema);
|
||||||
|
}
|
||||||
|
return defaultValues;
|
||||||
|
} else if (rule instanceof ZodIntersection) {
|
||||||
|
// 对于交集类型,从schema 提取默认值
|
||||||
|
const leftDefaultValue = getCustomDefaultValue(rule._def.left);
|
||||||
|
const rightDefaultValue = getCustomDefaultValue(rule._def.right);
|
||||||
|
|
||||||
|
// 如果左右两边都能提取默认值,合并它们
|
||||||
|
if (
|
||||||
|
typeof leftDefaultValue === 'object' &&
|
||||||
|
typeof rightDefaultValue === 'object'
|
||||||
|
) {
|
||||||
|
return { ...leftDefaultValue, ...rightDefaultValue };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则优先使用左边的默认值
|
||||||
|
return leftDefaultValue ?? rightDefaultValue;
|
||||||
|
} else {
|
||||||
|
return undefined; // 其他类型不提供默认值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
delegatedSlots,
|
delegatedSlots,
|
||||||
|
@ -5,6 +5,8 @@ import type {
|
|||||||
AvatarRootProps,
|
AvatarRootProps,
|
||||||
} from 'radix-vue';
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
import type { CSSProperties } from 'vue';
|
||||||
|
|
||||||
import type { ClassType } from '@vben-core/typings';
|
import type { ClassType } from '@vben-core/typings';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
@ -16,6 +18,7 @@ interface Props extends AvatarFallbackProps, AvatarImageProps, AvatarRootProps {
|
|||||||
class?: ClassType;
|
class?: ClassType;
|
||||||
dot?: boolean;
|
dot?: boolean;
|
||||||
dotClass?: ClassType;
|
dotClass?: ClassType;
|
||||||
|
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +31,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
as: 'button',
|
as: 'button',
|
||||||
dot: false,
|
dot: false,
|
||||||
dotClass: 'bg-green-500',
|
dotClass: 'bg-green-500',
|
||||||
|
fit: 'cover',
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageStyle = computed<CSSProperties>(() => {
|
||||||
|
const { fit } = props;
|
||||||
|
if (fit) {
|
||||||
|
return { objectFit: fit };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
const text = computed(() => {
|
const text = computed(() => {
|
||||||
@ -51,7 +63,7 @@ const rootStyle = computed(() => {
|
|||||||
class="relative flex flex-shrink-0 items-center"
|
class="relative flex flex-shrink-0 items-center"
|
||||||
>
|
>
|
||||||
<Avatar :class="props.class" class="size-full">
|
<Avatar :class="props.class" class="size-full">
|
||||||
<AvatarImage :alt="alt" :src="src" />
|
<AvatarImage :alt="alt" :src="src" :style="imageStyle" />
|
||||||
<AvatarFallback>{{ text }}</AvatarFallback>
|
<AvatarFallback>{{ text }}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<span
|
<span
|
||||||
|
@ -6,6 +6,10 @@ interface Props {
|
|||||||
* @zh_CN 是否收起文本
|
* @zh_CN 是否收起文本
|
||||||
*/
|
*/
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
|
/**
|
||||||
|
* @zh_CN Logo 图片适应方式
|
||||||
|
*/
|
||||||
|
fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
|
||||||
/**
|
/**
|
||||||
* @zh_CN Logo 跳转地址
|
* @zh_CN Logo 跳转地址
|
||||||
*/
|
*/
|
||||||
@ -38,6 +42,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
logoSize: 32,
|
logoSize: 32,
|
||||||
src: '',
|
src: '',
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
|
fit: 'cover',
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -53,6 +58,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
:alt="text"
|
:alt="text"
|
||||||
:src="src"
|
:src="src"
|
||||||
:size="logoSize"
|
:size="logoSize"
|
||||||
|
:fit="fit"
|
||||||
class="relative rounded-none bg-transparent"
|
class="relative rounded-none bg-transparent"
|
||||||
/>
|
/>
|
||||||
<template v-if="!collapsed">
|
<template v-if="!collapsed">
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
|
|
||||||
import { computed, ref, watchEffect } from 'vue';
|
import {
|
||||||
|
computed,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
onUpdated,
|
||||||
|
ref,
|
||||||
|
watchEffect,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
import { VbenTooltip } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
@ -33,6 +40,16 @@ interface Props {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
tooltip?: boolean;
|
tooltip?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否只在文本被截断时显示提示框
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
tooltipWhenEllipsis?: boolean;
|
||||||
|
/**
|
||||||
|
* 文本截断检测的像素差异阈值,越大则判断越严格
|
||||||
|
* @default 3
|
||||||
|
*/
|
||||||
|
ellipsisThreshold?: number;
|
||||||
/**
|
/**
|
||||||
* 提示框背景颜色,优先级高于 overlayStyle
|
* 提示框背景颜色,优先级高于 overlayStyle
|
||||||
*/
|
*/
|
||||||
@ -62,12 +79,15 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
placement: 'top',
|
placement: 'top',
|
||||||
tooltip: true,
|
tooltip: true,
|
||||||
|
tooltipWhenEllipsis: false,
|
||||||
|
ellipsisThreshold: 3,
|
||||||
tooltipBackgroundColor: '',
|
tooltipBackgroundColor: '',
|
||||||
tooltipColor: '',
|
tooltipColor: '',
|
||||||
tooltipFontSize: 14,
|
tooltipFontSize: 14,
|
||||||
tooltipMaxWidth: undefined,
|
tooltipMaxWidth: undefined,
|
||||||
tooltipOverlayStyle: () => ({ textAlign: 'justify' }),
|
tooltipOverlayStyle: () => ({ textAlign: 'justify' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{ expandChange: [boolean] }>();
|
const emit = defineEmits<{ expandChange: [boolean] }>();
|
||||||
|
|
||||||
const textMaxWidth = computed(() => {
|
const textMaxWidth = computed(() => {
|
||||||
@ -79,9 +99,67 @@ const textMaxWidth = computed(() => {
|
|||||||
const ellipsis = ref();
|
const ellipsis = ref();
|
||||||
const isExpand = ref(false);
|
const isExpand = ref(false);
|
||||||
const defaultTooltipMaxWidth = ref();
|
const defaultTooltipMaxWidth = ref();
|
||||||
|
const isEllipsis = ref(false);
|
||||||
|
|
||||||
const { width: eleWidth } = useElementSize(ellipsis);
|
const { width: eleWidth } = useElementSize(ellipsis);
|
||||||
|
|
||||||
|
// 检测文本是否被截断
|
||||||
|
const checkEllipsis = () => {
|
||||||
|
if (!ellipsis.value || !props.tooltipWhenEllipsis) return;
|
||||||
|
|
||||||
|
const element = ellipsis.value;
|
||||||
|
|
||||||
|
const originalText = element.textContent || '';
|
||||||
|
const originalTrimmed = originalText.trim();
|
||||||
|
|
||||||
|
// 对于空文本直接返回 false
|
||||||
|
if (!originalTrimmed) {
|
||||||
|
isEllipsis.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widthDiff = element.scrollWidth - element.clientWidth;
|
||||||
|
const heightDiff = element.scrollHeight - element.clientHeight;
|
||||||
|
|
||||||
|
// 使用足够大的差异阈值确保只有真正被截断的文本才会显示 tooltip
|
||||||
|
isEllipsis.value =
|
||||||
|
props.line === 1
|
||||||
|
? widthDiff > props.ellipsisThreshold
|
||||||
|
: heightDiff > props.ellipsisThreshold;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用 ResizeObserver 监听尺寸变化
|
||||||
|
let resizeObserver: null | ResizeObserver = null;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (typeof ResizeObserver !== 'undefined' && props.tooltipWhenEllipsis) {
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
checkEllipsis();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ellipsis.value) {
|
||||||
|
resizeObserver.observe(ellipsis.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始检测
|
||||||
|
checkEllipsis();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用onUpdated钩子检测内容变化
|
||||||
|
onUpdated(() => {
|
||||||
|
if (props.tooltipWhenEllipsis) {
|
||||||
|
checkEllipsis();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (resizeObserver) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
resizeObserver = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watchEffect(
|
watchEffect(
|
||||||
() => {
|
() => {
|
||||||
if (props.tooltip && eleWidth.value) {
|
if (props.tooltip && eleWidth.value) {
|
||||||
@ -91,9 +169,13 @@ watchEffect(
|
|||||||
},
|
},
|
||||||
{ flush: 'post' },
|
{ flush: 'post' },
|
||||||
);
|
);
|
||||||
|
|
||||||
function onExpand() {
|
function onExpand() {
|
||||||
isExpand.value = !isExpand.value;
|
isExpand.value = !isExpand.value;
|
||||||
emit('expandChange', isExpand.value);
|
emit('expandChange', isExpand.value);
|
||||||
|
if (props.tooltipWhenEllipsis) {
|
||||||
|
checkEllipsis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleExpand() {
|
function handleExpand() {
|
||||||
@ -110,7 +192,9 @@ function handleExpand() {
|
|||||||
color: tooltipColor,
|
color: tooltipColor,
|
||||||
backgroundColor: tooltipBackgroundColor,
|
backgroundColor: tooltipBackgroundColor,
|
||||||
}"
|
}"
|
||||||
:disabled="!props.tooltip || isExpand"
|
:disabled="
|
||||||
|
!props.tooltip || isExpand || (props.tooltipWhenEllipsis && !isEllipsis)
|
||||||
|
"
|
||||||
:side="placement"
|
:side="placement"
|
||||||
>
|
>
|
||||||
<slot name="tooltip">
|
<slot name="tooltip">
|
||||||
|
@ -234,6 +234,7 @@ const headerSlots = computed(() => {
|
|||||||
<template #logo>
|
<template #logo>
|
||||||
<VbenLogo
|
<VbenLogo
|
||||||
v-if="preferences.logo.enable"
|
v-if="preferences.logo.enable"
|
||||||
|
:fit="preferences.logo.fit"
|
||||||
:class="logoClass"
|
:class="logoClass"
|
||||||
:collapsed="logoCollapsed"
|
:collapsed="logoCollapsed"
|
||||||
:src="preferences.logo.source"
|
:src="preferences.logo.source"
|
||||||
@ -324,6 +325,7 @@ const headerSlots = computed(() => {
|
|||||||
<template #side-extra-title>
|
<template #side-extra-title>
|
||||||
<VbenLogo
|
<VbenLogo
|
||||||
v-if="preferences.logo.enable"
|
v-if="preferences.logo.enable"
|
||||||
|
:fit="preferences.logo.fit"
|
||||||
:text="preferences.app.name"
|
:text="preferences.app.name"
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
>
|
>
|
||||||
|
@ -4,7 +4,7 @@ import { useVbenModal } from '@vben-core/popup-ui';
|
|||||||
import { onMounted, onUnmounted, ref } from 'vue';
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// 轮训时间,分钟
|
// 轮询时间,分钟
|
||||||
checkUpdatesInterval?: number;
|
checkUpdatesInterval?: number;
|
||||||
// 检查更新的地址
|
// 检查更新的地址
|
||||||
checkUpdateUrl?: string;
|
checkUpdateUrl?: string;
|
||||||
@ -44,6 +44,7 @@ async function getVersionTag() {
|
|||||||
const response = await fetch(props.checkUpdateUrl, {
|
const response = await fetch(props.checkUpdateUrl, {
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
method: 'HEAD',
|
method: 'HEAD',
|
||||||
|
redirect: 'manual',
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,8 +48,12 @@
|
|||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
|
"json-bigint": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/json-bigint": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import type { ComponentType } from './component';
|
|||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
async function initSetupVbenForm() {
|
||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
@ -37,9 +38,10 @@ setupVbenForm<ComponentType>({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const useVbenForm = useForm<ComponentType>;
|
const useVbenForm = useForm<ComponentType>;
|
||||||
|
|
||||||
export { useVbenForm, z };
|
export { initSetupVbenForm, useVbenForm, z };
|
||||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||||
export type { VbenFormProps };
|
export type { VbenFormProps };
|
||||||
|
10
playground/src/api/examples/json-bigint.ts
Normal file
10
playground/src/api/examples/json-bigint.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起请求
|
||||||
|
*/
|
||||||
|
async function getBigIntData() {
|
||||||
|
return requestClient.get('/demo/bigint');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getBigIntData };
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 该文件可自行根据业务逻辑进行调整
|
* 该文件可自行根据业务逻辑进行调整
|
||||||
*/
|
*/
|
||||||
import type { RequestClientOptions } from '@vben/request';
|
import type { AxiosResponseHeaders, RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
@ -12,8 +12,10 @@ import {
|
|||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
import { cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import JSONBigInt from 'json-bigint';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
@ -25,6 +27,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
|||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
...options,
|
...options,
|
||||||
baseURL,
|
baseURL,
|
||||||
|
transformResponse: (data: any, header: AxiosResponseHeaders) => {
|
||||||
|
// storeAsString指示将BigInt存储为字符串,设为false则会存储为内置的BigInt类型
|
||||||
|
return header.getContentType()?.toString().includes('application/json')
|
||||||
|
? cloneDeep(
|
||||||
|
JSONBigInt({ storeAsString: true, strict: true }).parse(data),
|
||||||
|
)
|
||||||
|
: data;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,12 +13,16 @@ import { $t, setupI18n } from '#/locales';
|
|||||||
import { router } from '#/router';
|
import { router } from '#/router';
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component';
|
||||||
|
import { initSetupVbenForm } from './adapter/form';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
||||||
|
// 初始化表单组件
|
||||||
|
await initSetupVbenForm();
|
||||||
|
|
||||||
// 设置弹窗的默认配置
|
// 设置弹窗的默认配置
|
||||||
// setDefaultModalProps({
|
// setDefaultModalProps({
|
||||||
// fullscreenButton: false,
|
// fullscreenButton: false,
|
||||||
|
@ -255,6 +255,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
title: $t('demos.features.requestParamsSerializer'),
|
title: $t('demos.features.requestParamsSerializer'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'BigIntDemo',
|
||||||
|
path: '/demos/features/json-bigint',
|
||||||
|
component: () =>
|
||||||
|
import('#/views/demos/features/json-bigint/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:grape',
|
||||||
|
title: 'JSON BigInt',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// 面包屑导航
|
// 面包屑导航
|
||||||
|
39
playground/src/views/demos/features/json-bigint/index.vue
Normal file
39
playground/src/views/demos/features/json-bigint/index.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Alert, Button, Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getBigIntData } from '#/api/examples/json-bigint';
|
||||||
|
|
||||||
|
const response = ref('');
|
||||||
|
function fetchData() {
|
||||||
|
getBigIntData().then((res) => {
|
||||||
|
response.value = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
title="JSON BigInt Support"
|
||||||
|
description="解析后端返回的长整数(long/bigInt)。代码位置:playground/src/api/request.ts中的transformResponse"
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<Alert>
|
||||||
|
<template #message>
|
||||||
|
有些后端接口返回的ID是长整数,但javascript原生的JSON解析是不支持超过2^53-1的长整数的。
|
||||||
|
这种情况可以建议后端返回数据前将长整数转换为字符串类型。如果后端不接受我们的建议😡……
|
||||||
|
<br />
|
||||||
|
下面的按钮点击后会发起请求,接口返回的JSON数据中的id字段是超出整数范围的数字,已自动将其解析为字符串
|
||||||
|
</template>
|
||||||
|
</Alert>
|
||||||
|
<Button class="mt-4" type="primary" @click="fetchData">发起请求</Button>
|
||||||
|
<div>
|
||||||
|
<pre>
|
||||||
|
{{ response }}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -41,6 +41,7 @@ catalog:
|
|||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
'@types/html-minifier-terser': ^7.0.2
|
'@types/html-minifier-terser': ^7.0.2
|
||||||
|
'@types/json-bigint': ^1.0.4
|
||||||
'@types/jsonwebtoken': ^9.0.9
|
'@types/jsonwebtoken': ^9.0.9
|
||||||
'@types/lodash.clonedeep': ^4.5.9
|
'@types/lodash.clonedeep': ^4.5.9
|
||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
@ -112,6 +113,7 @@ catalog:
|
|||||||
happy-dom: ^17.4.6
|
happy-dom: ^17.4.6
|
||||||
html-minifier-terser: ^7.2.0
|
html-minifier-terser: ^7.2.0
|
||||||
is-ci: ^4.1.0
|
is-ci: ^4.1.0
|
||||||
|
json-bigint: ^1.0.0
|
||||||
jsonc-eslint-parser: ^2.4.0
|
jsonc-eslint-parser: ^2.4.0
|
||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
lefthook: ^1.11.12
|
lefthook: ^1.11.12
|
||||||
|
Loading…
Reference in New Issue
Block a user