feat: add vxe-table component (#4563)
* chore: wip vxe-table * feat: add table demo * chore: follow ci recommendations to adjust details * chore: add custom-cell demo * feat: add custom-cell table demo * feat: add table from demo
This commit is contained in:
@@ -79,6 +79,7 @@
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0deg 0% 0% / 40%;
|
||||
--overlay-content: 0deg 0% 0% / 40%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -79,7 +79,7 @@
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0 0% 0% / 45%;
|
||||
--overlay-light: 0 0% 95% / 45%;
|
||||
--overlay-content: 0 0% 95% / 45%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -3,5 +3,19 @@ import { defineBuildConfig } from 'unbuild';
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: ['src/index'],
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
27
packages/@core/base/icons/src/components/empty.vue
Normal file
27
packages/@core/base/icons/src/components/empty.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<svg
|
||||
height="41"
|
||||
viewBox="0 0 64 41"
|
||||
width="64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g fill="none" fill-rule="evenodd" transform="translate(0 1)">
|
||||
<ellipse
|
||||
cx="32"
|
||||
cy="33"
|
||||
fill="hsl(var(--background-deep))"
|
||||
rx="32"
|
||||
ry="7"
|
||||
/>
|
||||
<g fill-rule="nonzero" stroke="hsl(var(--heavy))">
|
||||
<path
|
||||
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
|
||||
/>
|
||||
<path
|
||||
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
|
||||
fill="hsl(var(--accent))"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
@@ -1,4 +1,5 @@
|
||||
export { default as EmptyIcon } from './components/empty.vue';
|
||||
export * from './create-icon';
|
||||
export * from './lucide';
|
||||
|
||||
export * from './lucide';
|
||||
export * from '@iconify/vue';
|
||||
|
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @zh_CN 布局内容高度 css变量
|
||||
* @en_US Layout content height
|
||||
*/
|
||||
/** layout content 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT = `--vben-content-height`;
|
||||
/** layout content 组件的宽度 */
|
||||
export const CSS_VARIABLE_LAYOUT_CONTENT_WIDTH = `--vben-content-width`;
|
||||
/** layout header 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`;
|
||||
/** layout footer 组件的高度 */
|
||||
export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;
|
||||
|
||||
/**
|
||||
* @zh_CN 默认命名空间
|
||||
|
@@ -85,3 +85,11 @@ export function needsScrollbar() {
|
||||
// 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
|
||||
return doc.scrollHeight > window.innerHeight;
|
||||
}
|
||||
|
||||
export function triggerWindowResize(): void {
|
||||
// 创建一个新的 resize 事件
|
||||
const resizeEvent = new Event('resize');
|
||||
|
||||
// 触发 window 的 resize 事件
|
||||
window.dispatchEvent(resizeEvent);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export * from './use-content-style';
|
||||
export * from './use-is-mobile';
|
||||
export * from './use-layout-style';
|
||||
export * from './use-namespace';
|
||||
export * from './use-priority-value';
|
||||
export * from './use-scroll-lock';
|
||||
|
@@ -4,6 +4,8 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import {
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_WIDTH,
|
||||
CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_HEADER_HEIGHT,
|
||||
} from '@vben-core/shared/constants';
|
||||
import {
|
||||
getElementVisibleRect,
|
||||
@@ -15,7 +17,7 @@ import { useCssVar, useDebounceFn } from '@vueuse/core';
|
||||
/**
|
||||
* @zh_CN content style
|
||||
*/
|
||||
function useContentStyle() {
|
||||
export function useLayoutContentStyle() {
|
||||
let resizeObserver: null | ResizeObserver = null;
|
||||
const contentElement = ref<HTMLDivElement | null>(null);
|
||||
const visibleDomRect = ref<null | VisibleDomRect>(null);
|
||||
@@ -40,7 +42,7 @@ function useContentStyle() {
|
||||
contentHeight.value = `${visibleDomRect.value.height}px`;
|
||||
contentWidth.value = `${visibleDomRect.value.width}px`;
|
||||
},
|
||||
100,
|
||||
16,
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
@@ -58,4 +60,28 @@ function useContentStyle() {
|
||||
return { contentElement, overlayStyle, visibleDomRect };
|
||||
}
|
||||
|
||||
export { useContentStyle };
|
||||
export function useLayoutHeaderStyle() {
|
||||
const headerHeight = useCssVar(CSS_VARIABLE_LAYOUT_HEADER_HEIGHT);
|
||||
|
||||
return {
|
||||
getLayoutHeaderHeight: () => {
|
||||
return Number.parseInt(`${headerHeight.value}`, 10);
|
||||
},
|
||||
setLayoutHeaderHeight: (height: number) => {
|
||||
headerHeight.value = `${height}px`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useLayoutFooterStyle() {
|
||||
const footerHeight = useCssVar(CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT);
|
||||
|
||||
return {
|
||||
getLayoutFooterHeight: () => {
|
||||
return Number.parseInt(`${footerHeight.value}`, 10);
|
||||
},
|
||||
setLayoutFooterHeight: (height: number) => {
|
||||
footerHeight.value = `${height}px`;
|
||||
},
|
||||
};
|
||||
}
|
@@ -39,7 +39,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||
"icpLink": "",
|
||||
},
|
||||
"footer": {
|
||||
"enable": true,
|
||||
"enable": false,
|
||||
"fixed": false,
|
||||
},
|
||||
"header": {
|
||||
|
@@ -39,7 +39,7 @@ const defaultPreferences: Preferences = {
|
||||
icpLink: '',
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
enable: false,
|
||||
fixed: false,
|
||||
},
|
||||
header: {
|
||||
|
@@ -28,6 +28,10 @@ function usePreferences() {
|
||||
return isDarkTheme(preferences.theme.mode);
|
||||
});
|
||||
|
||||
const locale = computed(() => {
|
||||
return preferences.app.locale;
|
||||
});
|
||||
|
||||
const isMobile = computed(() => {
|
||||
return appPreferences.value.isMobile;
|
||||
});
|
||||
@@ -218,6 +222,7 @@ function usePreferences() {
|
||||
isSideNav,
|
||||
keepAlive,
|
||||
layout,
|
||||
locale,
|
||||
preferencesButtonPosition,
|
||||
sidebarCollapsed,
|
||||
theme,
|
||||
|
@@ -109,7 +109,7 @@ describe('formApi', () => {
|
||||
});
|
||||
|
||||
it('should unmount form and reset state', () => {
|
||||
formApi.unmounted();
|
||||
formApi.unmount();
|
||||
expect(formApi.isMounted).toBe(false);
|
||||
});
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRaw, unref } from 'vue';
|
||||
import { computed, toRaw, unref, watch } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import { VbenExpandableArrow } from '@vben-core/shadcn-ui';
|
||||
import { cn, isFunction } from '@vben-core/shared/utils';
|
||||
import { cn, isFunction, triggerWindowResize } from '@vben-core/shared/utils';
|
||||
|
||||
import { COMPONENT_MAP } from '../config';
|
||||
import { injectFormProps } from '../use-form-context';
|
||||
@@ -65,6 +65,16 @@ async function handleReset(e: Event) {
|
||||
form.resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => collapsed.value,
|
||||
() => {
|
||||
const props = unref(rootProps);
|
||||
if (props.collapseTriggerResize) {
|
||||
triggerWindowResize();
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
@@ -24,9 +24,11 @@ function getDefaultState(): VbenFormProps {
|
||||
actionWrapperClass: '',
|
||||
collapsed: false,
|
||||
collapsedRows: 1,
|
||||
collapseTriggerResize: false,
|
||||
commonConfig: {},
|
||||
handleReset: undefined,
|
||||
handleSubmit: undefined,
|
||||
handleValuesChange: undefined,
|
||||
layout: 'horizontal',
|
||||
resetButtonOptions: {},
|
||||
schema: [],
|
||||
@@ -249,7 +251,7 @@ export class FormApi {
|
||||
return rawValues;
|
||||
}
|
||||
|
||||
unmounted() {
|
||||
unmount() {
|
||||
// this.state = null;
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
|
@@ -244,6 +244,11 @@ export interface FormRenderProps<
|
||||
* @default 1
|
||||
*/
|
||||
collapsedRows?: number;
|
||||
/**
|
||||
* 是否触发resize事件
|
||||
* @default false
|
||||
*/
|
||||
collapseTriggerResize?: boolean;
|
||||
/**
|
||||
* 表单项通用后备配置,当子项目没配置时使用这里的配置,子项目配置优先级高于此配置
|
||||
*/
|
||||
@@ -302,6 +307,10 @@ export interface VbenFormProps<
|
||||
* 表单提交回调
|
||||
*/
|
||||
handleSubmit?: HandleSubmitFn;
|
||||
/**
|
||||
* 表单值变化回调
|
||||
*/
|
||||
handleValuesChange?: (values: Record<string, any>) => void;
|
||||
/**
|
||||
* 重置按钮参数
|
||||
*/
|
||||
|
@@ -24,7 +24,7 @@ export function useVbenForm<
|
||||
const Form = defineComponent(
|
||||
(props: VbenFormProps, { attrs, slots }) => {
|
||||
onBeforeUnmount(() => {
|
||||
api.unmounted();
|
||||
api.unmount();
|
||||
});
|
||||
return () =>
|
||||
h(VbenUseForm, { ...props, ...attrs, formApi: extendedApi }, slots);
|
||||
|
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import type { ExtendedFormApi, VbenFormProps } from './types';
|
||||
|
||||
// import { toRaw, watch } from 'vue';
|
||||
|
||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||
// import { isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import FormActions from './components/form-actions.vue';
|
||||
import {
|
||||
@@ -31,6 +34,18 @@ props.formApi?.mount?.(form);
|
||||
const handleUpdateCollapsed = (value: boolean) => {
|
||||
props.formApi?.setState({ collapsed: !!value });
|
||||
};
|
||||
// if (isFunction(forward.value.handleValuesChange)) {
|
||||
// watch(
|
||||
// () => form.values,
|
||||
// (val) => {
|
||||
// forward.value.handleValuesChange?.(toRaw(val));
|
||||
// },
|
||||
// {
|
||||
// deep: true,
|
||||
// immediate: true,
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -4,7 +4,7 @@ import type { ContentCompactType } from '@vben-core/typings';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useContentStyle } from '@vben-core/composables';
|
||||
import { useLayoutContentStyle } from '@vben-core/composables';
|
||||
import { Slot } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
@@ -25,7 +25,7 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const { contentElement, overlayStyle } = useContentStyle();
|
||||
const { contentElement, overlayStyle } = useLayoutContentStyle();
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
const {
|
||||
|
@@ -4,7 +4,11 @@ import type { VbenLayoutProps } from './vben-layout';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { SCROLL_FIXED_CLASS } from '@vben-core/composables';
|
||||
import {
|
||||
SCROLL_FIXED_CLASS,
|
||||
useLayoutFooterStyle,
|
||||
useLayoutHeaderStyle,
|
||||
} from '@vben-core/composables';
|
||||
import { Menu } from '@vben-core/icons';
|
||||
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
@@ -74,6 +78,9 @@ const {
|
||||
y: scrollY,
|
||||
} = useScroll(document);
|
||||
|
||||
const { setLayoutHeaderHeight } = useLayoutHeaderStyle();
|
||||
const { setLayoutFooterHeight } = useLayoutFooterStyle();
|
||||
|
||||
const { y: mouseY } = useMouse({ target: contentRef, type: 'client' });
|
||||
|
||||
const {
|
||||
@@ -356,6 +363,26 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
[() => headerWrapperHeight.value, () => isFullContent.value],
|
||||
([height]) => {
|
||||
setLayoutHeaderHeight(isFullContent.value ? 0 : height);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.footerHeight,
|
||||
(height: number) => {
|
||||
setLayoutFooterHeight(height);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
const mouseMove = () => {
|
||||
mouseY.value > headerWrapperHeight.value
|
||||
|
@@ -15,13 +15,6 @@ export default defineBuildConfig({
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
// {
|
||||
// builder: 'mkdist',
|
||||
// format: 'cjs',
|
||||
// input: './src',
|
||||
// loaders: ['js'],
|
||||
// pattern: ['**/*.ts'],
|
||||
// },
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
|
@@ -16,13 +16,6 @@ export default defineBuildConfig({
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
// {
|
||||
// builder: 'mkdist',
|
||||
// format: 'cjs',
|
||||
// input: './src',
|
||||
// loaders: ['js'],
|
||||
// pattern: ['**/*.ts'],
|
||||
// },
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared/utils';
|
||||
|
||||
@@ -32,10 +32,13 @@ const {
|
||||
showRowsPerPage = true,
|
||||
showTotalText = true,
|
||||
siblingCount = 1,
|
||||
size = 'default',
|
||||
size = 'small',
|
||||
total = 500,
|
||||
} = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [currentPage: number, pageSize: number];
|
||||
}>();
|
||||
const currentPage = defineModel<number>('currentPage', { default: 1 });
|
||||
const itemPerPage = defineModel<number>('itemPerPage', { default: 20 });
|
||||
|
||||
@@ -53,6 +56,13 @@ const options = computed(() => {
|
||||
function handleUpdateModelValue(value: string) {
|
||||
itemPerPage.value = Number(value);
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => itemPerPage.value, () => currentPage.value],
|
||||
([itemPerPage, currentPage]) => {
|
||||
emit('pageChange', currentPage, itemPerPage);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -69,7 +69,7 @@ function onTransitionEnd() {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'z-100 dark:bg-overlay pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center bg-[hsl(var(--overlay-light))] transition-all duration-500',
|
||||
'z-100 dark:bg-overlay bg-overlay-content pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
|
@@ -63,7 +63,7 @@ function onTransitionEnd() {
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex-center z-100 dark:bg-overlay absolute left-0 top-0 size-full bg-[hsl(var(--overlay-light))] backdrop-blur-sm transition-all duration-500',
|
||||
'flex-center z-100 bg-overlay-content absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
|
@@ -54,7 +54,7 @@ describe('page.vue', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const contentDiv = wrapper.find('.m-4');
|
||||
const contentDiv = wrapper.find('.p-4');
|
||||
expect(contentDiv.classes()).toContain('custom-class');
|
||||
});
|
||||
|
||||
|
@@ -1,37 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
contentClass?: string;
|
||||
showFooter?: boolean;
|
||||
/**
|
||||
* 根据content可见高度自适应
|
||||
*/
|
||||
autoContentHeight?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'Page',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
contentClass: '',
|
||||
description: '',
|
||||
showFooter: false,
|
||||
title: '',
|
||||
const {
|
||||
contentClass = '',
|
||||
description = '',
|
||||
autoContentHeight = false,
|
||||
title = '',
|
||||
} = defineProps<Props>();
|
||||
|
||||
const headerHeight = ref(0);
|
||||
const footerHeight = ref(0);
|
||||
const shouldAutoHeight = ref(false);
|
||||
|
||||
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
|
||||
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
if (autoContentHeight) {
|
||||
return {
|
||||
height: shouldAutoHeight.value
|
||||
? `calc(var(--vben-content-height) - ${headerHeight.value}px - ${footerHeight.value}px)`
|
||||
: '0',
|
||||
// 'overflow-y': shouldAutoHeight.value?'auto':'unset',
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
async function calcContentHeight() {
|
||||
if (!autoContentHeight) {
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
headerHeight.value = headerRef.value?.offsetHeight || 0;
|
||||
footerHeight.value = footerRef.value?.offsetHeight || 0;
|
||||
setTimeout(() => {
|
||||
shouldAutoHeight.value = true;
|
||||
}, 30);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
calcContentHeight();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full">
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="description || $slots.description || title"
|
||||
class="bg-card px-6 py-4"
|
||||
v-if="
|
||||
description ||
|
||||
$slots.description ||
|
||||
title ||
|
||||
$slots.title ||
|
||||
$slots.extra
|
||||
"
|
||||
ref="headerRef"
|
||||
class="bg-card relative px-6 py-4"
|
||||
>
|
||||
<slot name="title">
|
||||
<div
|
||||
v-if="title"
|
||||
class="mb-2 flex justify-between text-lg font-semibold"
|
||||
>
|
||||
<div v-if="title" class="mb-2 flex text-lg font-semibold">
|
||||
{{ title }}
|
||||
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
@@ -40,14 +82,19 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
{{ description }}
|
||||
</p>
|
||||
</slot>
|
||||
|
||||
<div v-if="$slots.extra" class="absolute bottom-4 right-4">
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="contentClass" class="m-4">
|
||||
<div :class="contentClass" :style="contentStyle" class="h-full p-4">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="props.showFooter"
|
||||
v-if="$slots.footer"
|
||||
ref="footerRef"
|
||||
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
|
||||
>
|
||||
<slot name="footer"></slot>
|
||||
|
@@ -17,12 +17,26 @@
|
||||
"./echarts": {
|
||||
"types": "./src/echarts/index.ts",
|
||||
"default": "./src/echarts/index.ts"
|
||||
},
|
||||
"./vxe-table": {
|
||||
"types": "./src/vxe-table/index.ts",
|
||||
"default": "./src/vxe-table/index.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/form-ui": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"echarts": "catalog:",
|
||||
"vue": "catalog:"
|
||||
"vue": "catalog:",
|
||||
"vxe-pc-ui": "catalog:",
|
||||
"vxe-table": "catalog:"
|
||||
}
|
||||
}
|
||||
|
1
packages/effects/plugins/postcss.config.mjs
Normal file
1
packages/effects/plugins/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
111
packages/effects/plugins/src/vxe-table/api.ts
Normal file
111
packages/effects/plugins/src/vxe-table/api.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { VxeGridInstance } from 'vxe-table';
|
||||
|
||||
import type { VxeGridProps } from './types';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import { Store } from '@vben-core/shared/store';
|
||||
import {
|
||||
bindMethods,
|
||||
isFunction,
|
||||
mergeWithArrayOverride,
|
||||
StateHandler,
|
||||
} from '@vben-core/shared/utils';
|
||||
|
||||
function getDefaultState(): VxeGridProps {
|
||||
return {
|
||||
class: '',
|
||||
gridClass: '',
|
||||
gridOptions: {},
|
||||
gridEvents: {},
|
||||
formOptions: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export class VxeGridApi {
|
||||
// private prevState: null | VxeGridProps = null;
|
||||
public grid = {} as VxeGridInstance;
|
||||
|
||||
isMounted = false;
|
||||
public state: null | VxeGridProps = null;
|
||||
|
||||
stateHandler: StateHandler;
|
||||
|
||||
public store: Store<VxeGridProps>;
|
||||
|
||||
constructor(options: VxeGridProps = {}) {
|
||||
const storeState = { ...options };
|
||||
|
||||
const defaultState = getDefaultState();
|
||||
this.store = new Store<VxeGridProps>(
|
||||
mergeWithArrayOverride(storeState, defaultState),
|
||||
{
|
||||
onUpdate: () => {
|
||||
// this.prevState = this.state;
|
||||
this.state = this.store.state;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
this.state = this.store.state;
|
||||
this.stateHandler = new StateHandler();
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
mount(instance: null | VxeGridInstance) {
|
||||
if (!this.isMounted && instance) {
|
||||
this.grid = instance;
|
||||
this.stateHandler.setConditionTrue();
|
||||
this.isMounted = true;
|
||||
}
|
||||
}
|
||||
|
||||
async query(params: Record<string, any> = {}) {
|
||||
try {
|
||||
await this.grid.commitProxy('query', toRaw(params));
|
||||
} catch (error) {
|
||||
console.error('Error occurred while querying:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async reload(params: Record<string, any> = {}) {
|
||||
try {
|
||||
await this.grid.commitProxy('reload', toRaw(params));
|
||||
} catch (error) {
|
||||
console.error('Error occurred while reloading:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setGridOptions(options: Partial<VxeGridProps['gridOptions']>) {
|
||||
this.setState({
|
||||
gridOptions: options,
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(isLoading: boolean) {
|
||||
this.setState({
|
||||
gridOptions: {
|
||||
loading: isLoading,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setState(
|
||||
stateOrFn:
|
||||
| ((prev: VxeGridProps) => Partial<VxeGridProps>)
|
||||
| Partial<VxeGridProps>,
|
||||
) {
|
||||
if (isFunction(stateOrFn)) {
|
||||
this.store.setState((prev) => {
|
||||
return mergeWithArrayOverride(stateOrFn(prev), prev);
|
||||
});
|
||||
} else {
|
||||
this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev));
|
||||
}
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
}
|
||||
}
|
4
packages/effects/plugins/src/vxe-table/index.ts
Normal file
4
packages/effects/plugins/src/vxe-table/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { setupVbenVxeTable } from './init';
|
||||
export * from './use-vxe-grid';
|
||||
export { default as VbenVxeGrid } from './use-vxe-grid.vue';
|
||||
export type { VxeGridListeners, VxeGridProps } from 'vxe-table';
|
122
packages/effects/plugins/src/vxe-table/init.ts
Normal file
122
packages/effects/plugins/src/vxe-table/init.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { SetupVxeTable } from './types';
|
||||
|
||||
import { defineComponent, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
|
||||
import {
|
||||
VxeButton,
|
||||
VxeButtonGroup,
|
||||
// VxeFormGather,
|
||||
// VxeForm,
|
||||
// VxeFormItem,
|
||||
VxeIcon,
|
||||
VxeInput,
|
||||
VxeLoading,
|
||||
VxePager,
|
||||
// VxeList,
|
||||
// VxeModal,
|
||||
// VxeOptgroup,
|
||||
// VxeOption,
|
||||
// VxePulldown,
|
||||
// VxeRadio,
|
||||
// VxeRadioButton,
|
||||
// VxeRadioGroup,
|
||||
VxeSelect,
|
||||
VxeTooltip,
|
||||
VxeUI,
|
||||
// VxeSwitch,
|
||||
// VxeTextarea,
|
||||
} from 'vxe-pc-ui';
|
||||
import enUS from 'vxe-pc-ui/lib/language/en-US';
|
||||
|
||||
// 导入默认的语言
|
||||
import zhCN from 'vxe-pc-ui/lib/language/zh-CN';
|
||||
import {
|
||||
VxeColgroup,
|
||||
VxeColumn,
|
||||
VxeGrid,
|
||||
VxeTable,
|
||||
VxeToolbar,
|
||||
} from 'vxe-table';
|
||||
|
||||
// 是否加载过
|
||||
let isInit = false;
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let useTableForm: typeof useVbenForm;
|
||||
|
||||
// 部分组件,如果没注册,vxe-table 会报错,这里实际没用组件,只是为了不报错,同时可以减少打包体积
|
||||
const createVirtualComponent = (name = '') => {
|
||||
return defineComponent({
|
||||
name,
|
||||
});
|
||||
};
|
||||
|
||||
export function initVxeTable() {
|
||||
if (isInit) {
|
||||
return;
|
||||
}
|
||||
|
||||
VxeUI.component(VxeTable);
|
||||
VxeUI.component(VxeColumn);
|
||||
VxeUI.component(VxeColgroup);
|
||||
VxeUI.component(VxeLoading);
|
||||
VxeUI.component(VxeGrid);
|
||||
VxeUI.component(VxeToolbar);
|
||||
|
||||
VxeUI.component(VxeButton);
|
||||
VxeUI.component(VxeButtonGroup);
|
||||
// VxeUI.component(VxeCheckbox);
|
||||
// VxeUI.component(VxeCheckboxGroup);
|
||||
VxeUI.component(createVirtualComponent('VxeForm'));
|
||||
// VxeUI.component(VxeFormGather);
|
||||
// VxeUI.component(VxeFormItem);
|
||||
VxeUI.component(VxeIcon);
|
||||
VxeUI.component(VxeInput);
|
||||
// VxeUI.component(VxeList);
|
||||
VxeUI.component(VxeLoading);
|
||||
// VxeUI.component(VxeModal);
|
||||
// VxeUI.component(VxeOptgroup);
|
||||
// VxeUI.component(VxeOption);
|
||||
VxeUI.component(VxePager);
|
||||
// VxeUI.component(VxePulldown);
|
||||
// VxeUI.component(VxeRadio);
|
||||
// VxeUI.component(VxeRadioButton);
|
||||
// VxeUI.component(VxeRadioGroup);
|
||||
VxeUI.component(VxeSelect);
|
||||
// VxeUI.component(VxeSwitch);
|
||||
// VxeUI.component(VxeTextarea);
|
||||
VxeUI.component(VxeTooltip);
|
||||
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
|
||||
const { configVxeTable, useVbenForm } = setupOptions;
|
||||
|
||||
initVxeTable();
|
||||
useTableForm = useVbenForm;
|
||||
|
||||
const preference = usePreferences();
|
||||
|
||||
const localMap = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => preference.theme.value, () => preference.locale.value],
|
||||
([theme, locale]) => {
|
||||
VxeUI.setTheme(theme === 'dark' ? 'dark' : 'light');
|
||||
VxeUI.setI18n(locale, localMap[locale]);
|
||||
VxeUI.setLanguage(locale);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
configVxeTable(VxeUI);
|
||||
}
|
78
packages/effects/plugins/src/vxe-table/theme.css
Normal file
78
packages/effects/plugins/src/vxe-table/theme.css
Normal file
@@ -0,0 +1,78 @@
|
||||
:root {
|
||||
--vxe-ui-font-color: hsl(var(--foreground));
|
||||
--vxe-ui-font-primary-color: hsl(var(--primary));
|
||||
|
||||
/* --vxe-ui-font-lighten-color: #babdc0;
|
||||
--vxe-ui-font-darken-color: #86898e; */
|
||||
--vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%);
|
||||
|
||||
/* base */
|
||||
--vxe-ui-base-popup-border-color: hsl(var(--border));
|
||||
|
||||
/* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */
|
||||
|
||||
/* layout */
|
||||
--vxe-ui-layout-background-color: hsl(var(--background));
|
||||
--vxe-ui-table-resizable-line-color: hsl(var(--border));
|
||||
|
||||
/* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
|
||||
--vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
|
||||
|
||||
/* input */
|
||||
--vxe-ui-input-border-color: hsl(var(--border));
|
||||
|
||||
/* --vxe-ui-input-placeholder-color: #8d9095; */
|
||||
|
||||
/* --vxe-ui-input-disabled-background-color: #262727; */
|
||||
|
||||
/* loading */
|
||||
--vxe-ui-loading-background-color: hsl(var(--overlay-content));
|
||||
|
||||
/* table */
|
||||
--vxe-ui-table-header-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-border-color: hsl(var(--border));
|
||||
--vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover));
|
||||
--vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%);
|
||||
--vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-radio-checked-background-color: hsl(
|
||||
var(--accent-hover)
|
||||
);
|
||||
--vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-checkbox-checked-background-color: hsl(
|
||||
var(--accent-hover)
|
||||
);
|
||||
--vxe-ui-table-row-current-background-color: hsl(var(--accent));
|
||||
--vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover));
|
||||
|
||||
/* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */
|
||||
}
|
||||
|
||||
.vxe-pager {
|
||||
.vxe-pager--prev-btn:not(.is--disabled):active,
|
||||
.vxe-pager--next-btn:not(.is--disabled):active,
|
||||
.vxe-pager--num-btn:not(.is--disabled):active,
|
||||
.vxe-pager--jump-prev:not(.is--disabled):active,
|
||||
.vxe-pager--jump-next:not(.is--disabled):active,
|
||||
.vxe-pager--prev-btn:not(.is--disabled):focus,
|
||||
.vxe-pager--next-btn:not(.is--disabled):focus,
|
||||
.vxe-pager--num-btn:not(.is--disabled):focus,
|
||||
.vxe-pager--jump-prev:not(.is--disabled):focus,
|
||||
.vxe-pager--jump-next:not(.is--disabled):focus {
|
||||
color: hsl(var(--accent-foreground));
|
||||
background-color: hsl(var(--accent));
|
||||
border: 1px solid hsl(var(--border));
|
||||
box-shadow: 0 0 0 1px hsl(var(--border));
|
||||
}
|
||||
|
||||
.vxe-pager {
|
||||
&--wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--sizes {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
53
packages/effects/plugins/src/vxe-table/types.ts
Normal file
53
packages/effects/plugins/src/vxe-table/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { DeepPartial } from '@vben/types';
|
||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||
import type {
|
||||
VxeGridListeners,
|
||||
VxeGridProps as VxeTableGridProps,
|
||||
VxeUIExport,
|
||||
} from 'vxe-table';
|
||||
|
||||
import type { VxeGridApi } from './api';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { useVbenForm } from '@vben-core/form-ui';
|
||||
|
||||
export interface VxePaginationInfo {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface VxeGridProps {
|
||||
/**
|
||||
* 组件class
|
||||
*/
|
||||
class?: any;
|
||||
/**
|
||||
* vxe-grid class
|
||||
*/
|
||||
gridClass?: any;
|
||||
/**
|
||||
* vxe-grid 配置
|
||||
*/
|
||||
gridOptions?: DeepPartial<VxeTableGridProps>;
|
||||
/**
|
||||
* vxe-grid 事件
|
||||
*/
|
||||
gridEvents?: DeepPartial<VxeGridListeners>;
|
||||
/**
|
||||
* 表单配置
|
||||
*/
|
||||
formOptions?: VbenFormProps;
|
||||
}
|
||||
|
||||
export type ExtendedVxeGridApi = {
|
||||
useStore: <T = NoInfer<VxeGridProps>>(
|
||||
selector?: (state: NoInfer<VxeGridProps>) => T,
|
||||
) => Readonly<Ref<T>>;
|
||||
} & VxeGridApi;
|
||||
|
||||
export interface SetupVxeTable {
|
||||
configVxeTable: (ui: VxeUIExport) => void;
|
||||
useVbenForm: typeof useVbenForm;
|
||||
}
|
42
packages/effects/plugins/src/vxe-table/use-vxe-grid.ts
Normal file
42
packages/effects/plugins/src/vxe-table/use-vxe-grid.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
|
||||
|
||||
import { defineComponent, h, onBeforeUnmount } from 'vue';
|
||||
|
||||
import { useStore } from '@vben-core/shared/store';
|
||||
|
||||
import { VxeGridApi } from './api';
|
||||
import VxeGrid from './use-vxe-grid.vue';
|
||||
|
||||
export function useVbenVxeGrid(options: VxeGridProps) {
|
||||
// const IS_REACTIVE = isReactive(options);
|
||||
const api = new VxeGridApi(options);
|
||||
const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi;
|
||||
extendedApi.useStore = (selector) => {
|
||||
return useStore(api.store, selector);
|
||||
};
|
||||
|
||||
const Grid = defineComponent(
|
||||
(props: VxeGridProps, { attrs, slots }) => {
|
||||
onBeforeUnmount(() => {
|
||||
api.unmount();
|
||||
});
|
||||
return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots);
|
||||
},
|
||||
{
|
||||
inheritAttrs: false,
|
||||
name: 'VbenVxeGrid',
|
||||
},
|
||||
);
|
||||
// Add reactivity support
|
||||
// if (IS_REACTIVE) {
|
||||
// watch(
|
||||
// () => options,
|
||||
// () => {
|
||||
// api.setState(options);
|
||||
// },
|
||||
// { immediate: true },
|
||||
// );
|
||||
// }
|
||||
|
||||
return [Grid, extendedApi] as const;
|
||||
}
|
264
packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
Normal file
264
packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormProps } from '@vben-core/form-ui';
|
||||
import type {
|
||||
VxeGridInstance,
|
||||
VxeGridProps as VxeTableGridProps,
|
||||
} from 'vxe-table';
|
||||
|
||||
import type { ExtendedVxeGridApi, VxeGridProps } from './types';
|
||||
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
toRaw,
|
||||
useSlots,
|
||||
useTemplateRef,
|
||||
} from 'vue';
|
||||
|
||||
import { usePriorityValues } from '@vben/hooks';
|
||||
import { EmptyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils';
|
||||
import { VbenLoading } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { VxeGrid, VxeUI } from 'vxe-table';
|
||||
|
||||
import { useTableForm } from './init';
|
||||
|
||||
import 'vxe-table/styles/cssvar.scss';
|
||||
import 'vxe-pc-ui/styles/cssvar.scss';
|
||||
import './theme.css';
|
||||
|
||||
interface Props extends VxeGridProps {
|
||||
api: ExtendedVxeGridApi;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const gridRef = useTemplateRef<VxeGridInstance>('gridRef');
|
||||
|
||||
const state = props.api?.useStore?.();
|
||||
|
||||
const {
|
||||
gridOptions,
|
||||
class: className,
|
||||
gridClass,
|
||||
gridEvents,
|
||||
formOptions,
|
||||
} = usePriorityValues(props, state);
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const [Form, formApi] = useTableForm({});
|
||||
|
||||
const showToolbar = computed(() => {
|
||||
return !!slots['toolbar-actions']?.() || !!slots['toolbar-tools']?.();
|
||||
});
|
||||
|
||||
const options = computed(() => {
|
||||
const slotActions = slots['toolbar-actions']?.();
|
||||
const slotTools = slots['toolbar-tools']?.();
|
||||
|
||||
const forceUseToolbarOptions = showToolbar.value
|
||||
? {
|
||||
toolbarConfig: {
|
||||
slots: {
|
||||
...(slotActions ? { buttons: 'toolbar-actions' } : {}),
|
||||
...(slotTools ? { tools: 'toolbar-tools' } : {}),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
const mergedOptions: VxeTableGridProps = cloneDeep(
|
||||
mergeWithArrayOverride(
|
||||
{},
|
||||
forceUseToolbarOptions,
|
||||
toRaw(gridOptions.value),
|
||||
),
|
||||
);
|
||||
|
||||
if (mergedOptions.proxyConfig) {
|
||||
const { ajax } = mergedOptions.proxyConfig;
|
||||
mergedOptions.proxyConfig.enabled = !!ajax;
|
||||
// 不自动加载数据, 由组件控制
|
||||
mergedOptions.proxyConfig.autoLoad = false;
|
||||
}
|
||||
|
||||
if (!showToolbar.value && mergedOptions.toolbarConfig) {
|
||||
mergedOptions.toolbarConfig.enabled = false;
|
||||
}
|
||||
|
||||
if (mergedOptions.pagerConfig) {
|
||||
mergedOptions.pagerConfig = mergeWithArrayOverride(
|
||||
{},
|
||||
mergedOptions.pagerConfig,
|
||||
{
|
||||
pageSize: 20,
|
||||
background: true,
|
||||
pageSizes: [10, 20, 30, 50, 100, 200],
|
||||
className: 'mt-2 w-full',
|
||||
layouts: [
|
||||
'Total',
|
||||
'Sizes',
|
||||
'Home',
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'Number',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'End',
|
||||
// 'FullJump',
|
||||
] as any[],
|
||||
size: 'mini' as const,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (mergedOptions.formConfig) {
|
||||
mergedOptions.formConfig.enabled = false;
|
||||
}
|
||||
return mergedOptions;
|
||||
});
|
||||
|
||||
const events = computed(() => {
|
||||
return {
|
||||
...gridEvents.value,
|
||||
};
|
||||
});
|
||||
|
||||
const vbenFormOptions = computed(() => {
|
||||
const defaultFormProps: VbenFormProps = {
|
||||
handleSubmit: async () => {
|
||||
const formValues = formApi.form.values;
|
||||
props.api.reload(formValues);
|
||||
},
|
||||
handleReset: async () => {
|
||||
formApi.resetForm();
|
||||
const formValues = formApi.form.values;
|
||||
props.api.reload(formValues);
|
||||
},
|
||||
collapseTriggerResize: true,
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
text: $t('common.query'),
|
||||
},
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
};
|
||||
return {
|
||||
...mergeWithArrayOverride({}, formOptions.value, defaultFormProps),
|
||||
};
|
||||
});
|
||||
|
||||
const delegatedSlots = computed(() => {
|
||||
const resultSlots: string[] = [];
|
||||
|
||||
for (const key of Object.keys(slots)) {
|
||||
if (!['empty', 'form', 'loading'].includes(key)) {
|
||||
resultSlots.push(key);
|
||||
}
|
||||
}
|
||||
return resultSlots;
|
||||
});
|
||||
|
||||
const delegatedFormSlots = computed(() => {
|
||||
const resultSlots: string[] = [];
|
||||
|
||||
for (const key of Object.keys(slots)) {
|
||||
if (key.startsWith('form-')) {
|
||||
resultSlots.push(key);
|
||||
}
|
||||
}
|
||||
return resultSlots;
|
||||
});
|
||||
|
||||
async function init() {
|
||||
await nextTick();
|
||||
const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
|
||||
const defaultGridOptions: VxeTableGridProps = mergeWithArrayOverride(
|
||||
{},
|
||||
toRaw(gridOptions.value),
|
||||
toRaw(globalGridConfig),
|
||||
);
|
||||
// 内部主动加载数据,防止form的默认值影响
|
||||
const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
|
||||
const enableProxyConfig = options.value.proxyConfig?.enabled;
|
||||
if (enableProxyConfig && autoLoad) {
|
||||
props.api.reload(formApi.form.values);
|
||||
}
|
||||
|
||||
// form 由 vben-form代替,所以不适配formConfig,这里给出警告
|
||||
const formConfig = defaultGridOptions.formConfig;
|
||||
if (formConfig?.enabled) {
|
||||
console.warn(
|
||||
'[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
props.api?.mount?.(gridRef.value);
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('bg-card h-full rounded-md', className)">
|
||||
<VxeGrid
|
||||
ref="gridRef"
|
||||
:class="
|
||||
cn(
|
||||
'p-2',
|
||||
{
|
||||
'pt-0': showToolbar && !formOptions,
|
||||
},
|
||||
gridClass,
|
||||
)
|
||||
"
|
||||
v-bind="options"
|
||||
v-on="events"
|
||||
>
|
||||
<template
|
||||
v-for="slotName in delegatedSlots"
|
||||
:key="slotName"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot :name="slotName" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
<template #form>
|
||||
<div v-if="formOptions" class="relative rounded py-3 pb-6">
|
||||
<slot name="form">
|
||||
<Form v-bind="vbenFormOptions">
|
||||
<template
|
||||
v-for="slotName in delegatedFormSlots"
|
||||
:key="slotName"
|
||||
#[slotName]="slotProps"
|
||||
>
|
||||
<slot :name="slotName" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
</Form>
|
||||
</slot>
|
||||
<div
|
||||
class="bg-background-deep z-100 absolute -left-2 bottom-2 h-4 w-[calc(100%+1rem)] overflow-hidden"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #loading>
|
||||
<slot name="loading">
|
||||
<VbenLoading :spinning="true" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<EmptyIcon class="mx-auto" />
|
||||
<div class="mt-2">{{ $t('common.noData') }}</div>
|
||||
</slot>
|
||||
</template>
|
||||
</VxeGrid>
|
||||
</div>
|
||||
</template>
|
1
packages/effects/plugins/tailwind.config.mjs
Normal file
1
packages/effects/plugins/tailwind.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config';
|
@@ -31,7 +31,8 @@
|
||||
"confirm": "Comfirm",
|
||||
"noData": "No Data",
|
||||
"refresh": "Refresh",
|
||||
"loadingMenu": "Loading Menu"
|
||||
"loadingMenu": "Loading Menu",
|
||||
"query": "Search"
|
||||
},
|
||||
"fallback": {
|
||||
"pageNotFound": "Oops! Page Not Found",
|
||||
|
@@ -31,7 +31,8 @@
|
||||
"confirm": "确认",
|
||||
"noData": "暂无数据",
|
||||
"refresh": "刷新",
|
||||
"loadingMenu": "加载菜单中"
|
||||
"loadingMenu": "加载菜单中",
|
||||
"query": "查询"
|
||||
},
|
||||
"fallback": {
|
||||
"pageNotFound": "哎呀!未找到页面",
|
||||
|
Reference in New Issue
Block a user