refactor: 升级框架
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/common-ui",
|
||||
"version": "5.5.6",
|
||||
"version": "5.5.7",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { Component } from 'vue';
|
||||
|
||||
import type { AnyPromiseFunction } from '@vben/types';
|
||||
|
||||
import { computed, ref, unref, useAttrs, watch } from 'vue';
|
||||
import { computed, nextTick, ref, unref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { LoaderCircle } from '@vben/icons';
|
||||
|
||||
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
|
||||
import { cloneDeep, get, isEqual, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import { objectOmit } from '@vueuse/core';
|
||||
|
||||
@@ -104,6 +104,8 @@ const refOptions = ref<OptionsItem[]>([]);
|
||||
const loading = ref(false);
|
||||
// 首次是否加载过了
|
||||
const isFirstLoaded = ref(false);
|
||||
// 标记是否有待处理的请求
|
||||
const hasPendingRequest = ref(false);
|
||||
|
||||
const getOptions = computed(() => {
|
||||
const { labelField, valueField, childrenField, numberToString } = props;
|
||||
@@ -146,18 +148,26 @@ const bindProps = computed(() => {
|
||||
});
|
||||
|
||||
async function fetchApi() {
|
||||
let { api, beforeFetch, afterFetch, params, resultField } = props;
|
||||
const { api, beforeFetch, afterFetch, resultField } = props;
|
||||
|
||||
if (!api || !isFunction(api) || loading.value) {
|
||||
if (!api || !isFunction(api)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果正在加载,标记有待处理的请求并返回
|
||||
if (loading.value) {
|
||||
hasPendingRequest.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
refOptions.value = [];
|
||||
try {
|
||||
loading.value = true;
|
||||
let finalParams = unref(mergedParams);
|
||||
if (beforeFetch && isFunction(beforeFetch)) {
|
||||
params = (await beforeFetch(params)) || params;
|
||||
finalParams = (await beforeFetch(cloneDeep(finalParams))) || finalParams;
|
||||
}
|
||||
let res = await api(params);
|
||||
let res = await api(finalParams);
|
||||
if (afterFetch && isFunction(afterFetch)) {
|
||||
res = (await afterFetch(res)) || res;
|
||||
}
|
||||
@@ -177,6 +187,13 @@ async function fetchApi() {
|
||||
isFirstLoaded.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
// 如果有待处理的请求,立即触发新的请求
|
||||
if (hasPendingRequest.value) {
|
||||
hasPendingRequest.value = false;
|
||||
// 使用 nextTick 确保状态更新完成后再触发新请求
|
||||
await nextTick();
|
||||
fetchApi();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +207,7 @@ async function handleFetchForVisible(visible: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
const params = computed(() => {
|
||||
const mergedParams = computed(() => {
|
||||
return {
|
||||
...props.params,
|
||||
...unref(innerParams),
|
||||
@@ -198,7 +215,7 @@ const params = computed(() => {
|
||||
});
|
||||
|
||||
watch(
|
||||
params,
|
||||
mergedParams,
|
||||
(value, oldValue) => {
|
||||
if (isEqual(value, oldValue)) {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
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';
|
||||
|
||||
@@ -33,6 +40,16 @@ interface Props {
|
||||
* @default true
|
||||
*/
|
||||
tooltip?: boolean;
|
||||
/**
|
||||
* 是否只在文本被截断时显示提示框
|
||||
* @default false
|
||||
*/
|
||||
tooltipWhenEllipsis?: boolean;
|
||||
/**
|
||||
* 文本截断检测的像素差异阈值,越大则判断越严格
|
||||
* @default 3
|
||||
*/
|
||||
ellipsisThreshold?: number;
|
||||
/**
|
||||
* 提示框背景颜色,优先级高于 overlayStyle
|
||||
*/
|
||||
@@ -62,12 +79,15 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
maxWidth: '100%',
|
||||
placement: 'top',
|
||||
tooltip: true,
|
||||
tooltipWhenEllipsis: false,
|
||||
ellipsisThreshold: 3,
|
||||
tooltipBackgroundColor: '',
|
||||
tooltipColor: '',
|
||||
tooltipFontSize: 14,
|
||||
tooltipMaxWidth: undefined,
|
||||
tooltipOverlayStyle: () => ({ textAlign: 'justify' }),
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ expandChange: [boolean] }>();
|
||||
|
||||
const textMaxWidth = computed(() => {
|
||||
@@ -79,9 +99,67 @@ const textMaxWidth = computed(() => {
|
||||
const ellipsis = ref();
|
||||
const isExpand = ref(false);
|
||||
const defaultTooltipMaxWidth = ref();
|
||||
const isEllipsis = ref(false);
|
||||
|
||||
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(
|
||||
() => {
|
||||
if (props.tooltip && eleWidth.value) {
|
||||
@@ -91,9 +169,13 @@ watchEffect(
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
|
||||
function onExpand() {
|
||||
isExpand.value = !isExpand.value;
|
||||
emit('expandChange', isExpand.value);
|
||||
if (props.tooltipWhenEllipsis) {
|
||||
checkEllipsis();
|
||||
}
|
||||
}
|
||||
|
||||
function handleExpand() {
|
||||
@@ -110,7 +192,9 @@ function handleExpand() {
|
||||
color: tooltipColor,
|
||||
backgroundColor: tooltipBackgroundColor,
|
||||
}"
|
||||
:disabled="!props.tooltip || isExpand"
|
||||
:disabled="
|
||||
!props.tooltip || isExpand || (props.tooltipWhenEllipsis && !isEllipsis)
|
||||
"
|
||||
:side="placement"
|
||||
>
|
||||
<slot name="tooltip">
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './api-component';
|
||||
export * from './captcha';
|
||||
export * from './col-page';
|
||||
export * from './count-to';
|
||||
export * from './doc-alert';
|
||||
export * from './ellipsis-text';
|
||||
export * from './icon-picker';
|
||||
export * from './json-viewer';
|
||||
|
||||
@@ -63,7 +63,7 @@ onMounted(() => {
|
||||
ref="docRef"
|
||||
:class="
|
||||
cn(
|
||||
'bg-card border-border relative flex items-end rounded-md border-b p-4',
|
||||
'bg-card border-border relative mx-4 flex items-start rounded-md border-b',
|
||||
)
|
||||
"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as AuthenticationAuthTitle } from './auth-title.vue';
|
||||
export { default as AuthenticationCodeLogin } from './code-login.vue';
|
||||
export { default as DocLink } from './doc-link.vue';
|
||||
export { default as AuthenticationForgetPassword } from './forget-password.vue';
|
||||
export { default as AuthenticationLoginExpiredModal } from './login-expired-modal.vue';
|
||||
export { default as AuthenticationLogin } from './login.vue';
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useVbenForm } from '@vben-core/form-ui';
|
||||
import { VbenButton, VbenCheckbox } from '@vben-core/shadcn-ui';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
import DocLink from './doc-link.vue';
|
||||
import ThirdPartyLogin from './third-party-login.vue';
|
||||
|
||||
interface Props extends AuthenticationProps {
|
||||
@@ -195,5 +196,8 @@ defineExpose({
|
||||
</span>
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<!-- 萌新必读 -->
|
||||
<DocLink />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user