系统菜单部分功能及planToAdd文件中的config.ts文件修改
This commit is contained in:
4
hx-ai-intelligent/src/api/menuSystem.ts
Normal file
4
hx-ai-intelligent/src/api/menuSystem.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { BASE_URL } from './index';
|
||||
export enum menuS {
|
||||
queryMenuPage = `${BASE_URL}/deviceInfo/queryDevicePage`, // 菜单列表
|
||||
}
|
||||
6
hx-ai-intelligent/src/components/Drawer/index.ts
Normal file
6
hx-ai-intelligent/src/components/Drawer/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { withInstall } from '/@/utils';
|
||||
import basicDrawer from './src/BasicDrawer.vue';
|
||||
|
||||
export const BasicDrawer = withInstall(basicDrawer);
|
||||
export * from './src/typing';
|
||||
export { useDrawer, useDrawerInner } from './src/useDrawer';
|
||||
255
hx-ai-intelligent/src/components/Drawer/src/BasicDrawer.vue
Normal file
255
hx-ai-intelligent/src/components/Drawer/src/BasicDrawer.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
|
||||
<template #title v-if="!$slots.title">
|
||||
<DrawerHeader :title="getMergeProps.title" :isDetail="isDetail" :showDetailBack="showDetailBack" @close="onClose">
|
||||
<template #titleToolbar>
|
||||
<slot name="titleToolbar"></slot>
|
||||
</template>
|
||||
</DrawerHeader>
|
||||
</template>
|
||||
<template v-else #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
|
||||
<ScrollContainer :style="getScrollContentStyle" v-loading="getLoading" :loading-tip="loadingText || t('common.loadingText')">
|
||||
<slot></slot>
|
||||
</ScrollContainer>
|
||||
<DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</DrawerFooter>
|
||||
</Drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { DrawerInstance, DrawerProps } from './typing';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent, ref, computed, watch, unref, nextTick, toRaw, getCurrentInstance } from 'vue';
|
||||
import { Drawer } from 'ant-design-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isFunction, isNumber } from '/@/utils/is';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import DrawerFooter from './components/DrawerFooter.vue';
|
||||
import DrawerHeader from './components/DrawerHeader.vue';
|
||||
import { ScrollContainer } from '/@/components/Container';
|
||||
import { basicProps } from './props';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Drawer, ScrollContainer, DrawerFooter, DrawerHeader },
|
||||
inheritAttrs: false,
|
||||
props: basicProps,
|
||||
emits: ['visible-change', 'open-change', 'ok', 'close', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const visibleRef = ref(false);
|
||||
const attrs = useAttrs();
|
||||
const propsRef = ref<Partial<Nullable<DrawerProps>>>(null);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { prefixVar, prefixCls } = useDesign('basic-drawer');
|
||||
|
||||
const drawerInstance: DrawerInstance = {
|
||||
setDrawerProps: setDrawerProps,
|
||||
emitVisible: undefined,
|
||||
};
|
||||
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
instance && emit('register', drawerInstance, instance.uid);
|
||||
|
||||
const getMergeProps = computed((): DrawerProps => {
|
||||
// update-begin--author:liaozhiyang---date:20240320---for:【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误(toRaw改成cloneDeep,否则props的变化不会触发computed)
|
||||
return { ...deepMerge(cloneDeep(props), unref(propsRef)) };
|
||||
// update-end--author:liaozhiyang---date:20240320---for:【QQYUN-8389】vue3.4以上版本导致角色抽屉隐藏footer逻辑错误(toRaw改成cloneDeep,否则props的变化不会触发computed)
|
||||
});
|
||||
|
||||
const getProps = computed((): DrawerProps => {
|
||||
// update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
const opt = {
|
||||
placement: 'right',
|
||||
...unref(attrs),
|
||||
...unref(getMergeProps),
|
||||
open: unref(visibleRef),
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
opt.title = undefined;
|
||||
let { isDetail, width, wrapClassName, getContainer } = opt;
|
||||
if (isDetail) {
|
||||
if (!width) {
|
||||
opt.width = '100%';
|
||||
}
|
||||
const detailCls = `${prefixCls}__detail`;
|
||||
wrapClassName = opt['class'] ? opt['class'] : wrapClassName;
|
||||
opt.class = wrapClassName ? `${wrapClassName} ${detailCls}` : detailCls;
|
||||
|
||||
if (!getContainer) {
|
||||
// TODO type error?
|
||||
opt.getContainer = `.${prefixVar}-layout-content` as any;
|
||||
}
|
||||
}
|
||||
console.log('getProps:opt',opt);
|
||||
return opt as DrawerProps;
|
||||
});
|
||||
|
||||
const getBindValues = computed((): DrawerProps => {
|
||||
return {
|
||||
...attrs,
|
||||
...unref(getProps),
|
||||
};
|
||||
});
|
||||
|
||||
// Custom implementation of the bottom button,
|
||||
const getFooterHeight = computed(() => {
|
||||
const { footerHeight, showFooter } = unref(getProps);
|
||||
if (showFooter && footerHeight) {
|
||||
return isNumber(footerHeight) ? `${footerHeight}px` : `${footerHeight.replace('px', '')}px`;
|
||||
}
|
||||
return `0px`;
|
||||
});
|
||||
|
||||
const getScrollContentStyle = computed((): CSSProperties => {
|
||||
const footerHeight = unref(getFooterHeight);
|
||||
return {
|
||||
position: 'relative',
|
||||
height: `calc(100% - ${footerHeight})`,
|
||||
};
|
||||
});
|
||||
|
||||
const getLoading = computed(() => {
|
||||
return !!unref(getProps)?.loading;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) visibleRef.value = newVal;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) visibleRef.value = newVal;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => visibleRef.value,
|
||||
(visible) => {
|
||||
nextTick(() => {
|
||||
emit('visible-change', visible);
|
||||
emit('open-change', visible);
|
||||
instance && drawerInstance.emitVisible?.(visible, instance.uid);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Cancel event
|
||||
async function onClose(e: Recordable) {
|
||||
const { closeFunc } = unref(getProps);
|
||||
emit('close', e);
|
||||
if (closeFunc && isFunction(closeFunc)) {
|
||||
const res = await closeFunc();
|
||||
visibleRef.value = !res;
|
||||
return;
|
||||
}
|
||||
visibleRef.value = false;
|
||||
}
|
||||
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
// Keep the last setDrawerProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
|
||||
|
||||
if (Reflect.has(props, 'visible')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
}
|
||||
if (Reflect.has(props, 'open')) {
|
||||
visibleRef.value = !!props.open;
|
||||
}
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
}
|
||||
|
||||
return {
|
||||
onClose,
|
||||
t,
|
||||
prefixCls,
|
||||
getMergeProps: getMergeProps as any,
|
||||
getScrollContentStyle,
|
||||
getProps: getProps as any,
|
||||
getLoading,
|
||||
getBindValues,
|
||||
getFooterHeight,
|
||||
handleOk,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@header-height: 60px;
|
||||
@detail-header-height: 40px;
|
||||
@prefix-cls: ~'@{namespace}-basic-drawer';
|
||||
@prefix-cls-detail: ~'@{namespace}-basic-drawer__detail';
|
||||
|
||||
.@{prefix-cls} {
|
||||
.ant-drawer-wrapper-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
&:hover {
|
||||
color: @error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
height: calc(100% - @header-height);
|
||||
padding: 0;
|
||||
background-color: @component-background;
|
||||
|
||||
.scrollbar__wrap {
|
||||
padding: 16px !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
> .scrollbar > .scrollbar__bar.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls-detail} {
|
||||
position: absolute;
|
||||
|
||||
.ant-drawer-header {
|
||||
width: 100%;
|
||||
height: @detail-header-height;
|
||||
padding: 0;
|
||||
border-top: 1px solid @border-color-base;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ant-drawer-title {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
height: @detail-header-height;
|
||||
line-height: @detail-header-height;
|
||||
}
|
||||
|
||||
.scrollbar__wrap {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
height: calc(100% - @detail-header-height);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
|
||||
<template v-if="!$slots.footer">
|
||||
<slot name="insertFooter"></slot>
|
||||
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
|
||||
{{ cancelText }}
|
||||
</a-button>
|
||||
<slot name="centerFooter"></slot>
|
||||
<a-button :type="okType" @click="handleOk" v-bind="okButtonProps" class="mr-2" :loading="confirmLoading" v-if="showOkBtn">
|
||||
{{ okText }}
|
||||
</a-button>
|
||||
<slot name="appendFooter"></slot>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
import { footerProps } from '../props';
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerFooter',
|
||||
props: {
|
||||
...footerProps,
|
||||
height: {
|
||||
type: String,
|
||||
default: '60px',
|
||||
},
|
||||
},
|
||||
emits: ['ok', 'close'],
|
||||
setup(props, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-footer');
|
||||
|
||||
const getStyle = computed((): CSSProperties => {
|
||||
const heightStr = `${props.height}`;
|
||||
return {
|
||||
height: heightStr,
|
||||
lineHeight: heightStr,
|
||||
};
|
||||
});
|
||||
|
||||
function handleOk() {
|
||||
emit('ok');
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
return { handleOk, prefixCls, handleClose, getStyle };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-drawer-footer';
|
||||
@footer-height: 60px;
|
||||
.@{prefix-cls} {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 0 12px 0 20px;
|
||||
text-align: right;
|
||||
background-color: @component-background;
|
||||
border-top: 1px solid @border-color-base;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<BasicTitle v-if="!isDetail" :class="[prefixCls, 'is-drawer']">
|
||||
<slot name="title"></slot>
|
||||
{{ !$slots.title ? title : '' }}
|
||||
</BasicTitle>
|
||||
|
||||
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
|
||||
<span :class="`${prefixCls}__twrap`">
|
||||
<span @click="handleClose" v-if="showDetailBack">
|
||||
<ArrowLeftOutlined :class="`${prefixCls}__back`" />
|
||||
</span>
|
||||
<span v-if="title">{{ title }}</span>
|
||||
</span>
|
||||
|
||||
<span :class="`${prefixCls}__toolbar`">
|
||||
<slot name="titleToolbar"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { BasicTitle } from '/@/components/Basic';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
export default defineComponent({
|
||||
name: 'BasicDrawerHeader',
|
||||
components: { BasicTitle, ArrowLeftOutlined },
|
||||
props: {
|
||||
isDetail: propTypes.bool,
|
||||
showDetailBack: propTypes.bool,
|
||||
title: propTypes.string,
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(_, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-drawer-header');
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
return { prefixCls, handleClose };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-drawer-header';
|
||||
@footer-height: 60px;
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
||||
&__back {
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__twrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
padding-right: 50px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
46
hx-ai-intelligent/src/components/Drawer/src/props.ts
Normal file
46
hx-ai-intelligent/src/components/Drawer/src/props.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
export const footerProps = {
|
||||
confirmLoading: { type: Boolean },
|
||||
/**
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: { type: Boolean, default: true },
|
||||
cancelButtonProps: Object as PropType<Recordable>,
|
||||
cancelText: { type: String, default: t('common.cancelText') },
|
||||
/**
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: { type: Boolean, default: true },
|
||||
okButtonProps: Object as PropType<Recordable>,
|
||||
okText: { type: String, default: t('common.okText') },
|
||||
okType: { type: String, default: 'primary' },
|
||||
showFooter: { type: Boolean },
|
||||
footerHeight: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: 60,
|
||||
},
|
||||
};
|
||||
export const basicProps = {
|
||||
class: {type: [String, Object, Array]},
|
||||
isDetail: { type: Boolean },
|
||||
title: { type: String, default: '' },
|
||||
loadingText: { type: String },
|
||||
showDetailBack: { type: Boolean, default: true },
|
||||
visible: { type: Boolean },
|
||||
open: { type: Boolean },
|
||||
loading: { type: Boolean },
|
||||
maskClosable: { type: Boolean, default: true },
|
||||
getContainer: {
|
||||
type: [Object, String] as PropType<any>,
|
||||
},
|
||||
closeFunc: {
|
||||
type: [Function, Object] as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
destroyOnClose: { type: Boolean },
|
||||
...footerProps,
|
||||
};
|
||||
199
hx-ai-intelligent/src/components/Drawer/src/typing.ts
Normal file
199
hx-ai-intelligent/src/components/Drawer/src/typing.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes';
|
||||
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue';
|
||||
import type { ScrollContainerOptions } from '/@/components/Container/index';
|
||||
|
||||
export interface DrawerInstance {
|
||||
setDrawerProps: (props: Partial<DrawerProps> | boolean) => void;
|
||||
emitVisible?: (visible: boolean, uid: number) => void;
|
||||
}
|
||||
|
||||
export interface ReturnMethods extends DrawerInstance {
|
||||
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
|
||||
closeDrawer: () => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
getOpen?: ComputedRef<boolean>;
|
||||
}
|
||||
|
||||
export type RegisterFn = (drawerInstance: DrawerInstance, uuid?: string) => void;
|
||||
|
||||
export interface ReturnInnerMethods extends DrawerInstance {
|
||||
closeDrawer: () => void;
|
||||
changeLoading: (loading: boolean) => void;
|
||||
changeOkLoading: (loading: boolean) => void;
|
||||
getVisible?: ComputedRef<boolean>;
|
||||
getOpen?: ComputedRef<boolean>;
|
||||
}
|
||||
|
||||
export type UseDrawerReturnType = [RegisterFn, ReturnMethods];
|
||||
|
||||
export type UseDrawerInnerReturnType = [RegisterFn, ReturnInnerMethods];
|
||||
|
||||
export interface DrawerFooterProps {
|
||||
showOkBtn: boolean;
|
||||
showCancelBtn: boolean;
|
||||
/**
|
||||
* Text of the Cancel button
|
||||
* @default 'cancel'
|
||||
* @type string
|
||||
*/
|
||||
cancelText: string;
|
||||
/**
|
||||
* Text of the OK button
|
||||
* @default 'OK'
|
||||
* @type string
|
||||
*/
|
||||
okText: string;
|
||||
|
||||
/**
|
||||
* Button type of the OK button
|
||||
* @default 'primary'
|
||||
* @type string
|
||||
*/
|
||||
okType: 'primary' | 'danger' | 'dashed' | 'ghost' | 'default';
|
||||
/**
|
||||
* The ok button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
okButtonProps: { props: ButtonProps; on: {} };
|
||||
|
||||
/**
|
||||
* The cancel button props, follow jsx rules
|
||||
* @type object
|
||||
*/
|
||||
cancelButtonProps: { props: ButtonProps; on: {} };
|
||||
/**
|
||||
* Whether to apply loading visual effect for OK button or not
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
confirmLoading: boolean;
|
||||
|
||||
showFooter: boolean;
|
||||
footerHeight: string | number;
|
||||
}
|
||||
export interface DrawerProps extends DrawerFooterProps {
|
||||
isDetail?: boolean;
|
||||
loading?: boolean;
|
||||
showDetailBack?: boolean;
|
||||
visible?: boolean;
|
||||
open?: boolean;
|
||||
/**
|
||||
* Built-in ScrollContainer component configuration
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
closeFunc?: () => Promise<any>;
|
||||
triggerWindowResize?: boolean;
|
||||
/**
|
||||
* Whether a close (x) button is visible on top right of the Drawer dialog or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
closable?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to unmount child components on closing drawer or not.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
|
||||
/**
|
||||
* Return the mounted node for Drawer.
|
||||
* @default 'body'
|
||||
* @type any ( HTMLElement| () => HTMLElement | string)
|
||||
*/
|
||||
getContainer?: () => HTMLElement | string;
|
||||
|
||||
/**
|
||||
* Whether to show mask or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
mask?: boolean;
|
||||
|
||||
/**
|
||||
* Clicking on the mask (area outside the Drawer) to close the Drawer or not.
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
maskClosable?: boolean;
|
||||
|
||||
/**
|
||||
* Style for Drawer's mask element.
|
||||
* @default {}
|
||||
* @type object
|
||||
*/
|
||||
maskStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* The title for Drawer.
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* The class name of the container of the Drawer dialog.
|
||||
* @type string
|
||||
*/
|
||||
class?: string;
|
||||
// 兼容老版本的写法(后续可能会删除,优先写class)
|
||||
wrapClassName?: string;
|
||||
|
||||
/**
|
||||
* Style of wrapper element which **contains mask** compare to `drawerStyle`
|
||||
* @type object
|
||||
*/
|
||||
wrapStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Style of the popup layer element
|
||||
* @type object
|
||||
*/
|
||||
drawerStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Style of floating layer, typically used for adjusting its position.
|
||||
* @type object
|
||||
*/
|
||||
bodyStyle?: CSSProperties;
|
||||
headerStyle?: CSSProperties;
|
||||
|
||||
/**
|
||||
* Width of the Drawer dialog.
|
||||
* @default 256
|
||||
* @type string | number
|
||||
*/
|
||||
width?: string | number;
|
||||
|
||||
/**
|
||||
* placement is top or bottom, height of the Drawer dialog.
|
||||
* @type string | number
|
||||
*/
|
||||
height?: string | number;
|
||||
|
||||
/**
|
||||
* The z-index of the Drawer.
|
||||
* @default 1000
|
||||
* @type number
|
||||
*/
|
||||
zIndex?: number;
|
||||
|
||||
/**
|
||||
* The placement of the Drawer.
|
||||
* @default 'right'
|
||||
* @type string
|
||||
*/
|
||||
placement?: 'top' | 'right' | 'bottom' | 'left';
|
||||
afterVisibleChange?: (visible?: boolean) => void;
|
||||
keyboard?: boolean;
|
||||
/**
|
||||
* Specify a callback that will be called when a user clicks mask, close button or Cancel button.
|
||||
*/
|
||||
onClose?: (e?: Event) => void;
|
||||
}
|
||||
export interface DrawerActionType {
|
||||
scrollBottom: () => void;
|
||||
scrollTo: (to: number) => void;
|
||||
getScrollWrap: () => Element | null;
|
||||
}
|
||||
156
hx-ai-intelligent/src/components/Drawer/src/useDrawer.ts
Normal file
156
hx-ai-intelligent/src/components/Drawer/src/useDrawer.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import type { UseDrawerReturnType, DrawerInstance, ReturnMethods, DrawerProps, UseDrawerInnerReturnType } from './typing';
|
||||
import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw, computed } from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { error } from '/@/utils/log';
|
||||
|
||||
const dataTransferRef = reactive<any>({});
|
||||
|
||||
const visibleData = reactive<{ [key: number]: boolean }>({});
|
||||
|
||||
/**
|
||||
* @description: Applicable to separate drawer and call outside
|
||||
*/
|
||||
export function useDrawer(): UseDrawerReturnType {
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('useDrawer() can only be used inside setup() or functional components!');
|
||||
}
|
||||
const drawer = ref<DrawerInstance | null>(null);
|
||||
const loaded = ref<Nullable<boolean>>(false);
|
||||
const uid = ref<string>('');
|
||||
|
||||
function register(drawerInstance: DrawerInstance, uuid: string) {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawer.value = null;
|
||||
loaded.value = null;
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
});
|
||||
|
||||
if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
|
||||
return;
|
||||
}
|
||||
uid.value = uuid;
|
||||
drawer.value = drawerInstance;
|
||||
loaded.value = true;
|
||||
|
||||
drawerInstance.emitVisible = (visible: boolean, uid: number) => {
|
||||
visibleData[uid] = visible;
|
||||
};
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawer);
|
||||
if (!instance) {
|
||||
error('useDrawer instance is undefined!');
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const methods: ReturnMethods = {
|
||||
setDrawerProps: (props: Partial<DrawerProps>): void => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uid)];
|
||||
}),
|
||||
|
||||
getOpen: computed((): boolean => {
|
||||
return visibleData[~~unref(uid)];
|
||||
}),
|
||||
|
||||
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
|
||||
// update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
getInstance()?.setDrawerProps({
|
||||
open: visible,
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
if (!data) return;
|
||||
|
||||
if (openOnSet) {
|
||||
dataTransferRef[unref(uid)] = null;
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
return;
|
||||
}
|
||||
const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data));
|
||||
if (!equal) {
|
||||
dataTransferRef[unref(uid)] = toRaw(data);
|
||||
}
|
||||
},
|
||||
closeDrawer: () => {
|
||||
// update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
getInstance()?.setDrawerProps({ open: false });
|
||||
// update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
},
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
|
||||
export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
|
||||
const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null);
|
||||
const currentInstance = getCurrentInstance();
|
||||
const uidRef = ref<string>('');
|
||||
|
||||
if (!getCurrentInstance()) {
|
||||
throw new Error('useDrawerInner() can only be used inside setup() or functional components!');
|
||||
}
|
||||
|
||||
const getInstance = () => {
|
||||
const instance = unref(drawerInstanceRef);
|
||||
if (!instance) {
|
||||
error('useDrawerInner instance is undefined!');
|
||||
return;
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
const register = (modalInstance: DrawerInstance, uuid: string) => {
|
||||
isProdMode() &&
|
||||
tryOnUnmounted(() => {
|
||||
drawerInstanceRef.value = null;
|
||||
});
|
||||
|
||||
uidRef.value = uuid;
|
||||
drawerInstanceRef.value = modalInstance;
|
||||
currentInstance?.emit('register', modalInstance, uuid);
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
const data = dataTransferRef[unref(uidRef)];
|
||||
if (!data) return;
|
||||
if (!callbackFn || !isFunction(callbackFn)) return;
|
||||
nextTick(() => {
|
||||
callbackFn(data);
|
||||
});
|
||||
});
|
||||
|
||||
return [
|
||||
register,
|
||||
{
|
||||
changeLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ loading });
|
||||
},
|
||||
|
||||
changeOkLoading: (loading = true) => {
|
||||
getInstance()?.setDrawerProps({ confirmLoading: loading });
|
||||
},
|
||||
getVisible: computed((): boolean => {
|
||||
return visibleData[~~unref(uidRef)];
|
||||
}),
|
||||
getOpen: computed((): boolean => {
|
||||
return visibleData[~~unref(uidRef)];
|
||||
}),
|
||||
closeDrawer: () => {
|
||||
getInstance()?.setDrawerProps({ open: false });
|
||||
},
|
||||
|
||||
setDrawerProps: (props: Partial<DrawerProps>) => {
|
||||
getInstance()?.setDrawerProps(props);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
10
hx-ai-intelligent/src/components/Table/index.ts
Normal file
10
hx-ai-intelligent/src/components/Table/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { default as BasicTable } from './src/BasicTable.vue';
|
||||
export { default as TableAction } from './src/components/TableAction.vue';
|
||||
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
|
||||
// export { default as TableImg } from './src/components/TableImg.vue';
|
||||
export * from './src/types/table';
|
||||
// export * from './src/types/pagination';
|
||||
// export * from './src/types/tableAction';
|
||||
// export { useTable } from './src/hooks/useTable';
|
||||
// export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
|
||||
// export type { EditRecordRow } from './src/components/editable';
|
||||
605
hx-ai-intelligent/src/components/Table/src/BasicTable.vue
Normal file
605
hx-ai-intelligent/src/components/Table/src/BasicTable.vue
Normal file
@@ -0,0 +1,605 @@
|
||||
<template>
|
||||
<div ref="wrapRef" :class="getWrapperClass">
|
||||
<!-- <BasicForm
|
||||
:class="{ 'table-search-area-hidden': !getBindValues.formConfig?.schemas?.length }"
|
||||
submitOnReset
|
||||
v-bind="getFormProps"
|
||||
v-if="getBindValues.useSearchForm"
|
||||
:tableAction="tableAction"
|
||||
@register="registerForm"
|
||||
@submit="handleSearchInfoChange"
|
||||
@advanced-change="redoHeight"
|
||||
>
|
||||
<template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</BasicForm> -->
|
||||
|
||||
<!-- antd v3 升级兼容,阻止数据的收集,防止控制台报错 -->
|
||||
<!-- https://antdv.com/docs/vue/migration-v3-cn -->
|
||||
<a-form-item-rest>
|
||||
<!-- 【TV360X-377】关联记录必填影响到了table的输入框和页码样式 -->
|
||||
<a-form-item>
|
||||
<Table ref="tableElRef" v-bind="getBindValues" :rowClassName="getRowClassName" v-show="getEmptyDataIsShowTable" @resizeColumn="handleResizeColumn" @change="handleTableChange">
|
||||
<!-- antd的原生插槽直接传递 -->
|
||||
<template #[item]="data" v-for="item in slotNamesGroup.native" :key="item">
|
||||
<!-- update-begin--author:liaozhiyang---date:20240424---for:【issues/1146】BasicTable使用headerCell全选框出不来 -->
|
||||
<template v-if="item === 'headerCell'">
|
||||
<CustomSelectHeader v-if="isCustomSelection(data.column)" v-bind="selectHeaderProps" />
|
||||
<slot v-else :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<slot v-else :name="item" v-bind="data || {}"></slot>
|
||||
<!-- update-begin--author:liaozhiyang---date:20240424---for:【issues/1146】BasicTable使用headerCell全选框出不来 -->
|
||||
</template>
|
||||
<template #headerCell="{ column }">
|
||||
<!-- update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题 -->
|
||||
<CustomSelectHeader v-if="isCustomSelection(column)" v-bind="selectHeaderProps"/>
|
||||
<HeaderCell v-else :column="column" />
|
||||
<!-- update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题 -->
|
||||
</template>
|
||||
<!-- 增加对antdv3.x兼容 -->
|
||||
<template #bodyCell="data">
|
||||
<!-- update-begin--author:liaozhiyang---date:220230717---for:【issues-179】antd3 一些警告以及报错(针对表格) -->
|
||||
<!-- update-begin--author:liusq---date:20230921---for:【issues/770】slotsBak异常报错的问题,增加判断column是否存在 -->
|
||||
<template v-if="data.column?.slotsBak?.customRender">
|
||||
<!-- update-end--author:liusq---date:20230921---for:【issues/770】slotsBak异常报错的问题,增加判断column是否存在 -->
|
||||
<slot :name="data.column.slotsBak.customRender" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot name="bodyCell" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
<!-- update-begin--author:liaozhiyang---date:22030717---for:【issues-179】antd3 一些警告以及报错(针对表格) -->
|
||||
</template>
|
||||
<!-- update-begin--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计) -->
|
||||
<template v-if="showSummaryRef && !getBindValues.showSummary" #summary="data">
|
||||
<slot name="summary" v-bind="data || {}">
|
||||
<TableSummary :data="data || {}" v-bind="getSummaryProps" />
|
||||
</slot>
|
||||
</template>
|
||||
<!-- update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计) -->
|
||||
</Table>
|
||||
</a-form-item>
|
||||
</a-form-item-rest>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { BasicTableProps, TableActionType, SizeType, ColumnChangeParam, BasicColumn } from './types/table';
|
||||
|
||||
import { defineComponent, ref, computed, unref, toRaw, inject, watchEffect, watch, onUnmounted, onMounted, nextTick } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
// import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
// import { PageWrapperFixedHeightKey } from '/@/components/Page/injectionKey';
|
||||
import CustomSelectHeader from './components/CustomSelectHeader.vue'
|
||||
import expandIcon from './components/ExpandIcon';
|
||||
// import HeaderCell from './components/HeaderCell.vue';
|
||||
import TableSummary from './components/TableSummary';
|
||||
import { InnerHandlers } from './types/table';
|
||||
import { usePagination } from './hooks/usePagination';
|
||||
import { useColumns } from './hooks/useColumns';
|
||||
import { useDataSource } from './hooks/useDataSource';
|
||||
import { useLoading } from './hooks/useLoading';
|
||||
import { useRowSelection } from './hooks/useRowSelection';
|
||||
import { useTableScroll } from './hooks/useTableScroll';
|
||||
import { useCustomRow } from './hooks/useCustomRow';
|
||||
import { useTableStyle } from './hooks/useTableStyle';
|
||||
import { useTableHeader } from './hooks/useTableHeader';
|
||||
import { useTableExpand } from './hooks/useTableExpand';
|
||||
import { createTableContext } from './hooks/useTableContext';
|
||||
import { useTableFooter } from './hooks/useTableFooter';
|
||||
import { useTableForm } from './hooks/useTableForm';
|
||||
// import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useCustomSelection } from "./hooks/useCustomSelection";
|
||||
|
||||
import { omit, pick } from 'lodash-es';
|
||||
import { basicProps } from './props';
|
||||
// import { isFunction } from '/@/utils/is';
|
||||
// import { warn } from '/@/utils/log';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Table,
|
||||
// BasicForm,
|
||||
HeaderCell,
|
||||
TableSummary,
|
||||
CustomSelectHeader,
|
||||
},
|
||||
props: basicProps,
|
||||
emits: [
|
||||
'fetch-success',
|
||||
'fetch-error',
|
||||
'selection-change',
|
||||
'register',
|
||||
'row-click',
|
||||
'row-dbClick',
|
||||
'row-contextmenu',
|
||||
'row-mouseenter',
|
||||
'row-mouseleave',
|
||||
'edit-end',
|
||||
'edit-cancel',
|
||||
'edit-row-end',
|
||||
'edit-change',
|
||||
'expanded-rows-change',
|
||||
'change',
|
||||
'columns-change',
|
||||
'table-redo',
|
||||
],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const tableElRef = ref(null);
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
|
||||
const wrapRef = ref(null);
|
||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||
|
||||
const { prefixCls } = useDesign('basic-table');
|
||||
const [registerForm, formActions] = useForm();
|
||||
|
||||
const getProps = computed(() => {
|
||||
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
||||
});
|
||||
|
||||
const isFixedHeightPage = inject(PageWrapperFixedHeightKey, false);
|
||||
watchEffect(() => {
|
||||
unref(isFixedHeightPage) &&
|
||||
props.canResize &&
|
||||
warn("'canResize' of BasicTable may not work in PageWrapper with 'fixedHeight' (especially in hot updates)");
|
||||
});
|
||||
|
||||
const { getLoading, setLoading } = useLoading(getProps);
|
||||
const { getPaginationInfo, getPagination, setPagination, setShowPagination, getShowPagination } = usePagination(getProps);
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
|
||||
// const { getRowSelection, getRowSelectionRef, getSelectRows, clearSelectedRowKeys, getSelectRowKeys, deleteSelectRowByKey, setSelectedRowKeys } =
|
||||
// useRowSelection(getProps, tableData, emit);
|
||||
|
||||
// 子级列名
|
||||
const childrenColumnName = computed(() => getProps.value.childrenColumnName || 'children');
|
||||
|
||||
// 自定义选择列
|
||||
const {
|
||||
getRowSelection,
|
||||
getSelectRows,
|
||||
getSelectRowKeys,
|
||||
setSelectedRowKeys,
|
||||
getRowSelectionRef,
|
||||
selectHeaderProps,
|
||||
isCustomSelection,
|
||||
handleCustomSelectColumn,
|
||||
clearSelectedRowKeys,
|
||||
deleteSelectRowByKey,
|
||||
getExpandIconColumnIndex,
|
||||
} = useCustomSelection(
|
||||
getProps,
|
||||
emit,
|
||||
wrapRef,
|
||||
getPaginationInfo,
|
||||
tableData,
|
||||
childrenColumnName
|
||||
)
|
||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
|
||||
const {
|
||||
handleTableChange: onTableChange,
|
||||
getDataSourceRef,
|
||||
getDataSource,
|
||||
getRawDataSource,
|
||||
setTableData,
|
||||
updateTableDataRecord,
|
||||
deleteTableDataRecord,
|
||||
insertTableDataRecord,
|
||||
findTableDataRecord,
|
||||
fetch,
|
||||
getRowKey,
|
||||
reload,
|
||||
getAutoCreateKey,
|
||||
updateTableData,
|
||||
} = useDataSource(
|
||||
getProps,
|
||||
{
|
||||
tableData,
|
||||
getPaginationInfo,
|
||||
setLoading,
|
||||
setPagination,
|
||||
validate: formActions.validate,
|
||||
clearSelectedRowKeys,
|
||||
},
|
||||
emit
|
||||
);
|
||||
|
||||
function handleTableChange(...args) {
|
||||
onTableChange.call(undefined, ...args);
|
||||
emit('change', ...args);
|
||||
// 解决通过useTable注册onChange时不起作用的问题
|
||||
const { onChange } = unref(getProps);
|
||||
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
|
||||
}
|
||||
|
||||
const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(
|
||||
getProps,
|
||||
getPaginationInfo,
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
handleCustomSelectColumn,
|
||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
);
|
||||
|
||||
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef, getColumnsRef, getRowSelectionRef, getDataSourceRef);
|
||||
|
||||
const { customRow } = useCustomRow(getProps, {
|
||||
setSelectedRowKeys,
|
||||
getSelectRowKeys,
|
||||
clearSelectedRowKeys,
|
||||
getAutoCreateKey,
|
||||
emit,
|
||||
});
|
||||
|
||||
const { getRowClassName } = useTableStyle(getProps, prefixCls);
|
||||
|
||||
const { getExpandOption, expandAll, collapseAll } = useTableExpand(getProps, tableData, emit);
|
||||
|
||||
const handlers: InnerHandlers = {
|
||||
onColumnsChange: (data: ColumnChangeParam[]) => {
|
||||
emit('columns-change', data);
|
||||
// support useTable
|
||||
unref(getProps).onColumnsChange?.(data);
|
||||
},
|
||||
};
|
||||
|
||||
const { getHeaderProps } = useTableHeader(getProps, slots, handlers);
|
||||
// update-begin--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
const getSummaryProps = computed(() => {
|
||||
return pick(unref(getProps), ['summaryFunc', 'summaryData', 'hasExpandedRow', 'rowKey']);
|
||||
});
|
||||
const getIsEmptyData = computed(() => {
|
||||
return (unref(getDataSourceRef) || []).length === 0;
|
||||
});
|
||||
const showSummaryRef = computed(() => {
|
||||
const summaryProps = unref(getSummaryProps);
|
||||
return (summaryProps.summaryFunc || summaryProps.summaryData) && !unref(getIsEmptyData);
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
|
||||
const { getFooterProps } = useTableFooter(getProps, slots, getScrollRef, tableElRef, getDataSourceRef);
|
||||
|
||||
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } = useTableForm(getProps, slots, fetch, getLoading);
|
||||
|
||||
const getBindValues = computed(() => {
|
||||
const dataSource = unref(getDataSourceRef);
|
||||
let propsData: Recordable = {
|
||||
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
|
||||
...attrs,
|
||||
customRow,
|
||||
//树列表展开使用AntDesignVue默认的加减图标 author:scott date:20210914
|
||||
//expandIcon: slots.expandIcon ? null : expandIcon(),
|
||||
...unref(getProps),
|
||||
...unref(getHeaderProps),
|
||||
scroll: unref(getScrollRef),
|
||||
loading: unref(getLoading),
|
||||
tableLayout: 'fixed',
|
||||
rowSelection: unref(getRowSelectionRef),
|
||||
rowKey: unref(getRowKey),
|
||||
columns: toRaw(unref(getViewColumns)),
|
||||
pagination: toRaw(unref(getPaginationInfo)),
|
||||
dataSource,
|
||||
footer: unref(getFooterProps),
|
||||
...unref(getExpandOption),
|
||||
// 【QQYUN-5837】动态计算 expandIconColumnIndex
|
||||
expandIconColumnIndex: getExpandIconColumnIndex.value,
|
||||
};
|
||||
|
||||
//update-begin---author:wangshuai ---date:20230214 for:[QQYUN-4237]代码生成 内嵌子表模式 没有滚动条------------
|
||||
//额外的展开行存在插槽时会将滚动移除掉,注释掉
|
||||
/*if (slots.expandedRowRender) {
|
||||
propsData = omit(propsData, 'scroll');
|
||||
}*/
|
||||
//update-end---author:wangshuai ---date:20230214 for:[QQYUN-4237]代码生成 内嵌子表模式 没有滚动条------------
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
// 自定义选择列,需要去掉原生的
|
||||
delete propsData.rowSelection
|
||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20230919---for:【QQYUN-6387】展开写法(去掉报错)
|
||||
!propsData.isTreeTable && delete propsData.expandIconColumnIndex;
|
||||
propsData.expandedRowKeys === null && delete propsData.expandedRowKeys;
|
||||
// update-end--author:liaozhiyang---date:20230919---for:【QQYUN-6387】展开写法(去掉报错)
|
||||
propsData = omit(propsData, ['class', 'onChange']);
|
||||
return propsData;
|
||||
});
|
||||
|
||||
// 统一设置表格列宽度
|
||||
const getMaxColumnWidth = computed(() => {
|
||||
const values = unref(getBindValues);
|
||||
return values.maxColumnWidth > 0 ? values.maxColumnWidth + 'px' : null;
|
||||
});
|
||||
|
||||
const getWrapperClass = computed(() => {
|
||||
const values = unref(getBindValues);
|
||||
return [
|
||||
prefixCls,
|
||||
attrs.class,
|
||||
{
|
||||
[`${prefixCls}-form-container`]: values.useSearchForm,
|
||||
[`${prefixCls}--inset`]: values.inset,
|
||||
[`${prefixCls}-col-max-width`]: getMaxColumnWidth.value != null,
|
||||
// 是否显示表尾合计
|
||||
[`${prefixCls}--show-summary`]: values.showSummary,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const getEmptyDataIsShowTable = computed(() => {
|
||||
const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
|
||||
if (emptyDataIsShowTable || !useSearchForm) {
|
||||
return true;
|
||||
}
|
||||
return !!unref(getDataSourceRef).length;
|
||||
});
|
||||
|
||||
function setProps(props: Partial<BasicTableProps>) {
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
|
||||
const tableAction: TableActionType = {
|
||||
reload,
|
||||
getSelectRows,
|
||||
clearSelectedRowKeys,
|
||||
getSelectRowKeys,
|
||||
deleteSelectRowByKey,
|
||||
setPagination,
|
||||
setTableData,
|
||||
updateTableDataRecord,
|
||||
deleteTableDataRecord,
|
||||
insertTableDataRecord,
|
||||
findTableDataRecord,
|
||||
redoHeight,
|
||||
setSelectedRowKeys,
|
||||
setColumns,
|
||||
setLoading,
|
||||
getDataSource,
|
||||
getRawDataSource,
|
||||
setProps,
|
||||
getRowSelection,
|
||||
getPaginationRef: getPagination,
|
||||
getColumns,
|
||||
getCacheColumns,
|
||||
emit,
|
||||
updateTableData,
|
||||
setShowPagination,
|
||||
getShowPagination,
|
||||
setCacheColumnsByField,
|
||||
expandAll,
|
||||
collapseAll,
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
},
|
||||
};
|
||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230718---for:【issues/179】兼容新老slots写法,移除控制台警告
|
||||
// 获取分组之后的slot名称
|
||||
const slotNamesGroup = computed<{
|
||||
// AntTable原生插槽
|
||||
native: string[];
|
||||
// 列自定义插槽
|
||||
custom: string[];
|
||||
}>(() => {
|
||||
const native: string[] = [];
|
||||
const custom: string[] = [];
|
||||
const columns = unref<Recordable[]>(getViewColumns) as BasicColumn[];
|
||||
const allCustomRender = columns.map<string>((column) => column.slotsBak?.customRender);
|
||||
for (const name of Object.keys(slots)) {
|
||||
// 过滤特殊的插槽
|
||||
if (['bodyCell'].includes(name)) {
|
||||
continue;
|
||||
}
|
||||
if (allCustomRender.includes(name)) {
|
||||
custom.push(name);
|
||||
} else {
|
||||
native.push(name);
|
||||
}
|
||||
}
|
||||
return { native, custom };
|
||||
});
|
||||
// update-end--author:sunjianlei---date:220230718---for:【issues/179】兼容新老slots写法,移除控制台警告
|
||||
// update-begin--author:liaozhiyang---date:20231226---for:【issues/945】BasicTable组件设置默认展开不生效
|
||||
nextTick(() => {
|
||||
getProps.value.defaultExpandAllRows && expandAll();
|
||||
})
|
||||
// update-end--author:sunjianlei---date:20231226---for:【issues/945】BasicTable组件设置默认展开不生效
|
||||
expose(tableAction);
|
||||
|
||||
emit('register', tableAction, formActions);
|
||||
|
||||
|
||||
return {
|
||||
tableElRef,
|
||||
getBindValues,
|
||||
getLoading,
|
||||
registerForm,
|
||||
handleSearchInfoChange,
|
||||
getEmptyDataIsShowTable,
|
||||
handleTableChange,
|
||||
getRowClassName,
|
||||
wrapRef,
|
||||
tableAction,
|
||||
redoHeight,
|
||||
handleResizeColumn: (w, col) => {
|
||||
console.log('col',col);
|
||||
col.width = w;
|
||||
},
|
||||
getFormProps: getFormProps as any,
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
getWrapperClass,
|
||||
getMaxColumnWidth,
|
||||
columns: getViewColumns,
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
selectHeaderProps,
|
||||
isCustomSelection,
|
||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
slotNamesGroup,
|
||||
// update-begin--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
getSummaryProps,
|
||||
showSummaryRef,
|
||||
// update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@border-color: #cecece4d;
|
||||
|
||||
@prefix-cls: ~'@{namespace}-basic-table';
|
||||
|
||||
[data-theme='dark'] {
|
||||
.ant-table-tbody > tr:hover.ant-table-row-selected > td,
|
||||
.ant-table-tbody > tr.ant-table-row-selected td {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
//表格选择工具栏样式
|
||||
.alert {
|
||||
// background-color: #323232;
|
||||
// border-color: #424242;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
max-width: 100%;
|
||||
|
||||
&-row__striped {
|
||||
td {
|
||||
background-color: @app-content-background;
|
||||
}
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240613---for:【TV360X-1232】查询区域隐藏后点击刷新不走请求了(采用css隐藏)
|
||||
> .table-search-area-hidden {
|
||||
display: none;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240613---for:【TV360X-1232】查询区域隐藏后点击刷新不走请求了(采用css隐藏)
|
||||
&-form-container {
|
||||
padding: 10px;
|
||||
|
||||
.ant-form {
|
||||
padding: 12px 10px 6px 10px;
|
||||
margin-bottom: 8px;
|
||||
background-color: @component-background;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
//update-begin-author:liusq---date:20230517--for: [issues/526]RangePicker 设置预设范围按钮样式问题---
|
||||
.ant-picker-preset {
|
||||
.ant-tag {
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
}
|
||||
//update-end-author:liusq---date:20230517--for: [issues/526]RangePicker 设置预设范围按钮样式问题---
|
||||
|
||||
.ant-table-wrapper {
|
||||
padding: 6px;
|
||||
background-color: @component-background;
|
||||
border-radius: 2px;
|
||||
|
||||
.ant-table-title {
|
||||
min-height: 40px;
|
||||
padding: 0 0 8px 0 !important;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-bordered .ant-table-title {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
padding: 8px 6px;
|
||||
border-bottom: none;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
//定义行颜色
|
||||
.trcolor {
|
||||
background-color: rgba(255, 192, 203, 0.31);
|
||||
color: red;
|
||||
}
|
||||
|
||||
//.ant-table-tbody > tr.ant-table-row-selected td {
|
||||
//background-color: fade(@primary-color, 8%) !important;
|
||||
//}
|
||||
}
|
||||
|
||||
.ant-pagination {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.ant-table-footer {
|
||||
padding: 0;
|
||||
|
||||
.ant-table-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-table-content {
|
||||
overflow-x: hidden !important;
|
||||
// overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
}
|
||||
//表格选择工具栏样式
|
||||
.alert {
|
||||
height: 38px;
|
||||
// background-color: #e6f7ff;
|
||||
// border-color: #91d5ff;
|
||||
}
|
||||
&--inset {
|
||||
.ant-table-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ------ 统一设置表格列最大宽度 ------
|
||||
&-col-max-width {
|
||||
.ant-table-thead tr th,
|
||||
.ant-table-tbody tr td {
|
||||
max-width: v-bind(getMaxColumnWidth);
|
||||
}
|
||||
}
|
||||
// ------ 统一设置表格列最大宽度 ------
|
||||
|
||||
// update-begin--author:sunjianlei---date:220230718---for:【issues/622】修复表尾合计错位的问题
|
||||
&--show-summary {
|
||||
.ant-table > .ant-table-footer {
|
||||
padding: 12px 0 0;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-bordered > .ant-table-footer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
// update-end--author:sunjianlei---date:220230718---for:【issues/622】修复表尾合计错位的问题
|
||||
// update-begin--author:liaozhiyang---date:20240604---for:【TV360X-377】关联记录必填影响到了table的输入框和页码样式
|
||||
> .ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240604---for:【TV360X-377】关联记录必填影响到了table的输入框和页码样式
|
||||
}
|
||||
</style>
|
||||
26
hx-ai-intelligent/src/components/Table/src/componentMap.ts
Normal file
26
hx-ai-intelligent/src/components/Table/src/componentMap.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { Component } from 'vue';
|
||||
import { Input, Select, Checkbox, InputNumber, Switch, DatePicker, TimePicker } from 'ant-design-vue';
|
||||
import type { ComponentType } from './types/componentType';
|
||||
import { ApiSelect, ApiTreeSelect } from '/@/components/Form';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputNumber', InputNumber);
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('ApiSelect', ApiSelect);
|
||||
componentMap.set('ApiTreeSelect', ApiTreeSelect);
|
||||
componentMap.set('Switch', Switch);
|
||||
componentMap.set('Checkbox', Checkbox);
|
||||
componentMap.set('DatePicker', DatePicker);
|
||||
componentMap.set('TimePicker', TimePicker);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
@@ -0,0 +1,67 @@
|
||||
<!-- 自定义选择列,表头实现部分 -->
|
||||
<template>
|
||||
<!-- update-begin--author:liaozhiyang---date:20231130---for:【issues/5595】BasicTable组件hideSelectAll: true无法隐藏全选框 -->
|
||||
<template v-if="isRadio">
|
||||
<!-- radio不存在全选,所以放个空标签 -->
|
||||
<span></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="hideSelectAll">
|
||||
<span></span>
|
||||
</template>
|
||||
<a-checkbox :disabled="disabled" v-else :checked="checked" :indeterminate="isHalf" @update:checked="onChange" />
|
||||
</template>
|
||||
<!-- update-end--author:liaozhiyang---date:20231130---for:【issues/5595】BasicTable组件hideSelectAll: true无法隐藏全选框 -->
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
isRadio: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
selectedLength: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
// 当前页条目数
|
||||
pageSize: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
hideSelectAll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// update-begin--author:liaozhiyang---date:20231016---for:【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20231016---for:【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
|
||||
});
|
||||
const emit = defineEmits(['select-all']);
|
||||
|
||||
// 是否全选
|
||||
const checked = computed(() => {
|
||||
if (props.isRadio) {
|
||||
return false;
|
||||
}
|
||||
return props.selectedLength > 0 && props.selectedLength >= props.pageSize;
|
||||
});
|
||||
|
||||
// 是否半选
|
||||
const isHalf = computed(() => {
|
||||
if (props.isRadio) {
|
||||
return false;
|
||||
}
|
||||
return props.selectedLength > 0 && props.selectedLength < props.pageSize;
|
||||
});
|
||||
|
||||
function onChange(checked: boolean) {
|
||||
emit('select-all', checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<span>
|
||||
<slot></slot>
|
||||
{{ title }}
|
||||
<FormOutlined />
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { FormOutlined } from '@ant-design/icons-vue';
|
||||
export default defineComponent({
|
||||
name: 'EditTableHeaderIcon',
|
||||
components: { FormOutlined },
|
||||
props: { title: { type: String, default: '' } },
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { BasicArrow } from '/@/components/Basic';
|
||||
|
||||
export default () => {
|
||||
return (props: Recordable) => {
|
||||
if (!props.expandable) {
|
||||
if (props.needIndentSpaced) {
|
||||
return <span class="ant-table-row-expand-icon ant-table-row-spaced" />;
|
||||
} else {
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<BasicArrow
|
||||
style="margin-right: 8px"
|
||||
iconStyle="margin-top: -2px;"
|
||||
onClick={(e: Event) => {
|
||||
props.onExpand(props.record, e);
|
||||
}}
|
||||
expand={props.expanded}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<EditTableHeaderCell v-if="getIsEdit">
|
||||
{{ getTitle }}
|
||||
</EditTableHeaderCell>
|
||||
<span v-else>{{ getTitle }}</span>
|
||||
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import type { BasicColumn } from '../types/table';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import BasicHelp from '/@/components/Basic/src/BasicHelp.vue';
|
||||
import EditTableHeaderCell from './EditTableHeaderIcon.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableHeaderCell',
|
||||
components: {
|
||||
EditTableHeaderCell,
|
||||
BasicHelp,
|
||||
},
|
||||
props: {
|
||||
column: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('basic-table-header-cell');
|
||||
|
||||
const getIsEdit = computed(() => !!props.column?.edit);
|
||||
const getTitle = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
const result = props.column?.customTitle || props.column?.title;
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
|
||||
});
|
||||
const getHelpMessage = computed(() => props.column?.helpMessage);
|
||||
|
||||
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-header-cell';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__help {
|
||||
margin-left: 8px;
|
||||
color: rgb(0 0 0 / 65%) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div :class="[prefixCls, getAlign]" @click="onCellClick">
|
||||
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
|
||||
<template v-if="action.slot">
|
||||
<slot name="customButton"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
|
||||
<PopConfirmButton v-bind="action">
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
|
||||
<template v-if="action.label">{{ action.label }}</template>
|
||||
</PopConfirmButton>
|
||||
</Tooltip>
|
||||
<PopConfirmButton v-else v-bind="action">
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
|
||||
<template v-if="action.label">{{ action.label }}</template>
|
||||
</PopConfirmButton>
|
||||
</template>
|
||||
|
||||
<Divider type="vertical" class="action-divider" v-if="divider && index < getActions.length - 1" />
|
||||
</template>
|
||||
<Dropdown
|
||||
:overlayClassName="dropdownCls"
|
||||
:trigger="['hover']"
|
||||
:dropMenuList="getDropdownList"
|
||||
popconfirm
|
||||
v-if="dropDownActions && getDropdownList.length > 0"
|
||||
>
|
||||
<slot name="more"></slot>
|
||||
<!-- 设置插槽 -->
|
||||
<template v-slot:[item.slot] v-for="(item, index) in getDropdownSlotList" :key="`${index}-${item.label}`">
|
||||
<slot :name="item.slot"></slot>
|
||||
</template>
|
||||
|
||||
<a-button type="link" size="small" v-if="!$slots.more"> 更多 <Icon icon="mdi-light:chevron-down"></Icon> </a-button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, toRaw, unref } from 'vue';
|
||||
import { MoreOutlined } from '@ant-design/icons-vue';
|
||||
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import { ActionItem, TableActionType } from '/@/components/Table';
|
||||
import { PopConfirmButton } from '/@/components/Button';
|
||||
import { Dropdown } from '/@/components/Dropdown';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import { isBoolean, isFunction, isString } from '/@/utils/is';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { ACTION_COLUMN_FLAG } from '../const';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableAction',
|
||||
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined, Tooltip },
|
||||
props: {
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
},
|
||||
divider: propTypes.bool.def(true),
|
||||
outside: propTypes.bool,
|
||||
stopButtonPropagation: propTypes.bool.def(false),
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('basic-table-action');
|
||||
const dropdownCls = `${prefixCls}-dropdown`;
|
||||
let table: Partial<TableActionType> = {};
|
||||
if (!props.outside) {
|
||||
table = useTableContext();
|
||||
}
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
function isIfShow(action: ActionItem): boolean {
|
||||
const ifShow = action.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const getActions = computed(() => {
|
||||
return (toRaw(props.actions) || [])
|
||||
.filter((action) => {
|
||||
return hasPermission(action.auth) && isIfShow(action);
|
||||
})
|
||||
.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
// update-begin--author:liaozhiyang---date:20240105---for:【issues/951】table删除记录时按钮显示错位
|
||||
if (popConfirm) {
|
||||
const overlayClassName = popConfirm.overlayClassName;
|
||||
popConfirm.overlayClassName = `${overlayClassName ? overlayClassName : ''} ${prefixCls}-popconfirm`;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240105---for:【issues/951】table删除记录时按钮显示错位
|
||||
return {
|
||||
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
...action,
|
||||
...(popConfirm || {}),
|
||||
// update-begin--author:liaozhiyang---date:20240108---for:【issues/936】表格操作栏删除当接口失败时,气泡确认框不会消失
|
||||
onConfirm: handelConfirm(popConfirm?.confirm),
|
||||
// update-end--author:liaozhiyang---date:20240108---for:【issues/936】表格操作栏删除当接口失败时,气泡确认框不会消失
|
||||
onCancel: popConfirm?.cancel,
|
||||
enable: !!popConfirm,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const getDropdownList = computed((): any[] => {
|
||||
//过滤掉隐藏的dropdown,避免出现多余的分割线
|
||||
const list = (toRaw(props.dropDownActions) || []).filter((action) => {
|
||||
return hasPermission(action.auth) && isIfShow(action);
|
||||
});
|
||||
return list.map((action, index) => {
|
||||
const { label, popConfirm } = action;
|
||||
// update-begin--author:liaozhiyang---date:20240105---for:【issues/951】table删除记录时按钮显示错位
|
||||
if (popConfirm) {
|
||||
const overlayClassName = popConfirm.overlayClassName;
|
||||
popConfirm.overlayClassName = `${overlayClassName ? overlayClassName : ''} ${prefixCls}-popconfirm`;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240105---for:【issues/951】table删除记录时按钮显示错位
|
||||
// update-begin--author:liaozhiyang---date:20240108---for:【issues/936】表格操作栏删除当接口失败时,气泡确认框不会消失
|
||||
if (popConfirm) {
|
||||
popConfirm.confirm = handelConfirm(popConfirm?.confirm);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240108---for:【issues/936】表格操作栏删除当接口失败时,气泡确认框不会消失
|
||||
return {
|
||||
...action,
|
||||
...popConfirm,
|
||||
onConfirm: handelConfirm(popConfirm?.confirm),
|
||||
onCancel: popConfirm?.cancel,
|
||||
text: label,
|
||||
divider: index < list.length - 1 ? props.divider : false,
|
||||
};
|
||||
});
|
||||
});
|
||||
/*
|
||||
2023-01-08
|
||||
liaozhiyang
|
||||
给传进来的函数包一层promise
|
||||
*/
|
||||
const handelConfirm = (fn) => {
|
||||
if (typeof fn !== 'function') return fn;
|
||||
const anyc = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const result = fn();
|
||||
if (Object.prototype.toString.call(result) === '[object Promise]') {
|
||||
result
|
||||
.finally(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
return anyc;
|
||||
};
|
||||
const getDropdownSlotList = computed((): any[] => {
|
||||
return unref(getDropdownList).filter((item) => item.slot);
|
||||
});
|
||||
const getAlign = computed(() => {
|
||||
const columns = (table as TableActionType)?.getColumns?.() || [];
|
||||
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
|
||||
return actionColumn?.align ?? 'left';
|
||||
});
|
||||
|
||||
function getTooltip(data: string | TooltipProps): TooltipProps {
|
||||
return {
|
||||
getPopupContainer: () => unref((table as any)?.wrapRef.value) ?? document.body,
|
||||
placement: 'bottom',
|
||||
...(isString(data) ? { title: data } : data),
|
||||
};
|
||||
}
|
||||
|
||||
function onCellClick(e: MouseEvent) {
|
||||
if (!props.stopButtonPropagation) return;
|
||||
const path = e.composedPath() as HTMLElement[];
|
||||
const isInButton = path.find((ele) => {
|
||||
return ele.tagName?.toUpperCase() === 'BUTTON';
|
||||
});
|
||||
isInButton && e.stopPropagation();
|
||||
}
|
||||
|
||||
return { prefixCls, getActions, getDropdownList, getDropdownSlotList, getAlign, onCellClick, getTooltip, dropdownCls };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-action';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* update-begin-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px*/
|
||||
height: 22px;
|
||||
/* update-end-author:taoyan date:2022-11-18 for: 表格默认行高比官方示例多出2px*/
|
||||
|
||||
.action-divider {
|
||||
display: table;
|
||||
}
|
||||
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
button.ant-btn-circle {
|
||||
span {
|
||||
margin: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-divider,
|
||||
.ant-divider-vertical {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
transform: rotate(90deg);
|
||||
|
||||
svg {
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
&-popconfirm {
|
||||
.ant-popconfirm-buttons {
|
||||
min-width: 120px;
|
||||
// update-begin--author:liaozhiyang---date:20240124---for:【issues/1019】popConfirm确认框待端后端返回过程中(处理中)样式错乱
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// update-end--author:liaozhiyang---date:20240124---for:【issues/1019】popConfirm确认框待端后端返回过程中(处理中)样式错乱
|
||||
}
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240407---for:【QQYUN-8762】调整table操作栏ant-dropdown样式
|
||||
&-dropdown {
|
||||
.ant-dropdown-menu .ant-dropdown-menu-item-divider {
|
||||
margin: 2px 0;
|
||||
}
|
||||
.ant-dropdown-menu .ant-dropdown-menu-item {
|
||||
padding: 3px 8px;
|
||||
font-size: 13.6px;
|
||||
}
|
||||
.dropdown-event-area {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240407---for:【QQYUN-8762】调整table操作栏ant-dropdown样式
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<Table
|
||||
v-if="summaryFunc || summaryData"
|
||||
:showHeader="false"
|
||||
:bordered="bordered"
|
||||
:pagination="false"
|
||||
:dataSource="getDataSource"
|
||||
:rowKey="(r) => r[rowKey]"
|
||||
:columns="getColumns"
|
||||
tableLayout="fixed"
|
||||
:scroll="scroll"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, unref, computed, toRaw } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import type { BasicColumn } from '../types/table';
|
||||
import { INDEX_COLUMN_FLAG } from '../const';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
|
||||
const SUMMARY_ROW_KEY = '_row';
|
||||
const SUMMARY_INDEX_KEY = '_index';
|
||||
export default defineComponent({
|
||||
name: 'BasicTableFooter',
|
||||
components: { Table },
|
||||
props: {
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
summaryFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
},
|
||||
summaryData: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
},
|
||||
scroll: {
|
||||
type: Object as PropType<Recordable>,
|
||||
},
|
||||
rowKey: propTypes.string.def('key'),
|
||||
// 是否有展开列
|
||||
hasExpandedRow: propTypes.bool,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
|
||||
const getDataSource = computed((): Recordable[] => {
|
||||
const { summaryFunc, summaryData } = props;
|
||||
if (summaryData?.length) {
|
||||
summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`));
|
||||
return summaryData;
|
||||
}
|
||||
if (!isFunction(summaryFunc)) {
|
||||
return [];
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20230227---for:【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
|
||||
let dataSource = cloneDeep(unref(table.getDataSource()));
|
||||
// update-end--author:liaozhiyang---date:20230227---for:【QQYUN-8172】可编辑单元格编辑完以后不更新合计值
|
||||
dataSource = summaryFunc(dataSource);
|
||||
dataSource.forEach((item, i) => {
|
||||
item[props.rowKey] = `${i}`;
|
||||
});
|
||||
return dataSource;
|
||||
});
|
||||
|
||||
const getColumns = computed(() => {
|
||||
const dataSource = unref(getDataSource);
|
||||
let columns: BasicColumn[] = cloneDeep(table.getColumns());
|
||||
// update-begin--author:liaozhiyang---date:220230804---for:【issues/638】表格合计,列自定义隐藏或展示时,合计栏会错位
|
||||
columns = columns.filter((item) => !item.defaultHidden);
|
||||
// update-begin--author:liaozhiyang---date:220230804---for:【issues/638】表格合计,列自定义隐藏或展示时,合计栏会错位
|
||||
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
|
||||
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
|
||||
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
|
||||
|
||||
// 是否有序号列
|
||||
let hasIndexCol = false;
|
||||
// 是否有选择列
|
||||
let hasSelection = table.getRowSelection() && hasRowSummary
|
||||
|
||||
if (index !== -1) {
|
||||
if (hasIndexSummary) {
|
||||
hasIndexCol = true;
|
||||
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
|
||||
columns[index].ellipsis = false;
|
||||
} else {
|
||||
Reflect.deleteProperty(columns[index], 'customRender');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
// update-begin--author:liaozhiyang---date:20231009---for:【issues/776】显示100条/页,复选框只能显示3个的问题(fixed也有可能设置true)
|
||||
const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true);
|
||||
// update-begin--author:liaozhiyang---date:20231009---for:【issues/776】显示100条/页,复选框只能显示3个的问题(fixed也有可能设置true)
|
||||
columns.unshift({
|
||||
width: 50,
|
||||
title: 'selection',
|
||||
key: 'selectionKey',
|
||||
align: 'center',
|
||||
...(isFixed ? { fixed: 'left' } : {}),
|
||||
customRender: ({ record }) => hasIndexCol ? '' : record[SUMMARY_ROW_KEY],
|
||||
});
|
||||
}
|
||||
|
||||
if (props.hasExpandedRow) {
|
||||
const isFixed = columns.some((col) => col.fixed === 'left');
|
||||
columns.unshift({
|
||||
width: 50,
|
||||
title: 'expandedRow',
|
||||
key: 'expandedRowKey',
|
||||
align: 'center',
|
||||
...(isFixed ? { fixed: 'left' } : {}),
|
||||
customRender: () => '',
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
return { getColumns, getDataSource };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// update-begin--author:liaozhiyang---date:20231009---for:【issues/776】显示100条/页,复选框只能显示3个的问题(隐藏合计的滚动条)
|
||||
.ant-table-wrapper {
|
||||
:deep(.ant-table-body) {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231009---for:【issues/776】显示100条/页,复选框只能显示3个的问题(隐藏合计的滚动条)
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<div v-if="$slots.headerTop" style="margin: 5px">
|
||||
<slot name="headerTop"></slot>
|
||||
</div>
|
||||
<div :class="`flex items-center ${prefixCls}__table-title-box`">
|
||||
<div :class="`${prefixCls}__tableTitle`">
|
||||
<slot name="tableTitle" v-if="$slots.tableTitle"></slot>
|
||||
<!--修改标题插槽位置-->
|
||||
<TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
|
||||
</div>
|
||||
|
||||
<div :class="`${prefixCls}__toolbar`">
|
||||
<slot name="toolbar"></slot>
|
||||
<Divider type="vertical" v-if="$slots.toolbar && showTableSetting" />
|
||||
<TableSetting :class="`${prefixCls}__toolbar-desktop`" style="white-space: nowrap;" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
|
||||
<a-popover :overlayClassName="`${prefixCls}__toolbar-mobile`" trigger="click" placement="left" :getPopupContainer="(n) => n?.parentElement">
|
||||
<template #content>
|
||||
<TableSetting mode="mobile" :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" />
|
||||
</template>
|
||||
<a-button :class="`${prefixCls}__toolbar-mobile`" v-if="showTableSetting" type="text" preIcon="ant-design:menu" shape="circle" />
|
||||
</a-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!--添加tableTop插槽-->
|
||||
<div style="margin: -4px 0 -2px; padding-top: 5px">
|
||||
<slot name="tableTop">
|
||||
<a-alert type="info" show-icon class="alert" v-if="openRowSelection != null">
|
||||
<template #message>
|
||||
<template v-if="selectRowKeys.length > 0">
|
||||
<span>
|
||||
<span>已选中 {{ selectRowKeys.length }} 条记录</span>
|
||||
<span v-if="isAcrossPage">(可跨页)</span>
|
||||
</span>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="setSelectedRowKeys([])">清空</a>
|
||||
<slot name="alertAfter" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>未选中任何数据</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-alert>
|
||||
</slot>
|
||||
</div>
|
||||
<!--添加tableTop插槽-->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { TableSetting, ColumnChangeParam } from '../types/table';
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { Divider } from 'ant-design-vue';
|
||||
import TableSettingComponent from './settings/index.vue';
|
||||
import TableTitle from './TableTitle.vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicTableHeader',
|
||||
components: {
|
||||
Divider,
|
||||
TableTitle,
|
||||
TableSetting: TableSettingComponent,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
|
||||
},
|
||||
tableSetting: {
|
||||
type: Object as PropType<TableSetting>,
|
||||
},
|
||||
showTableSetting: {
|
||||
type: Boolean,
|
||||
},
|
||||
titleHelpMessage: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['columns-change'],
|
||||
setup(_, { emit }) {
|
||||
const { prefixCls } = useDesign('basic-table-header');
|
||||
|
||||
function handleColumnChange(data: ColumnChangeParam[]) {
|
||||
emit('columns-change', data);
|
||||
}
|
||||
|
||||
const { getSelectRowKeys, setSelectedRowKeys, getRowSelection } = useTableContext();
|
||||
const selectRowKeys = computed(() => getSelectRowKeys());
|
||||
const openRowSelection = computed(() => getRowSelection());
|
||||
// 是否允许跨页选择
|
||||
const isAcrossPage = computed(() => openRowSelection.value?.preserveSelectedRowKeys === true);
|
||||
|
||||
return { prefixCls, handleColumnChange, selectRowKeys, setSelectedRowKeys, openRowSelection, isAcrossPage };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-header';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__toolbar {
|
||||
//flex: 1;
|
||||
width: 140px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&__tableTitle {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
|
||||
> * {
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: @screen-lg) {
|
||||
&__table-title-box {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.table-settings > * {
|
||||
margin-right: 0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
&-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-mobile {
|
||||
display: block;
|
||||
.table-settings > * {
|
||||
margin-right: 6px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div :class="prefixCls" class="flex items-center mx-auto" v-if="imgList && imgList.length" :style="getWrapStyle">
|
||||
<Badge :count="!showBadge || imgList.length == 1 ? 0 : imgList.length" v-if="simpleShow">
|
||||
<div class="img-div">
|
||||
<PreviewGroup>
|
||||
<template v-for="(img, index) in imgList" :key="img">
|
||||
<Image
|
||||
:width="size"
|
||||
:style="{
|
||||
display: index === 0 ? '' : 'none !important',
|
||||
}"
|
||||
:src="srcPrefix + img"
|
||||
/>
|
||||
</template>
|
||||
</PreviewGroup>
|
||||
</div>
|
||||
</Badge>
|
||||
<PreviewGroup v-else>
|
||||
<template v-for="(img, index) in imgList" :key="img">
|
||||
<Image :width="size" :style="{ marginLeft: index === 0 ? 0 : margin }" :src="srcPrefix + img" />
|
||||
</template>
|
||||
</PreviewGroup>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { Image, Badge } from 'ant-design-vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableImage',
|
||||
components: { Image, PreviewGroup: Image.PreviewGroup, Badge },
|
||||
props: {
|
||||
imgList: propTypes.arrayOf(propTypes.string),
|
||||
size: propTypes.number.def(40),
|
||||
// 是否简单显示(只显示第一张图片)
|
||||
simpleShow: propTypes.bool,
|
||||
// 简单模式下是否显示图片数量的badge
|
||||
showBadge: propTypes.bool.def(true),
|
||||
// 图片间距
|
||||
margin: propTypes.number.def(4),
|
||||
// src前缀,将会附加在imgList中每一项之前
|
||||
srcPrefix: propTypes.string.def(''),
|
||||
},
|
||||
setup(props) {
|
||||
const getWrapStyle = computed((): CSSProperties => {
|
||||
const { size } = props;
|
||||
const s = `${size}px`;
|
||||
return { height: s, width: s };
|
||||
});
|
||||
|
||||
const { prefixCls } = useDesign('basic-table-img');
|
||||
return { prefixCls, getWrapStyle };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-img';
|
||||
|
||||
.@{prefix-cls} {
|
||||
.ant-image {
|
||||
margin-right: 4px;
|
||||
cursor: zoom-in;
|
||||
|
||||
img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.img-div {
|
||||
display: inline-grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { PropType, VNode } from 'vue';
|
||||
import { defineComponent, unref, computed, isVNode } from 'vue';
|
||||
import { cloneDeep, pick } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import type { BasicColumn } from '../types/table';
|
||||
import { INDEX_COLUMN_FLAG } from '../const';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
import { TableSummary, TableSummaryRow, TableSummaryCell } from 'ant-design-vue';
|
||||
|
||||
const SUMMARY_ROW_KEY = '_row';
|
||||
const SUMMARY_INDEX_KEY = '_index';
|
||||
export default defineComponent({
|
||||
name: 'BasicTableSummary',
|
||||
components: { TableSummary, TableSummaryRow, TableSummaryCell },
|
||||
props: {
|
||||
summaryFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
},
|
||||
summaryData: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
},
|
||||
rowKey: propTypes.string.def('key'),
|
||||
// 是否有展开列
|
||||
hasExpandedRow: propTypes.bool,
|
||||
data: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
|
||||
const getDataSource = computed((): Recordable[] => {
|
||||
const {
|
||||
summaryFunc,
|
||||
summaryData,
|
||||
data: { pageData },
|
||||
} = props;
|
||||
if (summaryData?.length) {
|
||||
summaryData.forEach((item, i) => (item[props.rowKey] = `${i}`));
|
||||
return summaryData;
|
||||
}
|
||||
if (!isFunction(summaryFunc)) {
|
||||
return [];
|
||||
}
|
||||
let dataSource = cloneDeep(unref(pageData));
|
||||
dataSource = summaryFunc(dataSource);
|
||||
dataSource.forEach((item, i) => {
|
||||
item[props.rowKey] = `${i}`;
|
||||
});
|
||||
return dataSource;
|
||||
});
|
||||
|
||||
const getColumns = computed(() => {
|
||||
const dataSource = unref(getDataSource);
|
||||
let columns: BasicColumn[] = cloneDeep(table.getColumns({ sort: true }));
|
||||
columns = columns.filter((item) => !item.defaultHidden);
|
||||
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
|
||||
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
|
||||
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
|
||||
|
||||
// 是否有序号列
|
||||
let hasIndexCol = false;
|
||||
// 是否有选择列
|
||||
const hasSelection = table.getRowSelection() && hasRowSummary;
|
||||
|
||||
if (index !== -1) {
|
||||
if (hasIndexSummary) {
|
||||
hasIndexCol = true;
|
||||
columns[index].customSummaryRender = ({ record }) => record[SUMMARY_INDEX_KEY];
|
||||
columns[index].ellipsis = false;
|
||||
} else {
|
||||
Reflect.deleteProperty(columns[index], 'customSummaryRender');
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
const isFixed = columns.some((col) => col.fixed === 'left' || col.fixed === true);
|
||||
columns.unshift({
|
||||
width: 60,
|
||||
title: 'selection',
|
||||
key: 'selectionKey',
|
||||
align: 'center',
|
||||
...(isFixed ? { fixed: 'left' } : {}),
|
||||
customSummaryRender: ({ record }) => (hasIndexCol ? '' : record[SUMMARY_ROW_KEY]),
|
||||
});
|
||||
}
|
||||
|
||||
if (props.hasExpandedRow) {
|
||||
const isFixed = columns.some((col) => col.fixed === 'left');
|
||||
columns.unshift({
|
||||
width: 50,
|
||||
title: 'expandedRow',
|
||||
key: 'expandedRowKey',
|
||||
align: 'center',
|
||||
...(isFixed ? { fixed: 'left' } : {}),
|
||||
customSummaryRender: () => '',
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
|
||||
function isRenderCell(data: any) {
|
||||
return data && typeof data === 'object' && !Array.isArray(data) && !isVNode(data);
|
||||
}
|
||||
|
||||
const getValues = (row: Recordable, col: BasicColumn, index: number) => {
|
||||
const value = row[col.dataIndex as string];
|
||||
let childNode: VNode | JSX.Element | string | number | undefined | null;
|
||||
childNode = value;
|
||||
if (col.customSummaryRender) {
|
||||
const renderData = col.customSummaryRender({
|
||||
text: value,
|
||||
value,
|
||||
record: row,
|
||||
index,
|
||||
column: cloneDeep(col),
|
||||
});
|
||||
if (isRenderCell(renderData)) {
|
||||
childNode = renderData.children;
|
||||
} else {
|
||||
childNode = renderData;
|
||||
}
|
||||
if (typeof childNode === 'object' && !Array.isArray(childNode) && !isVNode(childNode)) {
|
||||
childNode = null;
|
||||
}
|
||||
if (Array.isArray(childNode) && childNode.length === 1) {
|
||||
childNode = childNode[0];
|
||||
}
|
||||
return childNode;
|
||||
}
|
||||
return childNode;
|
||||
};
|
||||
|
||||
const getCellProps = (col: BasicColumn) => {
|
||||
const cellProps = pick(col, ['colSpan', 'rowSpan', 'align']);
|
||||
return {
|
||||
...cellProps,
|
||||
};
|
||||
};
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<TableSummary fixed>
|
||||
{(unref(getDataSource) || []).map((row) => {
|
||||
return (
|
||||
<TableSummaryRow key={row[props.rowKey]}>
|
||||
{unref(getColumns).map((col, index) => {
|
||||
return (
|
||||
<TableSummaryCell {...getCellProps(col)} index={index} key={`${row[props.rowKey]}_${col.dataIndex}_${index}`}>
|
||||
{getValues(row, col, index)}
|
||||
</TableSummaryCell>
|
||||
);
|
||||
})}
|
||||
</TableSummaryRow>
|
||||
);
|
||||
})}
|
||||
</TableSummary>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<BasicTitle :class="prefixCls" v-if="getTitle" :helpMessage="helpMessage">
|
||||
{{ getTitle }}
|
||||
</BasicTitle>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
import { BasicTitle } from '/@/components/Basic/index';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicTableTitle',
|
||||
components: { BasicTitle },
|
||||
props: {
|
||||
title: {
|
||||
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
|
||||
},
|
||||
getSelectRows: {
|
||||
type: Function as PropType<() => Recordable[]>,
|
||||
},
|
||||
helpMessage: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('basic-table-title');
|
||||
|
||||
const getTitle = computed(() => {
|
||||
const { title, getSelectRows = () => {} } = props;
|
||||
let tit = title;
|
||||
|
||||
if (isFunction(title)) {
|
||||
tit = title({
|
||||
selectRows: getSelectRows(),
|
||||
});
|
||||
}
|
||||
return tit;
|
||||
});
|
||||
|
||||
return { getTitle, prefixCls };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-title';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { FunctionalComponent, defineComponent } from 'vue';
|
||||
import type { ComponentType } from '../../types/componentType';
|
||||
import { componentMap } from '/@/components/Table/src/componentMap';
|
||||
|
||||
import { Popover } from 'ant-design-vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
export interface ComponentProps {
|
||||
component: ComponentType;
|
||||
rule: boolean;
|
||||
popoverVisible: boolean;
|
||||
ruleMessage: string;
|
||||
getPopupContainer?: Fn;
|
||||
}
|
||||
|
||||
export const CellComponent: FunctionalComponent = (
|
||||
{ component = 'Input', rule = true, ruleMessage, popoverVisible, getPopupContainer }: ComponentProps,
|
||||
{ attrs }
|
||||
) => {
|
||||
const Comp = componentMap.get(component) as typeof defineComponent;
|
||||
|
||||
const DefaultComp = h(Comp, attrs);
|
||||
if (!rule) {
|
||||
return DefaultComp;
|
||||
}
|
||||
return h(
|
||||
Popover,
|
||||
{
|
||||
overlayClassName: 'edit-cell-rule-popover',
|
||||
open: !!popoverVisible,
|
||||
...(getPopupContainer ? { getPopupContainer } : {}),
|
||||
},
|
||||
{
|
||||
default: () => DefaultComp,
|
||||
content: () => ruleMessage,
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<div v-show="!isEdit" :class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }" @click="handleEdit">
|
||||
<div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
|
||||
<!-- update-begin--author:liaozhiyang---date:20240731---for:【issues/6957】editableCell组件值长度为0,无法编辑 -->
|
||||
<!-- update-begin--author:liaozhiyang---date:20240709---for:【issues/6851】editableCell组件值为0时不展示 -->
|
||||
{{ typeof getValues === 'string' && getValues.length === 0 ? ' ' : getValues ?? ' ' }}
|
||||
<!-- update-end--author:liaozhiyang---date:20240709---for:【issues/6851】editableCell组件值为0时不展示 -->
|
||||
<!-- update-end--author:liaozhiyang---date:20240731---for:【issues/6957】editableCell组件值长度为0,无法编辑 -->
|
||||
</div>
|
||||
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
|
||||
</div>
|
||||
|
||||
<a-spin v-if="isEdit" :spinning="spinning">
|
||||
<div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
|
||||
<CellComponent
|
||||
v-bind="getComponentProps"
|
||||
:component="getComponent"
|
||||
:style="getWrapperStyle"
|
||||
:popoverVisible="getRuleVisible"
|
||||
:rule="getRule"
|
||||
:ruleMessage="ruleMessage"
|
||||
:class="getWrapperClass"
|
||||
ref="elRef"
|
||||
@change="handleChange"
|
||||
@options-change="handleOptionsChange"
|
||||
@pressEnter="handleEnter"
|
||||
/>
|
||||
<div :class="`${prefixCls}__action`" v-if="!getRowEditable">
|
||||
<CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
|
||||
<CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
|
||||
import type { BasicColumn } from '../../types/table';
|
||||
import type { EditRecordRow } from './index';
|
||||
import { CheckOutlined, CloseOutlined, FormOutlined } from '@ant-design/icons-vue';
|
||||
import { CellComponent } from './CellComponent';
|
||||
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
import clickOutside from '/@/directives/clickOutside';
|
||||
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { isArray, isBoolean, isFunction, isNumber, isString } from '/@/utils/is';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { omit, pick, set } from 'lodash-es';
|
||||
import { treeToList } from '/@/utils/helper/treeHelper';
|
||||
import { Spin } from 'ant-design-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditableCell',
|
||||
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin },
|
||||
directives: {
|
||||
clickOutside,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||
default: '',
|
||||
},
|
||||
record: {
|
||||
type: Object as PropType<EditRecordRow>,
|
||||
},
|
||||
column: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: propTypes.number,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const isEdit = ref(false);
|
||||
const elRef = ref();
|
||||
const ruleVisible = ref(false);
|
||||
const ruleMessage = ref('');
|
||||
const optionsRef = ref<LabelValueOptions>([]);
|
||||
const currentValueRef = ref<any>(props.value);
|
||||
const defaultValueRef = ref<any>(props.value);
|
||||
const spinning = ref<boolean>(false);
|
||||
|
||||
const { prefixCls } = useDesign('editable-cell');
|
||||
|
||||
const getComponent = computed(() => props.column?.editComponent || 'Input');
|
||||
const getRule = computed(() => props.column?.editRule);
|
||||
|
||||
const getRuleVisible = computed(() => {
|
||||
return unref(ruleMessage) && unref(ruleVisible);
|
||||
});
|
||||
|
||||
const getIsCheckComp = computed(() => {
|
||||
const component = unref(getComponent);
|
||||
return ['Checkbox', 'Switch'].includes(component);
|
||||
});
|
||||
|
||||
const getComponentProps = computed(() => {
|
||||
const compProps = props.column?.editComponentProps ?? {};
|
||||
const component = unref(getComponent);
|
||||
const apiSelectProps: Recordable = {};
|
||||
if (component === 'ApiSelect') {
|
||||
apiSelectProps.cache = true;
|
||||
}
|
||||
|
||||
const isCheckValue = unref(getIsCheckComp);
|
||||
|
||||
const valueField = isCheckValue ? 'checked' : 'value';
|
||||
const val = unref(currentValueRef);
|
||||
|
||||
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||
|
||||
return {
|
||||
size: 'small',
|
||||
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
|
||||
getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
|
||||
placeholder: createPlaceholderMessage(unref(getComponent)),
|
||||
...apiSelectProps,
|
||||
...omit(compProps, 'onChange'),
|
||||
[valueField]: value,
|
||||
};
|
||||
});
|
||||
|
||||
const getValues = computed(() => {
|
||||
const { editComponentProps, editValueMap } = props.column;
|
||||
|
||||
const value = unref(currentValueRef);
|
||||
|
||||
if (editValueMap && isFunction(editValueMap)) {
|
||||
return editValueMap(value);
|
||||
}
|
||||
|
||||
const component = unref(getComponent);
|
||||
if (!component.includes('Select')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
|
||||
const option = options.find((item) => `${item.value}` === `${value}`);
|
||||
|
||||
return option?.label ?? value;
|
||||
});
|
||||
|
||||
const getWrapperStyle = computed((): CSSProperties => {
|
||||
if (unref(getIsCheckComp) || unref(getRowEditable)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
width: 'calc(100% - 48px)',
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapperClass = computed(() => {
|
||||
const { align = 'center' } = props.column;
|
||||
return `edit-cell-align-${align}`;
|
||||
});
|
||||
|
||||
const getRowEditable = computed(() => {
|
||||
const { editable } = props.record || {};
|
||||
return !!editable;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
defaultValueRef.value = props.value;
|
||||
currentValueRef.value = props.value;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const { editable } = props.column;
|
||||
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
|
||||
isEdit.value = !!editable || unref(getRowEditable);
|
||||
}
|
||||
});
|
||||
|
||||
function handleEdit() {
|
||||
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
|
||||
ruleMessage.value = '';
|
||||
isEdit.value = true;
|
||||
nextTick(() => {
|
||||
const el = unref(elRef);
|
||||
el?.focus?.();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleChange(e: any) {
|
||||
const component = unref(getComponent);
|
||||
if (!e) {
|
||||
currentValueRef.value = e;
|
||||
} else if (e?.target && Reflect.has(e.target, 'value')) {
|
||||
currentValueRef.value = (e as ChangeEvent).target.value;
|
||||
} else if (component === 'Checkbox') {
|
||||
currentValueRef.value = (e as ChangeEvent).target.checked;
|
||||
} else if (isString(e) || isBoolean(e) || isNumber(e) || isArray(e)) {
|
||||
currentValueRef.value = e;
|
||||
}
|
||||
const onChange = props.column?.editComponentProps?.onChange;
|
||||
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||
|
||||
table.emit?.('edit-change', {
|
||||
column: props.column,
|
||||
value: unref(currentValueRef),
|
||||
record: toRaw(props.record),
|
||||
});
|
||||
handleSubmiRule();
|
||||
}
|
||||
|
||||
async function handleSubmiRule() {
|
||||
const { column, record } = props;
|
||||
const { editRule } = column;
|
||||
const currentValue = unref(currentValueRef);
|
||||
|
||||
if (editRule) {
|
||||
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
|
||||
ruleVisible.value = true;
|
||||
const component = unref(getComponent);
|
||||
ruleMessage.value = createPlaceholderMessage(component);
|
||||
return false;
|
||||
}
|
||||
if (isFunction(editRule)) {
|
||||
const res = await editRule(currentValue, record as Recordable);
|
||||
if (!!res) {
|
||||
ruleMessage.value = res;
|
||||
ruleVisible.value = true;
|
||||
return false;
|
||||
} else {
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSubmit(needEmit = true, valid = true) {
|
||||
if (valid) {
|
||||
const isPass = await handleSubmiRule();
|
||||
if (!isPass) return false;
|
||||
}
|
||||
|
||||
const { column, index, record } = props;
|
||||
if (!record) return false;
|
||||
const { key, dataIndex } = column;
|
||||
const value = unref(currentValueRef);
|
||||
if (!key || !dataIndex) return;
|
||||
|
||||
const dataKey = (dataIndex || key) as string;
|
||||
|
||||
if (!record.editable) {
|
||||
const { getBindValues } = table;
|
||||
|
||||
const { beforeEditSubmit, columns } = unref(getBindValues);
|
||||
|
||||
if (beforeEditSubmit && isFunction(beforeEditSubmit)) {
|
||||
spinning.value = true;
|
||||
const keys: string[] = columns.map((_column) => _column.dataIndex).filter((field) => !!field) as string[];
|
||||
let result: any = true;
|
||||
try {
|
||||
result = await beforeEditSubmit({
|
||||
record: pick(record, keys),
|
||||
index,
|
||||
key,
|
||||
value,
|
||||
});
|
||||
} catch (e) {
|
||||
result = false;
|
||||
} finally {
|
||||
spinning.value = false;
|
||||
}
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(record, dataKey, value);
|
||||
//const record = await table.updateTableData(index, dataKey, value);
|
||||
needEmit && table.emit?.('edit-end', { record, index, key, value });
|
||||
isEdit.value = false;
|
||||
}
|
||||
|
||||
async function handleEnter() {
|
||||
if (props.column?.editRow) {
|
||||
return;
|
||||
}
|
||||
handleSubmit();
|
||||
}
|
||||
|
||||
function handleSubmitClick() {
|
||||
handleSubmit();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
isEdit.value = false;
|
||||
currentValueRef.value = defaultValueRef.value;
|
||||
const { column, index, record } = props;
|
||||
const { key, dataIndex } = column;
|
||||
table.emit?.('edit-cancel', {
|
||||
record,
|
||||
index,
|
||||
key: dataIndex || key,
|
||||
value: unref(currentValueRef),
|
||||
});
|
||||
}
|
||||
|
||||
function onClickOutside() {
|
||||
if (props.column?.editable || unref(getRowEditable)) {
|
||||
return;
|
||||
}
|
||||
const component = unref(getComponent);
|
||||
|
||||
if (component.includes('Input')) {
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
|
||||
// only ApiSelect or TreeSelect
|
||||
function handleOptionsChange(options: LabelValueOptions) {
|
||||
const { replaceFields } = props.column?.editComponentProps ?? {};
|
||||
const component = unref(getComponent);
|
||||
if (component === 'ApiTreeSelect') {
|
||||
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
|
||||
let listOptions: Recordable[] = treeToList(options, { children });
|
||||
listOptions = listOptions.map((item) => {
|
||||
return {
|
||||
label: item[title],
|
||||
value: item[value],
|
||||
};
|
||||
});
|
||||
optionsRef.value = listOptions as LabelValueOptions;
|
||||
} else {
|
||||
optionsRef.value = options;
|
||||
}
|
||||
}
|
||||
|
||||
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||
if (props.record) {
|
||||
/* eslint-disable */
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
const { dataIndex, key } = props.column;
|
||||
const field: any = dataIndex || key;
|
||||
if (isArray(props.record[cbs])) {
|
||||
const findItem = props.record[cbs]?.find((item) => item[field]);
|
||||
if (findItem) {
|
||||
findItem[field] = handle;
|
||||
} else {
|
||||
props.record[cbs]?.push({ [field]: handle });
|
||||
}
|
||||
} else {
|
||||
props.record[cbs] = [{ [field]: handle }];
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
}
|
||||
}
|
||||
|
||||
if (props.record) {
|
||||
initCbs('submitCbs', handleSubmit);
|
||||
initCbs('validCbs', handleSubmiRule);
|
||||
initCbs('cancelCbs', handleCancel);
|
||||
|
||||
if (props.column.dataIndex) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
props.record.editValueRefs[props.column.dataIndex] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
isArray(props.record?.cancelCbs) &&
|
||||
props.record?.cancelCbs.forEach((item) => {
|
||||
const [fn] = Object.values(item);
|
||||
fn();
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
};
|
||||
/* eslint-disable */
|
||||
props.record.onSubmitEdit = async () => {
|
||||
if (isArray(props.record?.submitCbs)) {
|
||||
if (!props.record?.onValid?.()) return;
|
||||
const submitFns = props.record?.submitCbs || [];
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
submitFns.forEach((item) => {
|
||||
const [fn] = Object.values(item);
|
||||
fn(false, false);
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
table.emit?.('edit-row-end');
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isEdit,
|
||||
prefixCls,
|
||||
handleEdit,
|
||||
currentValueRef,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCancel,
|
||||
elRef,
|
||||
getComponent,
|
||||
getRule,
|
||||
onClickOutside,
|
||||
ruleMessage,
|
||||
getRuleVisible,
|
||||
getComponentProps,
|
||||
handleOptionsChange,
|
||||
getWrapperStyle,
|
||||
getWrapperClass,
|
||||
getRowEditable,
|
||||
getValues,
|
||||
handleEnter,
|
||||
handleSubmitClick,
|
||||
spinning,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-editable-cell';
|
||||
|
||||
.edit-cell-align-left {
|
||||
text-align: left;
|
||||
|
||||
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-cell-align-center {
|
||||
text-align: center;
|
||||
|
||||
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-cell-align-right {
|
||||
text-align: right;
|
||||
|
||||
input:not(.ant-calendar-picker-input, .ant-time-picker-input) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-cell-rule-popover {
|
||||
.ant-popover-inner-content {
|
||||
padding: 4px 8px;
|
||||
color: @error-color;
|
||||
// border: 1px solid @error-color;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.@{prefix-cls} {
|
||||
position: relative;
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> .ant-select {
|
||||
min-width: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
|
||||
svg {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis-cell {
|
||||
.cell-content {
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&__normal {
|
||||
&-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
display: none;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.@{prefix-cls}__normal-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ComponentType } from '../../types/componentType';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component.includes('Input')) {
|
||||
return t('common.inputText');
|
||||
}
|
||||
if (component.includes('Picker')) {
|
||||
return t('common.chooseText');
|
||||
}
|
||||
|
||||
if (
|
||||
component.includes('Select') ||
|
||||
component.includes('Checkbox') ||
|
||||
component.includes('Radio') ||
|
||||
component.includes('Switch') ||
|
||||
component.includes('DatePicker') ||
|
||||
component.includes('TimePicker')
|
||||
) {
|
||||
return t('common.chooseText');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { BasicColumn } from '/@/components/Table/src/types/table';
|
||||
|
||||
import { h, Ref, toRaw } from 'vue';
|
||||
|
||||
import EditableCell from './EditableCell.vue';
|
||||
import { isArray } from '/@/utils/is';
|
||||
|
||||
interface Params {
|
||||
text: string;
|
||||
record: Recordable;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export function renderEditCell(column: BasicColumn) {
|
||||
return ({ text: value, record, index }: Params) => {
|
||||
toRaw(record).onValid = async () => {
|
||||
if (isArray(record?.validCbs)) {
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
const validFns = (record?.validCbs || []).map((item) => {
|
||||
const [fn] = Object.values(item);
|
||||
// @ts-ignore
|
||||
return fn();
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1165】解决canResize为true时第一行校验不过
|
||||
const res = await Promise.all(validFns);
|
||||
return res.every((item) => !!item);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
toRaw(record).onEdit = async (edit: boolean, submit = false) => {
|
||||
if (!submit) {
|
||||
record.editable = edit;
|
||||
}
|
||||
|
||||
if (!edit && submit) {
|
||||
if (!(await record.onValid())) return false;
|
||||
const res = await record.onSubmitEdit?.();
|
||||
if (res) {
|
||||
record.editable = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// cancel
|
||||
if (!edit && !submit) {
|
||||
record.onCancelEdit?.();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return h(EditableCell, {
|
||||
value,
|
||||
record,
|
||||
column,
|
||||
index,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
interface Cbs {
|
||||
[key: string]: Fn;
|
||||
}
|
||||
|
||||
export type EditRecordRow<T = Recordable> = Partial<
|
||||
{
|
||||
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
|
||||
onValid: () => Promise<boolean>;
|
||||
editable: boolean;
|
||||
onCancel: Fn;
|
||||
onSubmit: Fn;
|
||||
submitCbs: Cbs[];
|
||||
cancelCbs: Cbs[];
|
||||
validCbs: Cbs[];
|
||||
editValueRefs: Recordable<Ref>;
|
||||
} & T
|
||||
>;
|
||||
@@ -0,0 +1,536 @@
|
||||
<template>
|
||||
<Tooltip placement="top" v-bind="getBindProps" >
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingColumn') }}</span>
|
||||
</template>
|
||||
<Popover
|
||||
v-model:open="popoverVisible"
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
@open-change="handleVisibleChange"
|
||||
:overlayClassName="`${prefixCls}__cloumn-list`"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
>
|
||||
<template #title>
|
||||
<div :class="`${prefixCls}__popover-title`">
|
||||
<Checkbox :indeterminate="indeterminate" v-model:checked="checkAll" @change="onCheckAllChange">
|
||||
{{ t('component.table.settingColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
{{ t('component.table.settingIndexColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<!-- <Checkbox-->
|
||||
<!-- v-model:checked="checkSelect"-->
|
||||
<!-- @change="handleSelectCheckChange"-->
|
||||
<!-- :disabled="!defaultRowSelection"-->
|
||||
<!-- >-->
|
||||
<!-- {{ t('component.table.settingSelectColumnShow') }}-->
|
||||
<!-- </Checkbox>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<ScrollContainer>
|
||||
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
|
||||
<template v-for="item in plainOptions" :key="item.value">
|
||||
<div :class="`${prefixCls}__check-item`" v-if="!('ifShow' in item && !item.ifShow)">
|
||||
<DragOutlined class="table-column-drag-icon" />
|
||||
<Checkbox :value="item.value">
|
||||
{{ item.label }}
|
||||
</Checkbox>
|
||||
|
||||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4" :getPopupContainer="getPopupContainer">
|
||||
<template #title>
|
||||
{{ t('component.table.settingFixedLeft') }}
|
||||
</template>
|
||||
<Icon
|
||||
icon="line-md:arrow-align-left"
|
||||
:class="[
|
||||
`${prefixCls}__fixed-left`,
|
||||
{
|
||||
active: item.fixed === 'left',
|
||||
disabled: !checkedList.includes(item.value),
|
||||
},
|
||||
]"
|
||||
@click="handleColumnFixed(item, 'left')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider type="vertical" />
|
||||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4" :getPopupContainer="getPopupContainer">
|
||||
<template #title>
|
||||
{{ t('component.table.settingFixedRight') }}
|
||||
</template>
|
||||
<Icon
|
||||
icon="line-md:arrow-align-left"
|
||||
:class="[
|
||||
`${prefixCls}__fixed-right`,
|
||||
{
|
||||
active: item.fixed === 'right',
|
||||
disabled: !checkedList.includes(item.value),
|
||||
},
|
||||
]"
|
||||
@click="handleColumnFixed(item, 'right')"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</CheckboxGroup>
|
||||
</ScrollContainer>
|
||||
<div :class="`${prefixCls}__popover-footer`">
|
||||
<a-button size="small" @click="reset">
|
||||
{{ t('common.resetText') }}
|
||||
</a-button>
|
||||
<a-button size="small" type="primary" @click="saveSetting"> 保存 </a-button>
|
||||
</div>
|
||||
</template>
|
||||
<SettingOutlined />
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { BasicColumn, ColumnChangeParam } from '../../types/table';
|
||||
import { defineComponent, ref, reactive, toRefs, watchEffect, nextTick, unref, computed } from 'vue';
|
||||
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
|
||||
import type { CheckboxChangeEvent } from 'ant-design-vue/lib/checkbox/interface';
|
||||
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { ScrollContainer } from '/@/components/Container';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { useColumnsCache } from '../../hooks/useColumnsCache';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
// import { useSortable } from '/@/hooks/web/useSortable';
|
||||
import { isFunction, isNullAndUnDef } from '/@/utils/is';
|
||||
import { getPopupContainer as getParentContainer } from '/@/utils';
|
||||
import { cloneDeep, omit } from 'lodash-es';
|
||||
import Sortablejs from 'sortablejs';
|
||||
import type Sortable from 'sortablejs';
|
||||
|
||||
interface State {
|
||||
checkAll: boolean;
|
||||
isInit?: boolean;
|
||||
checkedList: string[];
|
||||
defaultCheckList: string[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
label: string;
|
||||
value: string;
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ColumnSetting',
|
||||
props: {
|
||||
isMobile: Boolean,
|
||||
},
|
||||
components: {
|
||||
SettingOutlined,
|
||||
Popover,
|
||||
Tooltip,
|
||||
Checkbox,
|
||||
CheckboxGroup: Checkbox.Group,
|
||||
DragOutlined,
|
||||
ScrollContainer,
|
||||
Divider,
|
||||
Icon,
|
||||
},
|
||||
emits: ['columns-change'],
|
||||
|
||||
setup(props, { emit, attrs }) {
|
||||
const { t } = useI18n();
|
||||
const table = useTableContext();
|
||||
const popoverVisible = ref(false);
|
||||
// update-begin--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
|
||||
// nextTick(() => popoverVisible.value = false);
|
||||
// update-end--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
|
||||
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
|
||||
let inited = false;
|
||||
|
||||
const cachePlainOptions = ref<Options[]>([]);
|
||||
const plainOptions = ref<Options[] | any>([]);
|
||||
|
||||
const plainSortOptions = ref<Options[]>([]);
|
||||
|
||||
const columnListRef = ref<ComponentRef>(null);
|
||||
|
||||
const state = reactive<State>({
|
||||
checkAll: true,
|
||||
checkedList: [],
|
||||
defaultCheckList: [],
|
||||
});
|
||||
|
||||
const checkIndex = ref(false);
|
||||
const checkSelect = ref(false);
|
||||
|
||||
const { prefixCls } = useDesign('basic-column-setting');
|
||||
|
||||
const getValues = computed(() => {
|
||||
return unref(table?.getBindValues) || {};
|
||||
});
|
||||
|
||||
const getBindProps = computed(() => {
|
||||
let obj = {};
|
||||
if (props.isMobile) {
|
||||
obj['open'] = false;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
let sortable: Sortable;
|
||||
const sortableOrder = ref<string[]>();
|
||||
|
||||
// 列表字段配置缓存
|
||||
const { saveSetting, resetSetting } = useColumnsCache(
|
||||
{
|
||||
state,
|
||||
popoverVisible,
|
||||
plainOptions,
|
||||
plainSortOptions,
|
||||
sortableOrder,
|
||||
checkIndex,
|
||||
},
|
||||
setColumns,
|
||||
handleColumnFixed
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
setTimeout(() => {
|
||||
const columns = table.getColumns();
|
||||
if (columns.length && !state.isInit) {
|
||||
init();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const values = unref(getValues);
|
||||
checkIndex.value = !!values.showIndexColumn;
|
||||
checkSelect.value = !!values.rowSelection;
|
||||
});
|
||||
|
||||
function getColumns() {
|
||||
const ret: Options[] = [];
|
||||
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
|
||||
ret.push({
|
||||
label: (item.title as string) || (item.customTitle as string),
|
||||
value: (item.dataIndex || item.title) as string,
|
||||
...item,
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function init() {
|
||||
const columns = getColumns();
|
||||
|
||||
const checkList = table
|
||||
.getColumns({ ignoreAction: true })
|
||||
.map((item) => {
|
||||
if (item.defaultHidden) {
|
||||
return '';
|
||||
}
|
||||
return item.dataIndex || item.title;
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
if (!plainOptions.value.length) {
|
||||
plainOptions.value = columns;
|
||||
plainSortOptions.value = columns;
|
||||
cachePlainOptions.value = columns;
|
||||
state.defaultCheckList = checkList;
|
||||
} else {
|
||||
// const fixedColumns = columns.filter((item) =>
|
||||
// Reflect.has(item, 'fixed')
|
||||
// ) as BasicColumn[];
|
||||
|
||||
unref(plainOptions).forEach((item: BasicColumn) => {
|
||||
const findItem = columns.find((col: BasicColumn) => col.dataIndex === item.dataIndex);
|
||||
if (findItem) {
|
||||
item.fixed = findItem.fixed;
|
||||
}
|
||||
});
|
||||
}
|
||||
state.isInit = true;
|
||||
state.checkedList = checkList;
|
||||
}
|
||||
|
||||
// checkAll change
|
||||
function onCheckAllChange(e: CheckboxChangeEvent) {
|
||||
const checkList = plainOptions.value.map((item) => item.value);
|
||||
if (e.target.checked) {
|
||||
state.checkedList = checkList;
|
||||
setColumns(checkList);
|
||||
} else {
|
||||
state.checkedList = [];
|
||||
setColumns([]);
|
||||
}
|
||||
}
|
||||
|
||||
const indeterminate = computed(() => {
|
||||
const len = plainOptions.value.length;
|
||||
let checkedLen = state.checkedList.length;
|
||||
unref(checkIndex) && checkedLen--;
|
||||
return checkedLen > 0 && checkedLen < len;
|
||||
});
|
||||
|
||||
// Trigger when check/uncheck a column
|
||||
function onChange(checkedList: string[]) {
|
||||
const len = plainSortOptions.value.length;
|
||||
state.checkAll = checkedList.length === len;
|
||||
const sortList = unref(plainSortOptions).map((item) => item.value);
|
||||
checkedList.sort((prev, next) => {
|
||||
return sortList.indexOf(prev) - sortList.indexOf(next);
|
||||
});
|
||||
setColumns(checkedList);
|
||||
}
|
||||
|
||||
// reset columns
|
||||
function reset() {
|
||||
// state.checkedList = [...state.defaultCheckList];
|
||||
// update-begin--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
|
||||
state.checkedList = table
|
||||
.getColumns({ ignoreAction: true })
|
||||
.map((item) => {
|
||||
return item.dataIndex || item.title;
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
// update-end--author:liaozhiyang---date:20231103---for:【issues/825】tabel的列设置隐藏列保存后切换路由问题[重置没勾选]
|
||||
state.checkAll = true;
|
||||
plainOptions.value = unref(cachePlainOptions);
|
||||
plainSortOptions.value = unref(cachePlainOptions);
|
||||
setColumns(table.getCacheColumns());
|
||||
if (sortableOrder.value) {
|
||||
sortable.sort(sortableOrder.value);
|
||||
}
|
||||
resetSetting();
|
||||
}
|
||||
|
||||
// Open the pop-up window for drag and drop initialization
|
||||
function handleVisibleChange() {
|
||||
if (inited) return;
|
||||
// update-begin--author:liaozhiyang---date:20240529---for:【TV360X-254】列设置闪现及苹果浏览器弹窗过长
|
||||
setTimeout(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240529---for:【TV360X-254】列设置闪现及苹果浏览器弹窗过长
|
||||
const columnListEl = unref(columnListRef);
|
||||
if (!columnListEl) return;
|
||||
const el = columnListEl.$el as any;
|
||||
if (!el) return;
|
||||
// Drag and drop sort
|
||||
sortable = Sortablejs.create(unref(el), {
|
||||
animation: 500,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
handle: '.table-column-drag-icon ',
|
||||
onEnd: (evt) => {
|
||||
const { oldIndex, newIndex } = evt;
|
||||
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
// Sort column
|
||||
const columns = cloneDeep(plainSortOptions.value);
|
||||
|
||||
if (oldIndex > newIndex) {
|
||||
columns.splice(newIndex, 0, columns[oldIndex]);
|
||||
columns.splice(oldIndex + 1, 1);
|
||||
} else {
|
||||
columns.splice(newIndex + 1, 0, columns[oldIndex]);
|
||||
columns.splice(oldIndex, 1);
|
||||
}
|
||||
|
||||
plainSortOptions.value = columns;
|
||||
// update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6424】table字段列表设置不显示后,再拖拽字段顺序,原本不显示的,又显示了
|
||||
// update-begin--author:liaozhiyang---date:20240522---for:【TV360X-108】刷新后勾选之前未勾选的字段拖拽之后该字段对应的表格列消失了
|
||||
const cols = columns.map((item) => item.value);
|
||||
const arr = cols.filter((cItem) => state.checkedList.find((lItem) => lItem === cItem));
|
||||
setColumns(arr);
|
||||
// 最开始的代码
|
||||
// setColumns(columns);
|
||||
// update-end--author:liaozhiyang---date:20240522---for:【TV360X-108】刷新后勾选之前未勾选的字段拖拽之后该字段对应的表格列消失了
|
||||
// update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6424】table字段列表设置不显示后,再拖拽字段顺序,原本不显示的,又显示了
|
||||
},
|
||||
});
|
||||
// 记录原始 order 序列
|
||||
if (!sortableOrder.value) {
|
||||
sortableOrder.value = sortable.toArray();
|
||||
}
|
||||
inited = true;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Control whether the serial number column is displayed
|
||||
function handleIndexCheckChange(e: CheckboxChangeEvent) {
|
||||
table.setProps({
|
||||
showIndexColumn: e.target.checked,
|
||||
});
|
||||
}
|
||||
|
||||
// Control whether the check box is displayed
|
||||
function handleSelectCheckChange(e: CheckboxChangeEvent) {
|
||||
table.setProps({
|
||||
rowSelection: e.target.checked ? defaultRowSelection : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
|
||||
if (!state.checkedList.includes(item.dataIndex as string)) return;
|
||||
|
||||
const columns = getColumns() as BasicColumn[];
|
||||
const isFixed = item.fixed === fixed ? false : fixed;
|
||||
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
|
||||
if (index !== -1) {
|
||||
columns[index].fixed = isFixed;
|
||||
}
|
||||
item.fixed = isFixed;
|
||||
|
||||
if (isFixed && !item.width) {
|
||||
item.width = 100;
|
||||
}
|
||||
table.setCacheColumnsByField?.(item.dataIndex as string, { fixed: isFixed });
|
||||
setColumns(columns);
|
||||
}
|
||||
|
||||
function setColumns(columns: BasicColumn[] | string[]) {
|
||||
table.setColumns(columns);
|
||||
const data: ColumnChangeParam[] = unref(plainSortOptions).map((col) => {
|
||||
const visible =
|
||||
columns.findIndex((c: BasicColumn | string) => c === col.value || (typeof c !== 'string' && c.dataIndex === col.value)) !== -1;
|
||||
return { dataIndex: col.value, fixed: col.fixed, visible };
|
||||
});
|
||||
|
||||
emit('columns-change', data);
|
||||
}
|
||||
|
||||
function getPopupContainer() {
|
||||
return isFunction(attrs.getPopupContainer) ? attrs.getPopupContainer() : getParentContainer();
|
||||
}
|
||||
|
||||
return {
|
||||
getBindProps,
|
||||
t,
|
||||
...toRefs(state),
|
||||
popoverVisible,
|
||||
indeterminate,
|
||||
onCheckAllChange,
|
||||
onChange,
|
||||
plainOptions,
|
||||
reset,
|
||||
saveSetting,
|
||||
prefixCls,
|
||||
columnListRef,
|
||||
handleVisibleChange,
|
||||
checkIndex,
|
||||
checkSelect,
|
||||
handleIndexCheckChange,
|
||||
handleSelectCheckChange,
|
||||
defaultRowSelection,
|
||||
handleColumnFixed,
|
||||
getPopupContainer,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-column-setting';
|
||||
|
||||
.table-column-drag-icon {
|
||||
margin: 0 5px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__popover-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 卡片底部样式 */
|
||||
&__popover-footer {
|
||||
position: relative;
|
||||
top: 7px;
|
||||
text-align: right;
|
||||
padding: 4px 0 0;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.ant-btn {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&__check-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 100%;
|
||||
padding: 4px 16px 8px 0;
|
||||
|
||||
.ant-checkbox-wrapper {
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__fixed-left,
|
||||
&__fixed-right {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: @disabled-color;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&__fixed-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&__cloumn-list {
|
||||
svg {
|
||||
width: 1em !important;
|
||||
height: 1em !important;
|
||||
}
|
||||
|
||||
.ant-popover-inner-content {
|
||||
// max-height: 360px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
// overflow: auto;
|
||||
}
|
||||
|
||||
.ant-checkbox-group {
|
||||
// update-begin--author:liaozhiyang---date:20240118---for:【QQYUN-7887】表格列设置宽度过长
|
||||
// width: 100%;
|
||||
min-width: 260px;
|
||||
max-width: min-content;
|
||||
// update-end--author:liaozhiyang---date:20240118---for:【QQYUN-7887】表格列设置宽度过长
|
||||
// flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20240529---for:【TV360X-254】列设置闪现及苹果浏览器弹窗过长
|
||||
&.ant-popover,
|
||||
.ant-popover-content,
|
||||
.ant-popover-inner,
|
||||
.ant-popover-inner-content,
|
||||
.scroll-container,
|
||||
.scrollbar__wrap {
|
||||
max-width: min-content;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240529---for:【TV360X-254】列设置闪现及苹果浏览器弹窗过长
|
||||
.scrollbar {
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<Tooltip placement="top" v-bind="getBindProps">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingFullScreen') }}</span>
|
||||
</template>
|
||||
<FullscreenOutlined @click="toggle" v-if="!isFullscreen" />
|
||||
<FullscreenExitOutlined @click="toggle" v-else />
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FullScreenSetting',
|
||||
props: {
|
||||
isMobile: Boolean,
|
||||
},
|
||||
components: {
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
const { toggle, isFullscreen } = useFullscreen(table.wrapRef);
|
||||
const getBindProps = computed(() => {
|
||||
let obj = {};
|
||||
if (props.isMobile) {
|
||||
obj['visible'] = false;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
return {
|
||||
getBindProps,
|
||||
toggle,
|
||||
isFullscreen,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<Tooltip placement="top" v-bind="getBindProps">
|
||||
<template #title>
|
||||
<span>{{ t('common.redo') }}</span>
|
||||
</template>
|
||||
<RedoOutlined @click="redo" />
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { RedoOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedoSetting',
|
||||
props: {
|
||||
isMobile: Boolean,
|
||||
},
|
||||
components: {
|
||||
RedoOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
const getBindProps = computed(() => {
|
||||
let obj = {};
|
||||
if (props.isMobile) {
|
||||
obj['visible'] = false;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
function redo() {
|
||||
table.reload();
|
||||
table.emit!('table-redo');
|
||||
}
|
||||
|
||||
return { getBindProps, redo, t };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<Tooltip placement="top" v-bind="getBindProps">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingDens') }}</span>
|
||||
</template>
|
||||
|
||||
<Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
|
||||
<ColumnHeightOutlined />
|
||||
<template #overlay>
|
||||
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
|
||||
<MenuItem key="large">
|
||||
<span>{{ t('component.table.settingDensLarge') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="middle">
|
||||
<span>{{ t('component.table.settingDensMiddle') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="small">
|
||||
<span>{{ t('component.table.settingDensSmall') }}</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { SizeType } from '../../types/table';
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
|
||||
import { ColumnHeightOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { getPopupContainer } from '/@/utils';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { createLocalStorage } from '/@/utils/cache';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SizeSetting',
|
||||
props: {
|
||||
isMobile: Boolean,
|
||||
},
|
||||
components: {
|
||||
ColumnHeightOutlined,
|
||||
Tooltip,
|
||||
Dropdown,
|
||||
Menu,
|
||||
MenuItem: Menu.Item,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
const $ls = createLocalStorage();
|
||||
const route = useRoute();
|
||||
|
||||
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
|
||||
const getBindProps = computed(() => {
|
||||
let obj = {};
|
||||
if (props.isMobile) {
|
||||
obj['visible'] = false;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
function handleTitleClick({ key }: { key: SizeType }) {
|
||||
selectedKeysRef.value = [key];
|
||||
table.setProps({
|
||||
size: key,
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20240604---for:【TV360X-100】缓存表格密度
|
||||
$ls.set(cacheKey.value, key);
|
||||
// update-end--author:liaozhiyang---date:20240604---for:【TV360X-100】缓存表格密度
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240604---for:【TV360X-100】缓存表格密度
|
||||
const cacheKey = computed(() => {
|
||||
const path = route.path;
|
||||
let key = path.replace(/[\/\\]/g, '_');
|
||||
let cacheKey = table.getBindValues.value.tableSetting?.cacheKey;
|
||||
if (cacheKey) {
|
||||
key += ':' + cacheKey;
|
||||
}
|
||||
return 'tableSizeCache:' + key;
|
||||
});
|
||||
const local: SizeType | null = $ls.get(cacheKey.value);
|
||||
if (local) {
|
||||
selectedKeysRef.value = [local];
|
||||
table.setProps({
|
||||
size: local,
|
||||
});
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240604---for:【TV360X-100】缓存表格密度
|
||||
|
||||
return {
|
||||
getBindProps,
|
||||
handleTitleClick,
|
||||
selectedKeysRef,
|
||||
getPopupContainer,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="table-settings">
|
||||
<RedoSetting v-if="getSetting.redo" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
|
||||
<SizeSetting v-if="getSetting.size" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
|
||||
<ColumnSetting v-if="getSetting.setting" :isMobile="isMobile" @columns-change="handleColumnChange" :getPopupContainer="getTableContainer" />
|
||||
<FullScreenSetting v-if="getSetting.fullScreen" :isMobile="isMobile" :getPopupContainer="getTableContainer" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import type { TableSetting, ColumnChangeParam } from '../../types/table';
|
||||
import { defineComponent, computed, unref } from 'vue';
|
||||
import ColumnSetting from './ColumnSetting.vue';
|
||||
import SizeSetting from './SizeSetting.vue';
|
||||
import RedoSetting from './RedoSetting.vue';
|
||||
import FullScreenSetting from './FullScreenSetting.vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableSetting',
|
||||
components: {
|
||||
ColumnSetting,
|
||||
SizeSetting,
|
||||
RedoSetting,
|
||||
FullScreenSetting,
|
||||
},
|
||||
props: {
|
||||
setting: {
|
||||
type: Object as PropType<TableSetting>,
|
||||
default: () => ({}),
|
||||
},
|
||||
mode: String,
|
||||
},
|
||||
emits: ['columns-change'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const table = useTableContext();
|
||||
|
||||
const getSetting = computed((): TableSetting => {
|
||||
return {
|
||||
redo: true,
|
||||
size: true,
|
||||
setting: true,
|
||||
fullScreen: false,
|
||||
...props.setting,
|
||||
};
|
||||
});
|
||||
const isMobile = computed(() => props.mode === 'mobile');
|
||||
|
||||
function handleColumnChange(data: ColumnChangeParam[]) {
|
||||
emit('columns-change', data);
|
||||
}
|
||||
|
||||
function getTableContainer() {
|
||||
return table ? unref(table.wrapRef) : document.body;
|
||||
}
|
||||
|
||||
return { getSetting, t, handleColumnChange, getTableContainer, isMobile };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.table-settings {
|
||||
& > * {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
hx-ai-intelligent/src/components/Table/src/const.ts
Normal file
30
hx-ai-intelligent/src/components/Table/src/const.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import componentSetting from '/@/settings/componentSetting';
|
||||
|
||||
const { table } = componentSetting;
|
||||
|
||||
const { pageSizeOptions, defaultPageSize, defaultSize, fetchSetting, defaultSortFn, defaultFilterFn } = table;
|
||||
|
||||
export const ROW_KEY = 'key';
|
||||
|
||||
// Optional display number per page;
|
||||
export const PAGE_SIZE_OPTIONS = pageSizeOptions;
|
||||
|
||||
// Number of items displayed per page
|
||||
export const PAGE_SIZE = defaultPageSize;
|
||||
|
||||
// Common interface field settings
|
||||
export const FETCH_SETTING = fetchSetting;
|
||||
|
||||
// Configure general sort function
|
||||
export const DEFAULT_SORT_FN = defaultSortFn;
|
||||
|
||||
export const DEFAULT_FILTER_FN = defaultFilterFn;
|
||||
|
||||
// Default layout of table cells
|
||||
export const DEFAULT_ALIGN = 'center';
|
||||
// Default Size
|
||||
export const DEFAULT_SIZE = defaultSize;
|
||||
|
||||
export const INDEX_COLUMN_FLAG = 'INDEX';
|
||||
|
||||
export const ACTION_COLUMN_FLAG = 'ACTION';
|
||||
348
hx-ai-intelligent/src/components/Table/src/hooks/useColumns.ts
Normal file
348
hx-ai-intelligent/src/components/Table/src/hooks/useColumns.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import type { ComputedRef } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
import { computed, Ref, ref, toRaw, unref, watch, reactive } from 'vue';
|
||||
import { renderEditCell } from '../components/editable';
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isArray, isBoolean, isFunction, isMap, isString } from '/@/utils/is';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import { formatToDate } from '/@/utils/dateUtil';
|
||||
import { ACTION_COLUMN_FLAG, DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../const';
|
||||
import { CUS_SEL_COLUMN_KEY } from './useCustomSelection';
|
||||
|
||||
function handleItem(item: BasicColumn, ellipsis: boolean) {
|
||||
const { key, dataIndex, children } = item;
|
||||
item.align = item.align || DEFAULT_ALIGN;
|
||||
if (ellipsis) {
|
||||
if (!key) {
|
||||
item.key = dataIndex;
|
||||
}
|
||||
if (!isBoolean(item.ellipsis)) {
|
||||
Object.assign(item, {
|
||||
ellipsis,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (children && children.length) {
|
||||
handleChildren(children, !!ellipsis);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChildren(children: BasicColumn[] | undefined, ellipsis: boolean) {
|
||||
if (!children) return;
|
||||
children.forEach((item) => {
|
||||
const { children } = item;
|
||||
handleItem(item, ellipsis);
|
||||
handleChildren(children, ellipsis);
|
||||
});
|
||||
}
|
||||
|
||||
function handleIndexColumn(propsRef: ComputedRef<BasicTableProps>, getPaginationRef: ComputedRef<boolean | PaginationProps>, columns: BasicColumn[]) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { showIndexColumn, indexColumnProps, isTreeTable } = unref(propsRef);
|
||||
|
||||
let pushIndexColumns = false;
|
||||
if (unref(isTreeTable)) {
|
||||
return;
|
||||
}
|
||||
columns.forEach(() => {
|
||||
const indIndex = columns.findIndex((column) => column.flag === INDEX_COLUMN_FLAG);
|
||||
if (showIndexColumn) {
|
||||
pushIndexColumns = indIndex === -1;
|
||||
} else if (!showIndexColumn && indIndex !== -1) {
|
||||
columns.splice(indIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
if (!pushIndexColumns) return;
|
||||
|
||||
const isFixedLeft = columns.some((item) => item.fixed === 'left');
|
||||
|
||||
columns.unshift({
|
||||
flag: INDEX_COLUMN_FLAG,
|
||||
width: 50,
|
||||
title: t('component.table.index'),
|
||||
align: 'center',
|
||||
customRender: ({ index }) => {
|
||||
const getPagination = unref(getPaginationRef);
|
||||
if (isBoolean(getPagination)) {
|
||||
return `${index + 1}`;
|
||||
}
|
||||
const { current = 1, pageSize = PAGE_SIZE } = getPagination;
|
||||
return ((current < 1 ? 1 : current) - 1) * pageSize + index + 1;
|
||||
},
|
||||
...(isFixedLeft
|
||||
? {
|
||||
fixed: 'left',
|
||||
}
|
||||
: {}),
|
||||
...indexColumnProps,
|
||||
});
|
||||
}
|
||||
|
||||
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
|
||||
const { actionColumn, showActionColumn } = unref(propsRef);
|
||||
if (!actionColumn || !showActionColumn) return;
|
||||
|
||||
const hasIndex = columns.findIndex((column) => column.flag === ACTION_COLUMN_FLAG);
|
||||
if (hasIndex === -1) {
|
||||
columns.push({
|
||||
...columns[hasIndex],
|
||||
...actionColumn,
|
||||
flag: ACTION_COLUMN_FLAG,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function useColumns(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
getPaginationRef: ComputedRef<boolean | PaginationProps>,
|
||||
handleCustomSelectColumn: Fn
|
||||
) {
|
||||
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
|
||||
let cacheColumns = unref(propsRef).columns;
|
||||
|
||||
const getColumnsRef = computed(() => {
|
||||
const columns = cloneDeep(unref(columnsRef));
|
||||
|
||||
handleIndexColumn(propsRef, getPaginationRef, columns);
|
||||
handleActionColumn(propsRef, columns);
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
handleCustomSelectColumn(columns);
|
||||
// update-end--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
|
||||
if (!columns) {
|
||||
return [];
|
||||
}
|
||||
const { ellipsis } = unref(propsRef);
|
||||
|
||||
columns.forEach((item) => {
|
||||
const { customRender, slots } = item;
|
||||
|
||||
handleItem(item, Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots);
|
||||
});
|
||||
return columns;
|
||||
});
|
||||
|
||||
function isIfShow(column: BasicColumn): boolean {
|
||||
const ifShow = column.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(column);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const getViewColumns = computed(() => {
|
||||
const viewColumns = sortFixedColumn(unref(getColumnsRef));
|
||||
|
||||
const columns = cloneDeep(viewColumns);
|
||||
const result = columns
|
||||
.filter((column) => {
|
||||
return hasPermission(column.auth) && isIfShow(column);
|
||||
})
|
||||
.map((column) => {
|
||||
// update-begin--author:liaozhiyang---date:20230718---for: 【issues-179】antd3 一些警告以及报错(针对表格)
|
||||
if(column.slots?.customRender) {
|
||||
// slots的备份,兼容老的写法,转成新写法避免控制台警告
|
||||
column.slotsBak = column.slots;
|
||||
delete column.slots;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20230718---for: 【issues-179】antd3 一些警告以及报错(针对表格)
|
||||
|
||||
const { slots, customRender, format, edit, editRow, flag, title: metaTitle } = column;
|
||||
|
||||
if (!slots || !slots?.title) {
|
||||
// column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
|
||||
column.customTitle = column.title as string;
|
||||
Reflect.deleteProperty(column, 'title');
|
||||
}
|
||||
//update-begin-author:taoyan date:20211203 for:【online报表】分组标题显示错误,都显示成了联系信息 LOWCOD-2343
|
||||
if (column.children) {
|
||||
column.title = metaTitle;
|
||||
}
|
||||
//update-end-author:taoyan date:20211203 for:【online报表】分组标题显示错误,都显示成了联系信息 LOWCOD-2343
|
||||
|
||||
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
|
||||
if (!customRender && format && !edit && !isDefaultAction) {
|
||||
column.customRender = ({ text, record, index }) => {
|
||||
return formatCell(text, format, record, index);
|
||||
};
|
||||
}
|
||||
|
||||
// edit table
|
||||
if ((edit || editRow) && !isDefaultAction) {
|
||||
column.customRender = renderEditCell(column);
|
||||
}
|
||||
return reactive(column);
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20230919---for:【QQYUN-6387】展开写法(去掉报错)
|
||||
if (propsRef.value.expandedRowKeys && !propsRef.value.isTreeTable) {
|
||||
let index = 0;
|
||||
const findIndex = result.findIndex((item) => item.key === CUS_SEL_COLUMN_KEY);
|
||||
if (findIndex != -1) {
|
||||
index = findIndex + 1;
|
||||
}
|
||||
const next: any = result[index + 1];
|
||||
let expand = Table.EXPAND_COLUMN;
|
||||
if (next && (next['fixed'] == true || next['fixed'] == 'left')) {
|
||||
expand = Object.assign(expand, { fixed: 'left' });
|
||||
}
|
||||
result.splice(index, 0, expand);
|
||||
}
|
||||
return result;
|
||||
// update-end--author:liaozhiyang---date:20230919---for:【QQYUN-6387】展开写法(去掉报错)
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(propsRef).columns,
|
||||
(columns) => {
|
||||
columnsRef.value = columns;
|
||||
cacheColumns = columns?.filter((item) => !item.flag) ?? [];
|
||||
}
|
||||
);
|
||||
|
||||
function setCacheColumnsByField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
|
||||
if (!dataIndex || !value) {
|
||||
return;
|
||||
}
|
||||
cacheColumns.forEach((item) => {
|
||||
if (item.dataIndex === dataIndex) {
|
||||
Object.assign(item, value);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// update-begin--author:sunjianlei---date:20220523---for: 【VUEN-1089】合并vben最新版代码,解决表格字段排序问题
|
||||
/**
|
||||
* set columns
|
||||
* @param columnList key|column
|
||||
*/
|
||||
function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) {
|
||||
const columns = cloneDeep(columnList);
|
||||
if (!isArray(columns)) return;
|
||||
|
||||
if (columns.length <= 0) {
|
||||
columnsRef.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const firstColumn = columns[0];
|
||||
|
||||
const cacheKeys = cacheColumns.map((item) => item.dataIndex);
|
||||
|
||||
if (!isString(firstColumn) && !isArray(firstColumn)) {
|
||||
columnsRef.value = columns as BasicColumn[];
|
||||
} else {
|
||||
const columnKeys = (columns as (string | string[])[]).map((m) => m.toString());
|
||||
const newColumns: BasicColumn[] = [];
|
||||
cacheColumns.forEach((item) => {
|
||||
newColumns.push({
|
||||
...item,
|
||||
defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string)),
|
||||
});
|
||||
});
|
||||
// Sort according to another array
|
||||
if (!isEqual(cacheKeys, columns)) {
|
||||
newColumns.sort((prev, next) => {
|
||||
return columnKeys.indexOf(prev.dataIndex?.toString() as string) - columnKeys.indexOf(next.dataIndex?.toString() as string);
|
||||
});
|
||||
}
|
||||
columnsRef.value = newColumns;
|
||||
}
|
||||
}
|
||||
// update-end--author:sunjianlei---date:20220523---for: 【VUEN-1089】合并vben最新版代码,解决表格字段排序问题
|
||||
|
||||
function getColumns(opt?: GetColumnsParams) {
|
||||
const { ignoreIndex, ignoreAction, sort } = opt || {};
|
||||
let columns = toRaw(unref(getColumnsRef));
|
||||
if (ignoreIndex) {
|
||||
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
|
||||
}
|
||||
if (ignoreAction) {
|
||||
columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
|
||||
}
|
||||
// update-begin--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
// 过滤自定义选择列
|
||||
columns = columns.filter((item) => item.key !== CUS_SEL_COLUMN_KEY);
|
||||
// update-enb--author:sunjianlei---date:220230630---for:【QQYUN-5571】自封装选择列,解决数据行选择卡顿问题
|
||||
|
||||
if (sort) {
|
||||
columns = sortFixedColumn(columns);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
function getCacheColumns() {
|
||||
return cacheColumns;
|
||||
}
|
||||
|
||||
return {
|
||||
getColumnsRef,
|
||||
getCacheColumns,
|
||||
getColumns,
|
||||
setColumns,
|
||||
getViewColumns,
|
||||
setCacheColumnsByField,
|
||||
};
|
||||
}
|
||||
|
||||
function sortFixedColumn(columns: BasicColumn[]) {
|
||||
const fixedLeftColumns: BasicColumn[] = [];
|
||||
const fixedRightColumns: BasicColumn[] = [];
|
||||
const defColumns: BasicColumn[] = [];
|
||||
for (const column of columns) {
|
||||
if (column.fixed === 'left') {
|
||||
fixedLeftColumns.push(column);
|
||||
continue;
|
||||
}
|
||||
if (column.fixed === 'right') {
|
||||
fixedRightColumns.push(column);
|
||||
continue;
|
||||
}
|
||||
defColumns.push(column);
|
||||
}
|
||||
return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter((item) => !item.defaultHidden);
|
||||
}
|
||||
|
||||
// format cell
|
||||
export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) {
|
||||
if (!format) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// custom function
|
||||
if (isFunction(format)) {
|
||||
return format(text, record, index);
|
||||
}
|
||||
|
||||
try {
|
||||
// date type
|
||||
const DATE_FORMAT_PREFIX = 'date|';
|
||||
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
|
||||
const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
|
||||
|
||||
if (!dateFormat) {
|
||||
return text;
|
||||
}
|
||||
return formatToDate(text, dateFormat);
|
||||
}
|
||||
|
||||
// Map
|
||||
if (isMap(format)) {
|
||||
return format.get(text);
|
||||
}
|
||||
} catch (error) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { computed, nextTick, unref, watchEffect } from 'vue';
|
||||
import { router } from '/@/router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { createLocalStorage } from '/@/utils/cache';
|
||||
import { useTableContext } from './useTableContext';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
/**
|
||||
* 列表配置缓存
|
||||
*/
|
||||
export function useColumnsCache(opt, setColumns, handleColumnFixed) {
|
||||
let isInit = false;
|
||||
const table = useTableContext();
|
||||
const $ls = createLocalStorage();
|
||||
const { createMessage: $message } = useMessage();
|
||||
const route = useRoute();
|
||||
// 列表配置缓存key
|
||||
const cacheKey = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240226---for:【QQYUN-8367】online报表配置列展示保存,影响到其他页面的table字段的显示隐藏(开发环境热更新会有此问题,生产环境无问题)
|
||||
const path = route.path;
|
||||
let key = path.replace(/[\/\\]/g, '_');
|
||||
// update-end--author:liaozhiyang---date:20240226---for:【QQYUN-8367】online报表配置列展示保存,影响到其他页面的table字段的显示隐藏(开发环境热更新会有此问题,生产环境无问题)
|
||||
let cacheKey = table.getBindValues.value.tableSetting?.cacheKey;
|
||||
if (cacheKey) {
|
||||
key += ':' + cacheKey;
|
||||
}
|
||||
return 'columnCache:' + key;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const columns = table.getColumns();
|
||||
if (columns.length) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
async function init() {
|
||||
if (isInit) {
|
||||
return;
|
||||
}
|
||||
isInit = true;
|
||||
let columnCache = $ls.get(cacheKey.value);
|
||||
if (columnCache && columnCache.checkedList) {
|
||||
const { checkedList, sortedList, sortableOrder, checkIndex } = columnCache;
|
||||
await nextTick();
|
||||
// checkbox的排序缓存
|
||||
opt.sortableOrder.value = sortableOrder;
|
||||
// checkbox的选中缓存
|
||||
opt.state.checkedList = checkedList;
|
||||
// tableColumn的排序缓存
|
||||
opt.plainSortOptions.value.sort((prev, next) => {
|
||||
return sortedList.indexOf(prev.value) - sortedList.indexOf(next.value);
|
||||
});
|
||||
// 重新排序tableColumn
|
||||
checkedList.sort((prev, next) => sortedList.indexOf(prev) - sortedList.indexOf(next));
|
||||
// 是否显示行号列
|
||||
if (checkIndex) {
|
||||
table.setProps({ showIndexColumn: true });
|
||||
}
|
||||
setColumns(checkedList);
|
||||
// 设置固定列
|
||||
setColumnFixed(columnCache);
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置被固定的列 */
|
||||
async function setColumnFixed(columnCache) {
|
||||
const { fixedColumns } = columnCache;
|
||||
const columns = opt.plainOptions.value;
|
||||
for (const column of columns) {
|
||||
let fixedCol = fixedColumns.find((fc) => fc.key === (column.key || column.dataIndex));
|
||||
if (fixedCol) {
|
||||
await nextTick();
|
||||
handleColumnFixed(column, fixedCol.fixed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断列固定状态
|
||||
const fixedReg = /^(true|left|right)$/;
|
||||
|
||||
/** 获取被固定的列 */
|
||||
function getFixedColumns() {
|
||||
let fixedColumns: any[] = [];
|
||||
const columns = opt.plainOptions.value;
|
||||
for (const column of columns) {
|
||||
if (fixedReg.test((column.fixed ?? '').toString())) {
|
||||
fixedColumns.push({
|
||||
key: column.key || column.dataIndex,
|
||||
fixed: column.fixed === true ? 'left' : column.fixed,
|
||||
});
|
||||
}
|
||||
}
|
||||
return fixedColumns;
|
||||
}
|
||||
|
||||
/** 保存列配置 */
|
||||
function saveSetting() {
|
||||
const { checkedList } = opt.state;
|
||||
const sortedList = unref(opt.plainSortOptions).map((item) => item.value);
|
||||
$ls.set(cacheKey.value, {
|
||||
// 保存的列
|
||||
checkedList,
|
||||
// 排序后的列
|
||||
sortedList,
|
||||
// 是否显示行号列
|
||||
checkIndex: unref(opt.checkIndex),
|
||||
// checkbox原始排序
|
||||
sortableOrder: unref(opt.sortableOrder),
|
||||
// 固定列
|
||||
fixedColumns: getFixedColumns(),
|
||||
});
|
||||
$message.success('保存成功');
|
||||
// 保存之后直接关闭
|
||||
opt.popoverVisible.value = false;
|
||||
}
|
||||
|
||||
/** 重置(删除)列配置 */
|
||||
async function resetSetting() {
|
||||
// 重置固定列
|
||||
await resetFixedColumn();
|
||||
$ls.remove(cacheKey.value);
|
||||
$message.success('重置成功');
|
||||
}
|
||||
|
||||
async function resetFixedColumn() {
|
||||
const columns = opt.plainOptions.value;
|
||||
for (const column of columns) {
|
||||
column.fixed;
|
||||
if (fixedReg.test((column.fixed ?? '').toString())) {
|
||||
await nextTick();
|
||||
handleColumnFixed(column, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
saveSetting,
|
||||
resetSetting,
|
||||
};
|
||||
}
|
||||
108
hx-ai-intelligent/src/components/Table/src/hooks/useCustomRow.ts
Normal file
108
hx-ai-intelligent/src/components/Table/src/hooks/useCustomRow.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { unref } from 'vue';
|
||||
import { ROW_KEY } from '../const';
|
||||
import { isString, isFunction } from '/@/utils/is';
|
||||
|
||||
interface Options {
|
||||
setSelectedRowKeys: (keys: string[]) => void;
|
||||
getSelectRowKeys: () => string[];
|
||||
clearSelectedRowKeys: () => void;
|
||||
emit: EmitType;
|
||||
getAutoCreateKey: ComputedRef<boolean | undefined>;
|
||||
}
|
||||
|
||||
function getKey(record: Recordable, rowKey: string | ((record: Record<string, any>) => string) | undefined, autoCreateKey?: boolean) {
|
||||
if (!rowKey || autoCreateKey) {
|
||||
return record[ROW_KEY];
|
||||
}
|
||||
if (isString(rowKey)) {
|
||||
return record[rowKey];
|
||||
}
|
||||
if (isFunction(rowKey)) {
|
||||
return record[rowKey(record)];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useCustomRow(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
{ setSelectedRowKeys, getSelectRowKeys, getAutoCreateKey, clearSelectedRowKeys, emit }: Options
|
||||
) {
|
||||
const customRow = (record: Recordable, index: number) => {
|
||||
return {
|
||||
onClick: (e: Event) => {
|
||||
e?.stopPropagation();
|
||||
function handleClick() {
|
||||
const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef);
|
||||
if (!rowSelection || !clickToRowSelect) return;
|
||||
const keys = getSelectRowKeys();
|
||||
const key = getKey(record, rowKey, unref(getAutoCreateKey));
|
||||
if (!key) return;
|
||||
|
||||
const isCheckbox = rowSelection.type === 'checkbox';
|
||||
if (isCheckbox) {
|
||||
// 找到tr
|
||||
const tr: HTMLElement = (e as MouseEvent).composedPath?.().find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement;
|
||||
if (!tr) return;
|
||||
// 找到Checkbox,检查是否为disabled
|
||||
const checkBox = tr.querySelector('input[type=checkbox]');
|
||||
if (!checkBox || checkBox.hasAttribute('disabled')) return;
|
||||
if (!keys.includes(key)) {
|
||||
setSelectedRowKeys([...keys, key]);
|
||||
return;
|
||||
}
|
||||
const keyIndex = keys.findIndex((item) => item === key);
|
||||
keys.splice(keyIndex, 1);
|
||||
setSelectedRowKeys(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
const isRadio = rowSelection.type === 'radio';
|
||||
if (isRadio) {
|
||||
// update-begin--author:liaozhiyang---date:20231016---for:【QQYUN-6794】table列表增加radio禁用功能
|
||||
const rowSelection = propsRef.value.rowSelection;
|
||||
if (rowSelection.getCheckboxProps) {
|
||||
const result = rowSelection.getCheckboxProps(record);
|
||||
if (result.disabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231016---for:【QQYUN-6794】table列表增加radio禁用功能
|
||||
if (!keys.includes(key)) {
|
||||
if (keys.length) {
|
||||
clearSelectedRowKeys();
|
||||
}
|
||||
setSelectedRowKeys([key]);
|
||||
return;
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20240527---for:【TV360X-359】erp主表点击已选中的选到了最后一个
|
||||
// 点击已经选中的,直接return不在做操作
|
||||
return;
|
||||
// update-end--author:liaozhiyang---date:20240527---for:【TV360X-359】erp主表点击已选中的选到了最后一个
|
||||
}
|
||||
clearSelectedRowKeys();
|
||||
}
|
||||
}
|
||||
handleClick();
|
||||
emit('row-click', record, index, e);
|
||||
},
|
||||
onDblclick: (event: Event) => {
|
||||
emit('row-dbClick', record, index, event);
|
||||
},
|
||||
onContextmenu: (event: Event) => {
|
||||
emit('row-contextmenu', record, index, event);
|
||||
},
|
||||
onMouseenter: (event: Event) => {
|
||||
emit('row-mouseenter', record, index, event);
|
||||
},
|
||||
onMouseleave: (event: Event) => {
|
||||
emit('row-mouseleave', record, index, event);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
customRow,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
import type { BasicColumn } from '/@/components/Table';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import type { BasicTableProps, PaginationProps, TableRowSelection } from '/@/components/Table';
|
||||
import { computed, nextTick, onUnmounted, ref, toRaw, unref, watch, watchEffect } from 'vue';
|
||||
import { omit, isEqual } from 'lodash-es';
|
||||
import { throttle } from 'lodash-es';
|
||||
import { Checkbox, Radio } from 'ant-design-vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { findNodeAll } from '/@/utils/helper/treeHelper';
|
||||
import { ROW_KEY } from '/@/components/Table/src/const';
|
||||
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { ModalFunc } from 'ant-design-vue/lib/modal/Modal';
|
||||
|
||||
// 自定义选择列的key
|
||||
export const CUS_SEL_COLUMN_KEY = 'j-custom-selected-column';
|
||||
|
||||
/**
|
||||
* 自定义选择列
|
||||
*/
|
||||
export function useCustomSelection(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
emit: EmitType,
|
||||
wrapRef: Ref<null | HTMLDivElement>,
|
||||
getPaginationRef: ComputedRef<boolean | PaginationProps>,
|
||||
tableData: Ref<Recordable[]>,
|
||||
childrenColumnName: ComputedRef<string>
|
||||
) {
|
||||
const { createConfirm } = useMessage();
|
||||
// 表格body元素
|
||||
const bodyEl = ref<HTMLDivElement>();
|
||||
// body元素高度
|
||||
const bodyHeight = ref<number>(0);
|
||||
// 表格tr高度
|
||||
const rowHeight = ref<number>(0);
|
||||
// body 滚动高度
|
||||
const scrollTop = ref(0);
|
||||
// 选择的key
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
// 选择的行
|
||||
const selectedRows = ref<Recordable[]>([]);
|
||||
// 变更的行
|
||||
let changeRows: Recordable[] = [];
|
||||
let allSelected: boolean = false;
|
||||
|
||||
// 扁平化数据,children数据也会放到一起
|
||||
const flattedData = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20231016---for:【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
|
||||
const data = flattenData(tableData.value, childrenColumnName.value);
|
||||
const rowSelection = propsRef.value.rowSelection;
|
||||
if (rowSelection?.type === 'checkbox' && rowSelection.getCheckboxProps) {
|
||||
for (let i = 0, len = data.length; i < len; i++) {
|
||||
const record = data[i];
|
||||
const result = rowSelection.getCheckboxProps(record);
|
||||
if (result.disabled) {
|
||||
data.splice(i, 1);
|
||||
i--;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
// update-end--author:liaozhiyang---date:20231016---for:【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
|
||||
});
|
||||
|
||||
const getRowSelectionRef = computed((): TableRowSelection | null => {
|
||||
const { rowSelection } = unref(propsRef);
|
||||
if (!rowSelection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
preserveSelectedRowKeys: true,
|
||||
// selectedRowKeys: unref(selectedKeys),
|
||||
// onChange: (selectedRowKeys: string[]) => {
|
||||
// setSelectedRowKeys(selectedRowKeys);
|
||||
// },
|
||||
...omit(rowSelection, ['onChange', 'selectedRowKeys']),
|
||||
};
|
||||
});
|
||||
|
||||
// 是否是单选
|
||||
const isRadio = computed(() => {
|
||||
return getRowSelectionRef.value?.type === 'radio';
|
||||
});
|
||||
|
||||
const getAutoCreateKey = computed(() => {
|
||||
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
|
||||
});
|
||||
|
||||
// 列key字段
|
||||
const getRowKey = computed(() => {
|
||||
const { rowKey } = unref(propsRef);
|
||||
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
|
||||
});
|
||||
// 获取行的key字段数据
|
||||
const getRecordKey = (record) => {
|
||||
if (!getRowKey.value) {
|
||||
return record[ROW_KEY];
|
||||
} else if (isFunction(getRowKey.value)) {
|
||||
return getRowKey.value(record);
|
||||
} else {
|
||||
return record[getRowKey.value];
|
||||
}
|
||||
};
|
||||
|
||||
// 分页配置
|
||||
const getPagination = computed<PaginationProps>(() => {
|
||||
return typeof getPaginationRef.value === 'boolean' ? {} : getPaginationRef.value;
|
||||
});
|
||||
// 当前页条目数量
|
||||
const currentPageSize = computed(() => {
|
||||
const { pageSize = 10, total = flattedData.value.length } = getPagination.value;
|
||||
return pageSize > total ? total : pageSize;
|
||||
});
|
||||
|
||||
// 选择列表头props
|
||||
const selectHeaderProps = computed(() => {
|
||||
return {
|
||||
onSelectAll,
|
||||
isRadio: isRadio.value,
|
||||
selectedLength: flattedData.value.filter((data) => selectedKeys.value.includes(getRecordKey(data))).length,
|
||||
// update-begin--author:liaozhiyang---date:20240511---for:【QQYUN-9289】解决表格条数不足pageSize数量时行数全部勾选但是全选框不勾选
|
||||
// 【TV360X-53】为空时会报错,加强判断
|
||||
pageSize: tableData.value?.length ?? 0,
|
||||
// update-end--author:liaozhiyang---date:20240511---for:【QQYUN-9289】解决表格条数不足pageSize数量时行数全部勾选但是全选框不勾选
|
||||
// 【QQYUN-6774】解决checkbox禁用后全选仍能勾选问题
|
||||
disabled: flattedData.value.length == 0,
|
||||
hideSelectAll: unref(propsRef)?.rowSelection?.hideSelectAll,
|
||||
};
|
||||
});
|
||||
|
||||
// 监听传入的selectedRowKeys
|
||||
// update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8390】部门人员组件点击重置未清空(selectedRowKeys.value=[],watch没监听到加deep)
|
||||
watch(
|
||||
() => unref(propsRef)?.rowSelection?.selectedRowKeys,
|
||||
(val: string[]) => {
|
||||
// 解决selectedRowKeys在页面调用处使用ref失效
|
||||
const value = unref(val);
|
||||
if (Array.isArray(value) && !sameArray(value, selectedKeys.value)) {
|
||||
setSelectedRowKeys(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8390】部门人员组件点击重置未清空(selectedRowKeys.value=[],watch没监听到加deep)
|
||||
|
||||
/**
|
||||
* 2024-03-06
|
||||
* liaozhiyang
|
||||
* 判断是否同一个数组 (引用地址,长度,元素位置信息相同才是同一个数组。数组元素只有字符串)
|
||||
*/
|
||||
function sameArray(a, b) {
|
||||
if (a === b) {
|
||||
if (a.length === b.length) {
|
||||
return a.toString() === b.toString();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20240425---for:【QQYUN-9123】popupdict打开弹窗打开程序运行
|
||||
if (isEqual(a, b)) {
|
||||
return true;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240425---for:【QQYUN-9123】popupdict打开弹窗打开程序运行
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 当任意一个变化时,触发同步检测
|
||||
watch([selectedKeys, selectedRows], () => {
|
||||
nextTick(() => {
|
||||
syncSelectedRows();
|
||||
});
|
||||
});
|
||||
|
||||
// 监听滚动条事件
|
||||
const onScrollTopChange = throttle((e) => (scrollTop.value = e?.target?.scrollTop), 150);
|
||||
|
||||
let bodyResizeObserver: Nullable<ResizeObserver> = null;
|
||||
// 获取首行行高
|
||||
watchEffect(() => {
|
||||
if (bodyEl.value) {
|
||||
// 监听div高度变化
|
||||
bodyResizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
if (entry.target === bodyEl.value && entry.contentRect) {
|
||||
const { height } = entry.contentRect;
|
||||
bodyHeight.value = Math.ceil(height);
|
||||
}
|
||||
}
|
||||
});
|
||||
bodyResizeObserver.observe(bodyEl.value);
|
||||
const el = bodyEl.value?.querySelector('tbody.ant-table-tbody tr.ant-table-row') as HTMLDivElement;
|
||||
if (el) {
|
||||
rowHeight.value = el.offsetHeight;
|
||||
return;
|
||||
}
|
||||
}
|
||||
rowHeight.value = 50;
|
||||
// 这种写法是为了监听到 size 的变化
|
||||
propsRef.value.size && void 0;
|
||||
});
|
||||
|
||||
onMountedOrActivated(async () => {
|
||||
bodyEl.value = await getTableBody(wrapRef.value!);
|
||||
bodyEl.value.addEventListener('scroll', onScrollTopChange);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (bodyEl.value) {
|
||||
bodyEl.value?.removeEventListener('scroll', onScrollTopChange);
|
||||
}
|
||||
if (bodyResizeObserver != null) {
|
||||
bodyResizeObserver.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
// 选择全部
|
||||
function onSelectAll(checked: boolean) {
|
||||
// update-begin--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
|
||||
if (unref(propsRef)?.rowSelection?.onSelectAll) {
|
||||
allSelected = checked;
|
||||
changeRows = getInvertRows(selectedRows.value);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
|
||||
// 取消全选
|
||||
if (!checked) {
|
||||
// update-begin--author:liaozhiyang---date:20240510---for:【issues/1173】取消全选只是当前页面取消
|
||||
// selectedKeys.value = [];
|
||||
// selectedRows.value = [];
|
||||
// emitChange('all');
|
||||
flattedData.value.forEach((item) => {
|
||||
updateSelected(item, false);
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240510---for:【issues/1173】取消全选只是当前页面取消
|
||||
return;
|
||||
}
|
||||
let modal: Nullable<ReturnType<ModalFunc>> = null;
|
||||
// 全选
|
||||
const checkAll = () => {
|
||||
if (modal != null) {
|
||||
modal.update({
|
||||
content: '正在分批全选,请稍后……',
|
||||
cancelButtonProps: { disabled: true },
|
||||
});
|
||||
}
|
||||
let showCount = 0;
|
||||
// 最小选中数量
|
||||
let minSelect = 100;
|
||||
const hidden: Recordable[] = [];
|
||||
flattedData.value.forEach((item, index, array) => {
|
||||
if (array.length > 120) {
|
||||
if (showCount <= minSelect && recordIsShow(index, Math.max((minSelect - 10) / 2, 3))) {
|
||||
showCount++;
|
||||
updateSelected(item, checked);
|
||||
} else {
|
||||
hidden.push(item);
|
||||
}
|
||||
} else {
|
||||
updateSelected(item, checked);
|
||||
}
|
||||
});
|
||||
if (hidden.length > 0) {
|
||||
return batchesSelectAll(hidden, checked, minSelect);
|
||||
} else {
|
||||
emitChange('all');
|
||||
}
|
||||
};
|
||||
|
||||
// 当数据量大于120条时,全选会导致页面卡顿,需进行慢速全选
|
||||
if (flattedData.value.length > 120) {
|
||||
modal = createConfirm({
|
||||
title: '全选',
|
||||
content: '当前数据量较大,全选可能会导致页面卡顿,确定要执行此操作吗?',
|
||||
iconType: 'warning',
|
||||
onOk: () => checkAll(),
|
||||
});
|
||||
} else {
|
||||
checkAll();
|
||||
}
|
||||
}
|
||||
|
||||
// 分批全选
|
||||
function batchesSelectAll(hidden: Recordable[], checked: boolean, minSelect: number) {
|
||||
return new Promise<void>((resolve) => {
|
||||
(function call() {
|
||||
// 每隔半秒钟,选择100条数据
|
||||
setTimeout(() => {
|
||||
const list = hidden.splice(0, minSelect);
|
||||
if (list.length > 0) {
|
||||
list.forEach((item) => {
|
||||
updateSelected(item, checked);
|
||||
});
|
||||
call();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
emitChange('all');
|
||||
// update-begin--author:liaozhiyang---date:20230811---for:【QQYUN-5687】批量选择,提示成功后,又来一个提示
|
||||
setTimeout(() =>resolve(), 0);
|
||||
// update-end--author:liaozhiyang---date:20230811---for:【QQYUN-5687】批量选择,提示成功后,又来一个提示
|
||||
}, 500);
|
||||
}
|
||||
}, 300);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
// 选中单个
|
||||
function onSelect(record, checked) {
|
||||
updateSelected(record, checked);
|
||||
emitChange();
|
||||
}
|
||||
|
||||
function updateSelected(record, checked) {
|
||||
const recordKey = getRecordKey(record);
|
||||
if (isRadio.value) {
|
||||
selectedKeys.value = [recordKey];
|
||||
selectedRows.value = [record];
|
||||
return;
|
||||
}
|
||||
const index = selectedKeys.value.findIndex((key) => key === recordKey);
|
||||
if (checked) {
|
||||
if (index === -1) {
|
||||
selectedKeys.value.push(recordKey);
|
||||
selectedRows.value.push(record);
|
||||
}
|
||||
} else {
|
||||
if (index !== -1) {
|
||||
selectedKeys.value.splice(index, 1);
|
||||
selectedRows.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调用用户自定义的onChange事件
|
||||
function emitChange(mode = 'single') {
|
||||
const { rowSelection } = unref(propsRef);
|
||||
if (rowSelection) {
|
||||
const { onChange } = rowSelection;
|
||||
if (onChange && isFunction(onChange)) {
|
||||
setTimeout(() => {
|
||||
onChange(selectedKeys.value, selectedRows.value);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
emit('selection-change', {
|
||||
keys: getSelectRowKeys(),
|
||||
rows: getSelectRows(),
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发onSelectAll事件
|
||||
if (mode == 'all') {
|
||||
const rowSelection = unref(propsRef)?.rowSelection;
|
||||
if (rowSelection?.onSelectAll) {
|
||||
rowSelection.onSelectAll(allSelected, toRaw(getSelectRows()), toRaw(changeRows));
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231122---for:【issues/5577】BasicTable组件全选和取消全选时不触发
|
||||
}
|
||||
|
||||
// 用于判断是否是自定义选择列
|
||||
function isCustomSelection(column: BasicColumn) {
|
||||
return column.key === CUS_SEL_COLUMN_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前行是否可视,虚拟滚动用
|
||||
* @param index 行下标
|
||||
* @param threshold 前后阈值,默认可视区域前后显示3条
|
||||
*/
|
||||
function recordIsShow(index: number, threshold = 3) {
|
||||
// 只有数据量大于50条时,才会进行虚拟滚动
|
||||
const isVirtual = flattedData.value.length > 50;
|
||||
if (isVirtual) {
|
||||
// 根据 scrollTop、bodyHeight、rowHeight 计算出当前行是否可视(阈值前后3条)
|
||||
// flag1 = 判断当前行是否在可视区域上方3条
|
||||
const flag1 = scrollTop.value - rowHeight.value * threshold < index * rowHeight.value;
|
||||
// flag2 = 判断当前行是否在可视区域下方3条
|
||||
const flag2 = index * rowHeight.value < scrollTop.value + bodyHeight.value + rowHeight.value * threshold;
|
||||
// 全部条件满足时,才显示当前行
|
||||
return flag1 && flag2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 自定义渲染Body
|
||||
function bodyCustomRender(params) {
|
||||
const { index } = params;
|
||||
// update-begin--author:liaozhiyang---date:20231009--for:【issues/776】显示100条/页,复选框只能显示3个的问题
|
||||
if (propsRef.value.canResize && !recordIsShow(index)) {
|
||||
return '';
|
||||
}
|
||||
if (isRadio.value) {
|
||||
return renderRadioComponent(params);
|
||||
} else {
|
||||
return renderCheckboxComponent(params);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231009---for:【issues/776】显示100条/页,复选框只能显示3个的问题
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染checkbox组件
|
||||
*/
|
||||
function renderCheckboxComponent({ record }) {
|
||||
const recordKey = getRecordKey(record);
|
||||
// 获取用户自定义checkboxProps
|
||||
const checkboxProps = ((getCheckboxProps) => {
|
||||
if (typeof getCheckboxProps === 'function') {
|
||||
try {
|
||||
return getCheckboxProps(record) ?? {};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
})(propsRef.value.rowSelection?.getCheckboxProps);
|
||||
return (
|
||||
<Checkbox
|
||||
{...checkboxProps}
|
||||
key={'j-select__' + recordKey}
|
||||
checked={selectedKeys.value.includes(recordKey)}
|
||||
onUpdate:checked={(checked) => onSelect(record, checked)}
|
||||
// update-begin--author:liaozhiyang---date:20230326---for:【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
// update-end--author:liaozhiyang---date:20230326---for:【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染radio组件
|
||||
*/
|
||||
function renderRadioComponent({ record }) {
|
||||
const recordKey = getRecordKey(record);
|
||||
// update-begin--author:liaozhiyang---date:20231016---for:【QQYUN-6794】table列表增加radio禁用功能
|
||||
// 获取用户自定义radioProps
|
||||
const checkboxProps = (() => {
|
||||
const rowSelection = propsRef.value.rowSelection;
|
||||
if (rowSelection?.getCheckboxProps) {
|
||||
return rowSelection.getCheckboxProps(record);
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
// update-end--author:liaozhiyang---date:20231016---for:【QQYUN-6794】table列表增加radio禁用功能
|
||||
return (
|
||||
<Radio
|
||||
{...checkboxProps}
|
||||
key={'j-select__' + recordKey}
|
||||
checked={selectedKeys.value.includes(recordKey)}
|
||||
onUpdate:checked={(checked) => onSelect(record, checked)}
|
||||
// update-begin--author:liaozhiyang---date:20230326---for:【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
// update-end--author:liaozhiyang---date:20230326---for:【QQYUN-8694】BasicTable在使用clickToRowSelect=true下,selection-change 事件在触发多次
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// 创建选择列
|
||||
function handleCustomSelectColumn(columns: BasicColumn[]) {
|
||||
// update-begin--author:liaozhiyang---date:20230919---for:【issues/757】JPopup表格的选择列固定配置不生效
|
||||
const rowSelection = propsRef.value.rowSelection;
|
||||
if (!rowSelection) {
|
||||
return;
|
||||
}
|
||||
const isFixedLeft = rowSelection.fixed || columns.some((item) => item.fixed === 'left');
|
||||
// update-begin--author:liaozhiyang---date:20230919---for:【issues/757】JPopup表格的选择列固定配置不生效
|
||||
columns.unshift({
|
||||
title: '选择列',
|
||||
flag: 'CHECKBOX',
|
||||
key: CUS_SEL_COLUMN_KEY,
|
||||
width: 50,
|
||||
minWidth: 50,
|
||||
maxWidth: 50,
|
||||
align: 'center',
|
||||
...(isFixedLeft ? { fixed: 'left' } : {}),
|
||||
customRender: bodyCustomRender,
|
||||
});
|
||||
}
|
||||
|
||||
// 清空所有选择
|
||||
function clearSelectedRowKeys() {
|
||||
onSelectAll(false);
|
||||
}
|
||||
|
||||
// 通过 selectedKeys 同步 selectedRows
|
||||
function syncSelectedRows() {
|
||||
if (selectedKeys.value.length !== selectedRows.value.length) {
|
||||
setSelectedRowKeys(selectedKeys.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置选择的key
|
||||
function setSelectedRowKeys(rowKeys: string[]) {
|
||||
const isSomeRowKeys = selectedKeys.value === rowKeys;
|
||||
selectedKeys.value = rowKeys;
|
||||
const allSelectedRows = findNodeAll(
|
||||
toRaw(unref(flattedData)).concat(toRaw(unref(selectedRows))),
|
||||
(item) => rowKeys.includes(getRecordKey(item)),
|
||||
{
|
||||
children: propsRef.value.childrenColumnName ?? 'children',
|
||||
}
|
||||
);
|
||||
const trueSelectedRows: any[] = [];
|
||||
rowKeys.forEach((key: string) => {
|
||||
const found = allSelectedRows.find((item) => getRecordKey(item) === key);
|
||||
found && trueSelectedRows.push(found);
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20231103---for:【issues/828】解决卡死问题
|
||||
if (!(isSomeRowKeys && equal(selectedRows.value, trueSelectedRows))) {
|
||||
selectedRows.value = trueSelectedRows;
|
||||
emitChange();
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231103---for:【issues/828】解决卡死问题
|
||||
}
|
||||
/**
|
||||
*2023-11-03
|
||||
*廖志阳
|
||||
*检测selectedRows.value和trueSelectedRows是否相等,防止死循环
|
||||
*/
|
||||
function equal(oldVal, newVal) {
|
||||
let oldKeys = [],
|
||||
newKeys = [];
|
||||
if (oldVal.length === newVal.length) {
|
||||
oldKeys = oldVal.map((item) => getRecordKey(item));
|
||||
newKeys = newVal.map((item) => getRecordKey(item));
|
||||
for (let i = 0, len = oldKeys.length; i < len; i++) {
|
||||
const findItem = newKeys.find((item) => item === oldKeys[i]);
|
||||
if (!findItem) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
*2023-11-22
|
||||
*廖志阳
|
||||
*根据全选或者反选返回源数据中这次需要变更的数据
|
||||
*/
|
||||
function getInvertRows(rows: any): any {
|
||||
const allRows = findNodeAll(toRaw(unref(flattedData)), () => true, {
|
||||
children: propsRef.value.childrenColumnName ?? 'children',
|
||||
});
|
||||
if (rows.length === 0 || rows.length === allRows.length) {
|
||||
return allRows;
|
||||
} else {
|
||||
const allRowsKey = allRows.map((item) => getRecordKey(item));
|
||||
rows.forEach((rItem) => {
|
||||
const rItemKey = getRecordKey(rItem);
|
||||
const findIndex = allRowsKey.findIndex((item) => rItemKey === item);
|
||||
if (findIndex != -1) {
|
||||
allRowsKey.splice(findIndex, 1);
|
||||
allRows.splice(findIndex, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
return allRows;
|
||||
}
|
||||
function getSelectRows<T = Recordable>() {
|
||||
return unref(selectedRows) as T[];
|
||||
}
|
||||
|
||||
function getSelectRowKeys() {
|
||||
return unref(selectedKeys);
|
||||
}
|
||||
|
||||
function getRowSelection() {
|
||||
return unref(getRowSelectionRef)!;
|
||||
}
|
||||
|
||||
function deleteSelectRowByKey(key: string) {
|
||||
const index = selectedKeys.value.findIndex((item) => item === key);
|
||||
if (index !== -1) {
|
||||
selectedKeys.value.splice(index, 1);
|
||||
selectedRows.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 【QQYUN-5837】动态计算 expandIconColumnIndex
|
||||
const getExpandIconColumnIndex = computed(() => {
|
||||
const { expandIconColumnIndex } = unref(propsRef);
|
||||
// 未设置选择列,则保持不变
|
||||
if (getRowSelectionRef.value == null) {
|
||||
return expandIconColumnIndex;
|
||||
}
|
||||
// 设置了选择列,并且未传入 index 参数,则返回 1
|
||||
if (expandIconColumnIndex == null) {
|
||||
return 1;
|
||||
}
|
||||
return expandIconColumnIndex;
|
||||
});
|
||||
|
||||
return {
|
||||
getRowSelection,
|
||||
getRowSelectionRef,
|
||||
getSelectRows,
|
||||
getSelectRowKeys,
|
||||
setSelectedRowKeys,
|
||||
deleteSelectRowByKey,
|
||||
selectHeaderProps,
|
||||
isCustomSelection,
|
||||
handleCustomSelectColumn,
|
||||
clearSelectedRowKeys,
|
||||
getExpandIconColumnIndex,
|
||||
};
|
||||
}
|
||||
|
||||
function getTableBody(wrap: HTMLDivElement) {
|
||||
return new Promise<HTMLDivElement>((resolve) => {
|
||||
(function fn() {
|
||||
const bodyEl = wrap.querySelector('.ant-table-wrapper .ant-table-body') as HTMLDivElement;
|
||||
if (bodyEl) {
|
||||
resolve(bodyEl);
|
||||
} else {
|
||||
setTimeout(fn, 100);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function flattenData<RecordType>(data: RecordType[] | undefined, childrenColumnName: string): RecordType[] {
|
||||
let list: RecordType[] = [];
|
||||
(data || []).forEach((record) => {
|
||||
list.push(record);
|
||||
|
||||
if (record && typeof record === 'object' && childrenColumnName in record) {
|
||||
list = [...list, ...flattenData<RecordType>((record as any)[childrenColumnName], childrenColumnName)];
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import { ref, unref, ComputedRef, computed, onMounted, watch, reactive, Ref, watchEffect } from 'vue';
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { isFunction, isBoolean } from '/@/utils/is';
|
||||
import { get, cloneDeep } from 'lodash-es';
|
||||
import { FETCH_SETTING, ROW_KEY, PAGE_SIZE } from '../const';
|
||||
|
||||
interface ActionType {
|
||||
getPaginationInfo: ComputedRef<boolean | PaginationProps>;
|
||||
setPagination: (info: Partial<PaginationProps>) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
// update-begin--author:sunjianlei---date:220220419---for:由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
|
||||
validate: () => Recordable;
|
||||
// update-end--author:sunjianlei---date:220220419---for:由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
|
||||
clearSelectedRowKeys: () => void;
|
||||
tableData: Ref<Recordable[]>;
|
||||
}
|
||||
|
||||
interface SearchState {
|
||||
sortInfo: Recordable;
|
||||
filterInfo: Record<string, string[]>;
|
||||
}
|
||||
export function useDataSource(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
{ getPaginationInfo, setPagination, setLoading, validate, clearSelectedRowKeys, tableData }: ActionType,
|
||||
emit: EmitType
|
||||
) {
|
||||
const searchState = reactive<SearchState>({
|
||||
sortInfo: {},
|
||||
filterInfo: {},
|
||||
});
|
||||
const dataSourceRef = ref<Recordable[]>([]);
|
||||
const rawDataSourceRef = ref<Recordable>({});
|
||||
|
||||
watchEffect(() => {
|
||||
tableData.value = unref(dataSourceRef);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(propsRef).dataSource,
|
||||
() => {
|
||||
const { dataSource, api } = unref(propsRef);
|
||||
!api && dataSource && (dataSourceRef.value = dataSource);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function handleTableChange(pagination: PaginationProps, filters: Partial<Recordable<string[]>>, sorter: SorterResult) {
|
||||
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
|
||||
if (clearSelectOnPageChange) {
|
||||
clearSelectedRowKeys();
|
||||
}
|
||||
setPagination(pagination);
|
||||
|
||||
const params: Recordable = {};
|
||||
if (sorter && isFunction(sortFn)) {
|
||||
const sortInfo = sortFn(sorter);
|
||||
searchState.sortInfo = sortInfo;
|
||||
params.sortInfo = sortInfo;
|
||||
}
|
||||
|
||||
if (filters && isFunction(filterFn)) {
|
||||
const filterInfo = filterFn(filters);
|
||||
searchState.filterInfo = filterInfo;
|
||||
params.filterInfo = filterInfo;
|
||||
}
|
||||
fetch(params);
|
||||
}
|
||||
|
||||
function setTableKey(items: any[]) {
|
||||
if (!items || !Array.isArray(items)) return;
|
||||
items.forEach((item) => {
|
||||
if (!item[ROW_KEY]) {
|
||||
item[ROW_KEY] = buildUUID();
|
||||
}
|
||||
if (item.children && item.children.length) {
|
||||
setTableKey(item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const getAutoCreateKey = computed(() => {
|
||||
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
|
||||
});
|
||||
|
||||
const getRowKey = computed(() => {
|
||||
const { rowKey } = unref(propsRef);
|
||||
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
|
||||
});
|
||||
|
||||
const getDataSourceRef = computed(() => {
|
||||
const dataSource = unref(dataSourceRef);
|
||||
if (!dataSource || dataSource.length === 0) {
|
||||
return unref(dataSourceRef);
|
||||
}
|
||||
if (unref(getAutoCreateKey)) {
|
||||
const firstItem = dataSource[0];
|
||||
const lastItem = dataSource[dataSource.length - 1];
|
||||
|
||||
if (firstItem && lastItem) {
|
||||
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
|
||||
const data = cloneDeep(unref(dataSourceRef));
|
||||
data.forEach((item) => {
|
||||
if (!item[ROW_KEY]) {
|
||||
item[ROW_KEY] = buildUUID();
|
||||
}
|
||||
if (item.children && item.children.length) {
|
||||
setTableKey(item.children);
|
||||
}
|
||||
});
|
||||
dataSourceRef.value = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return unref(dataSourceRef);
|
||||
});
|
||||
|
||||
async function updateTableData(index: number, key: string, value: any) {
|
||||
const record = dataSourceRef.value[index];
|
||||
if (record) {
|
||||
dataSourceRef.value[index][key] = value;
|
||||
}
|
||||
return dataSourceRef.value[index];
|
||||
}
|
||||
|
||||
function updateTableDataRecord(rowKey: string | number, record: Recordable): Recordable | undefined {
|
||||
const row = findTableDataRecord(rowKey);
|
||||
|
||||
if (row) {
|
||||
for (const field in row) {
|
||||
if (Reflect.has(record, field)) row[field] = record[field];
|
||||
//update-begin---author:wangshuai---date:2024-06-11---for:【TV360X-437】树表 部分组件编辑完后,列表未刷新---
|
||||
if (Reflect.has(record, field + '_dictText')) {
|
||||
row[field + '_dictText'] = record[field + '_dictText'];
|
||||
}
|
||||
//update-end---author:wangshuai---date:2024-06-11---for:【TV360X-437】树表 部分组件编辑完后,列表未刷新---
|
||||
}
|
||||
return row;
|
||||
}
|
||||
}
|
||||
function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
|
||||
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
|
||||
const rowKeyName = unref(getRowKey);
|
||||
if (!rowKeyName) return;
|
||||
const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
|
||||
for (const key of rowKeys) {
|
||||
let index: number | undefined = dataSourceRef.value.findIndex((row) => {
|
||||
let targetKeyName: string;
|
||||
if (typeof rowKeyName === 'function') {
|
||||
targetKeyName = rowKeyName(row);
|
||||
} else {
|
||||
targetKeyName = rowKeyName as string;
|
||||
}
|
||||
return row[targetKeyName] === key;
|
||||
});
|
||||
if (index >= 0) {
|
||||
dataSourceRef.value.splice(index, 1);
|
||||
}
|
||||
index = unref(propsRef).dataSource?.findIndex((row) => {
|
||||
let targetKeyName: string;
|
||||
if (typeof rowKeyName === 'function') {
|
||||
targetKeyName = rowKeyName(row);
|
||||
} else {
|
||||
targetKeyName = rowKeyName as string;
|
||||
}
|
||||
return row[targetKeyName] === key;
|
||||
});
|
||||
if (typeof index !== 'undefined' && index !== -1) unref(propsRef).dataSource?.splice(index, 1);
|
||||
}
|
||||
setPagination({
|
||||
total: unref(propsRef).dataSource?.length,
|
||||
});
|
||||
}
|
||||
|
||||
function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
|
||||
// if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
|
||||
index = index ?? dataSourceRef.value?.length;
|
||||
unref(dataSourceRef).splice(index, 0, record);
|
||||
return unref(dataSourceRef);
|
||||
}
|
||||
function findTableDataRecord(rowKey: string | number) {
|
||||
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
|
||||
|
||||
const rowKeyName = unref(getRowKey);
|
||||
if (!rowKeyName) return;
|
||||
|
||||
const { childrenColumnName = 'children' } = unref(propsRef);
|
||||
|
||||
const findRow = (array: any[]) => {
|
||||
let ret;
|
||||
array.some(function iter(r) {
|
||||
if (typeof rowKeyName === 'function') {
|
||||
if ((rowKeyName(r) as string) === rowKey) {
|
||||
ret = r;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
|
||||
ret = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return r[childrenColumnName] && r[childrenColumnName].some(iter);
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
// const row = dataSourceRef.value.find(r => {
|
||||
// if (typeof rowKeyName === 'function') {
|
||||
// return (rowKeyName(r) as string) === rowKey
|
||||
// } else {
|
||||
// return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
|
||||
// }
|
||||
// })
|
||||
return findRow(dataSourceRef.value);
|
||||
}
|
||||
|
||||
async function fetch(opt?: FetchParams) {
|
||||
const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = unref(propsRef);
|
||||
if (!api || !isFunction(api)) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting);
|
||||
let pageParams: Recordable = {};
|
||||
|
||||
const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps;
|
||||
|
||||
if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
|
||||
pageParams = {};
|
||||
} else {
|
||||
pageParams[pageField] = (opt && opt.page) || current;
|
||||
pageParams[sizeField] = pageSize;
|
||||
}
|
||||
|
||||
const { sortInfo = {}, filterInfo } = searchState;
|
||||
|
||||
let params: Recordable = {
|
||||
...pageParams,
|
||||
// 由于 getFieldsValue 返回的不是逗号分割的数据,所以改用 validate
|
||||
...(useSearchForm ? await validate() : {}),
|
||||
...searchInfo,
|
||||
...defSort,
|
||||
...(opt?.searchInfo ?? {}),
|
||||
...sortInfo,
|
||||
...filterInfo,
|
||||
...(opt?.sortInfo ?? {}),
|
||||
...(opt?.filterInfo ?? {}),
|
||||
};
|
||||
if (beforeFetch && isFunction(beforeFetch)) {
|
||||
params = (await beforeFetch(params)) || params;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240227---for:【QQYUN-8316】table查询条件,请求剔除空字符串字段
|
||||
for (let item of Object.entries(params)) {
|
||||
const [key, val] = item;
|
||||
if (val === '') {
|
||||
delete params[key];
|
||||
};
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240227---for:【QQYUN-8316】table查询条件,请求剔除空字符串字段
|
||||
const res = await api(params);
|
||||
rawDataSourceRef.value = res;
|
||||
|
||||
const isArrayResult = Array.isArray(res);
|
||||
|
||||
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
|
||||
const resultTotal: number = isArrayResult ? 0 : get(res, totalField);
|
||||
|
||||
// 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行
|
||||
if (resultTotal) {
|
||||
const currentTotalPage = Math.ceil(Number(resultTotal) / pageSize);
|
||||
if (current > currentTotalPage) {
|
||||
setPagination({
|
||||
current: currentTotalPage,
|
||||
});
|
||||
return await fetch(opt);
|
||||
}
|
||||
}
|
||||
|
||||
if (afterFetch && isFunction(afterFetch)) {
|
||||
resultItems = (await afterFetch(resultItems)) || resultItems;
|
||||
}
|
||||
dataSourceRef.value = resultItems;
|
||||
setPagination({
|
||||
total: Number(resultTotal) || 0,
|
||||
});
|
||||
if (opt && opt.page) {
|
||||
setPagination({
|
||||
current: opt.page || 1,
|
||||
});
|
||||
}
|
||||
emit('fetch-success', {
|
||||
items: unref(resultItems),
|
||||
total: Number(resultTotal),
|
||||
});
|
||||
return resultItems;
|
||||
} catch (error) {
|
||||
emit('fetch-error', error);
|
||||
dataSourceRef.value = [];
|
||||
setPagination({
|
||||
total: 0,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function setTableData<T = Recordable>(values: T[]) {
|
||||
dataSourceRef.value = values;
|
||||
}
|
||||
|
||||
function getDataSource<T = Recordable>() {
|
||||
return getDataSourceRef.value as T[];
|
||||
}
|
||||
|
||||
function getRawDataSource<T = Recordable>() {
|
||||
return rawDataSourceRef.value as T;
|
||||
}
|
||||
|
||||
async function reload(opt?: FetchParams) {
|
||||
return await fetch(opt);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
useTimeoutFn(() => {
|
||||
unref(propsRef).immediate && fetch();
|
||||
}, 16);
|
||||
});
|
||||
|
||||
return {
|
||||
getDataSourceRef,
|
||||
getDataSource,
|
||||
getRawDataSource,
|
||||
getRowKey,
|
||||
setTableData,
|
||||
getAutoCreateKey,
|
||||
fetch,
|
||||
reload,
|
||||
updateTableData,
|
||||
updateTableDataRecord,
|
||||
deleteTableDataRecord,
|
||||
insertTableDataRecord,
|
||||
findTableDataRecord,
|
||||
handleTableChange,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ref, ComputedRef, unref, computed, watch } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
|
||||
export function useLoading(props: ComputedRef<BasicTableProps>) {
|
||||
const loadingRef = ref(unref(props).loading);
|
||||
|
||||
watch(
|
||||
() => unref(props).loading,
|
||||
(loading) => {
|
||||
loadingRef.value = loading;
|
||||
}
|
||||
);
|
||||
|
||||
const getLoading = computed(() => unref(loadingRef));
|
||||
|
||||
function setLoading(loading: boolean) {
|
||||
loadingRef.value = loading;
|
||||
}
|
||||
|
||||
return { getLoading, setLoading };
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { computed, unref, ref, ComputedRef, watch } from 'vue';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import { isBoolean } from '/@/utils/is';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
interface ItemRender {
|
||||
page: number;
|
||||
type: 'page' | 'prev' | 'next';
|
||||
originalElement: any;
|
||||
}
|
||||
|
||||
function itemRender({ page, type, originalElement }: ItemRender) {
|
||||
if (type === 'prev') {
|
||||
return page === 0 ? null : <LeftOutlined />;
|
||||
} else if (type === 'next') {
|
||||
return page === 1 ? null : <RightOutlined />;
|
||||
}
|
||||
return originalElement;
|
||||
}
|
||||
|
||||
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const configRef = ref<PaginationProps>({});
|
||||
const show = ref(true);
|
||||
|
||||
watch(
|
||||
() => unref(refProps).pagination,
|
||||
(pagination) => {
|
||||
if (!isBoolean(pagination) && pagination) {
|
||||
configRef.value = {
|
||||
...unref(configRef),
|
||||
...(pagination ?? {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const getPaginationInfo = computed((): PaginationProps | boolean => {
|
||||
const { pagination } = unref(refProps);
|
||||
|
||||
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
current: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
size: 'small',
|
||||
defaultPageSize: PAGE_SIZE,
|
||||
showTotal: (total) => t('component.table.total', { total }),
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
||||
itemRender: itemRender,
|
||||
showQuickJumper: true,
|
||||
...(isBoolean(pagination) ? {} : pagination),
|
||||
...unref(configRef),
|
||||
};
|
||||
});
|
||||
|
||||
function setPagination(info: Partial<PaginationProps>) {
|
||||
const paginationInfo = unref(getPaginationInfo);
|
||||
configRef.value = {
|
||||
...(!isBoolean(paginationInfo) ? paginationInfo : {}),
|
||||
...info,
|
||||
};
|
||||
}
|
||||
|
||||
function getPagination() {
|
||||
return unref(getPaginationInfo);
|
||||
}
|
||||
|
||||
function getShowPagination() {
|
||||
return unref(show);
|
||||
}
|
||||
|
||||
async function setShowPagination(flag: boolean) {
|
||||
show.value = flag;
|
||||
}
|
||||
|
||||
return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination };
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import type { BasicTableProps, TableRowSelection } from '../types/table';
|
||||
import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue';
|
||||
import { ROW_KEY } from '../const';
|
||||
import { omit } from 'lodash-es';
|
||||
import { findNodeAll } from '/@/utils/helper/treeHelper';
|
||||
|
||||
export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, tableData: Ref<Recordable[]>, emit: EmitType) {
|
||||
const selectedRowKeysRef = ref<string[]>([]);
|
||||
const selectedRowRef = ref<Recordable[]>([]);
|
||||
|
||||
const getRowSelectionRef = computed((): TableRowSelection | null => {
|
||||
const { rowSelection } = unref(propsRef);
|
||||
if (!rowSelection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// AntDV3.0 之后使用远程加载数据进行分页时,
|
||||
// 默认会清空上一页选择的行数据(导致无法跨页选择),
|
||||
// 将此属性设置为 true 即可解决。
|
||||
preserveSelectedRowKeys: true,
|
||||
selectedRowKeys: unref(selectedRowKeysRef),
|
||||
onChange: (selectedRowKeys: string[]) => {
|
||||
setSelectedRowKeys(selectedRowKeys);
|
||||
},
|
||||
...omit(rowSelection, ['onChange']),
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(propsRef).rowSelection?.selectedRowKeys,
|
||||
(v: string[]) => {
|
||||
setSelectedRowKeys(v);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => unref(selectedRowKeysRef),
|
||||
() => {
|
||||
nextTick(() => {
|
||||
const { rowSelection } = unref(propsRef);
|
||||
if (rowSelection) {
|
||||
const { onChange } = rowSelection;
|
||||
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
|
||||
}
|
||||
//update-begin---author:scott ---date:2023-06-19 for:【issues/503】table行选择时卡顿明显 #503---
|
||||
//table行选择时卡顿明显 #503
|
||||
if (unref(tableData).length > 0) {
|
||||
emit('selection-change', {
|
||||
keys: getSelectRowKeys(),
|
||||
rows: getSelectRows(),
|
||||
});
|
||||
}
|
||||
//update-end---author:scott ---date::2023-06-19 for:【issues/503】table行选择时卡顿明显 #503---
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const getAutoCreateKey = computed(() => {
|
||||
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
|
||||
});
|
||||
|
||||
const getRowKey = computed(() => {
|
||||
const { rowKey } = unref(propsRef);
|
||||
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
|
||||
});
|
||||
|
||||
function setSelectedRowKeys(rowKeys: string[]) {
|
||||
selectedRowKeysRef.value = rowKeys;
|
||||
const allSelectedRows = findNodeAll(
|
||||
toRaw(unref(tableData)).concat(toRaw(unref(selectedRowRef))),
|
||||
(item) => rowKeys.includes(item[unref(getRowKey) as string]),
|
||||
{
|
||||
children: propsRef.value.childrenColumnName ?? 'children',
|
||||
}
|
||||
);
|
||||
const trueSelectedRows: any[] = [];
|
||||
rowKeys.forEach((key: string) => {
|
||||
const found = allSelectedRows.find((item) => item[unref(getRowKey) as string] === key);
|
||||
found && trueSelectedRows.push(found);
|
||||
});
|
||||
selectedRowRef.value = trueSelectedRows;
|
||||
}
|
||||
|
||||
function setSelectedRows(rows: Recordable[]) {
|
||||
selectedRowRef.value = rows;
|
||||
}
|
||||
|
||||
function clearSelectedRowKeys() {
|
||||
selectedRowRef.value = [];
|
||||
selectedRowKeysRef.value = [];
|
||||
}
|
||||
|
||||
function deleteSelectRowByKey(key: string) {
|
||||
const selectedRowKeys = unref(selectedRowKeysRef);
|
||||
const index = selectedRowKeys.findIndex((item) => item === key);
|
||||
if (index !== -1) {
|
||||
unref(selectedRowKeysRef).splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectRowKeys() {
|
||||
return unref(selectedRowKeysRef);
|
||||
}
|
||||
|
||||
function getSelectRows<T = Recordable>() {
|
||||
// const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item));
|
||||
return unref(selectedRowRef) as T[];
|
||||
}
|
||||
|
||||
function getRowSelection() {
|
||||
return unref(getRowSelectionRef)!;
|
||||
}
|
||||
|
||||
return {
|
||||
getRowSelection,
|
||||
getRowSelectionRef,
|
||||
getSelectRows,
|
||||
getSelectRowKeys,
|
||||
setSelectedRowKeys,
|
||||
clearSelectedRowKeys,
|
||||
deleteSelectRowByKey,
|
||||
setSelectedRows,
|
||||
};
|
||||
}
|
||||
168
hx-ai-intelligent/src/components/Table/src/hooks/useTable.ts
Normal file
168
hx-ai-intelligent/src/components/Table/src/hooks/useTable.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import type { DynamicProps } from '/#/utils';
|
||||
import type { FormActionType } from '/@/components/Form';
|
||||
import type { WatchStopHandle } from 'vue';
|
||||
import { getDynamicProps } from '/@/utils';
|
||||
import { ref, onUnmounted, unref, watch, toRaw } from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
import { error } from '/@/utils/log';
|
||||
|
||||
type Props = Partial<DynamicProps<BasicTableProps>>;
|
||||
|
||||
type UseTableMethod = TableActionType & {
|
||||
getForm: () => FormActionType;
|
||||
};
|
||||
|
||||
export function useTable(tableProps?: Props): [
|
||||
(instance: TableActionType, formInstance: UseTableMethod) => void,
|
||||
TableActionType & {
|
||||
getForm: () => FormActionType;
|
||||
}
|
||||
] {
|
||||
const tableRef = ref<Nullable<TableActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
const formRef = ref<Nullable<UseTableMethod>>(null);
|
||||
|
||||
let stopWatch: WatchStopHandle;
|
||||
|
||||
function register(instance: TableActionType, formInstance: UseTableMethod) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
tableRef.value = null;
|
||||
loadedRef.value = null;
|
||||
});
|
||||
|
||||
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) return;
|
||||
|
||||
tableRef.value = instance;
|
||||
formRef.value = formInstance;
|
||||
tableProps && instance.setProps(getDynamicProps(tableProps));
|
||||
loadedRef.value = true;
|
||||
|
||||
stopWatch?.();
|
||||
|
||||
stopWatch = watch(
|
||||
() => tableProps,
|
||||
() => {
|
||||
tableProps && instance.setProps(getDynamicProps(tableProps));
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getTableInstance(): TableActionType {
|
||||
const table = unref(tableRef);
|
||||
if (!table) {
|
||||
error('The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!');
|
||||
}
|
||||
return table as TableActionType;
|
||||
}
|
||||
|
||||
function getTableRef(){
|
||||
return tableRef;
|
||||
}
|
||||
|
||||
const methods: TableActionType & {
|
||||
getForm: () => FormActionType;
|
||||
} & {
|
||||
getTableRef: () => any;
|
||||
} = {
|
||||
reload: async (opt?: FetchParams) => {
|
||||
return await getTableInstance().reload(opt);
|
||||
},
|
||||
setProps: (props: Partial<BasicTableProps>) => {
|
||||
getTableInstance().setProps(props);
|
||||
},
|
||||
redoHeight: () => {
|
||||
getTableInstance().redoHeight();
|
||||
},
|
||||
setLoading: (loading: boolean) => {
|
||||
getTableInstance().setLoading(loading);
|
||||
},
|
||||
getDataSource: () => {
|
||||
return getTableInstance().getDataSource();
|
||||
},
|
||||
getRawDataSource: () => {
|
||||
return getTableInstance().getRawDataSource();
|
||||
},
|
||||
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
|
||||
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
|
||||
return toRaw(columns);
|
||||
},
|
||||
setColumns: (columns: BasicColumn[]) => {
|
||||
getTableInstance().setColumns(columns);
|
||||
},
|
||||
setTableData: (values: any[]) => {
|
||||
return getTableInstance().setTableData(values);
|
||||
},
|
||||
setPagination: (info: Partial<PaginationProps>) => {
|
||||
return getTableInstance().setPagination(info);
|
||||
},
|
||||
deleteSelectRowByKey: (key: string) => {
|
||||
getTableInstance().deleteSelectRowByKey(key);
|
||||
},
|
||||
getSelectRowKeys: () => {
|
||||
return toRaw(getTableInstance().getSelectRowKeys());
|
||||
},
|
||||
getSelectRows: () => {
|
||||
return toRaw(getTableInstance().getSelectRows());
|
||||
},
|
||||
clearSelectedRowKeys: () => {
|
||||
getTableInstance().clearSelectedRowKeys();
|
||||
},
|
||||
setSelectedRowKeys: (keys: string[] | number[]) => {
|
||||
getTableInstance().setSelectedRowKeys(keys);
|
||||
},
|
||||
getPaginationRef: () => {
|
||||
return getTableInstance().getPaginationRef();
|
||||
},
|
||||
getSize: () => {
|
||||
return toRaw(getTableInstance().getSize());
|
||||
},
|
||||
updateTableData: (index: number, key: string, value: any) => {
|
||||
return getTableInstance().updateTableData(index, key, value);
|
||||
},
|
||||
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
|
||||
return getTableInstance().deleteTableDataRecord(rowKey);
|
||||
},
|
||||
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
|
||||
return getTableInstance().insertTableDataRecord(record, index);
|
||||
},
|
||||
updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
|
||||
return getTableInstance().updateTableDataRecord(rowKey, record);
|
||||
},
|
||||
findTableDataRecord: (rowKey: string | number) => {
|
||||
return getTableInstance().findTableDataRecord(rowKey);
|
||||
},
|
||||
getRowSelection: () => {
|
||||
return toRaw(getTableInstance().getRowSelection());
|
||||
},
|
||||
getCacheColumns: () => {
|
||||
return toRaw(getTableInstance().getCacheColumns());
|
||||
},
|
||||
getForm: () => {
|
||||
return unref(formRef) as unknown as FormActionType;
|
||||
},
|
||||
setShowPagination: async (show: boolean) => {
|
||||
getTableInstance().setShowPagination(show);
|
||||
},
|
||||
getShowPagination: () => {
|
||||
return toRaw(getTableInstance().getShowPagination());
|
||||
},
|
||||
expandAll: () => {
|
||||
getTableInstance().expandAll();
|
||||
},
|
||||
collapseAll: () => {
|
||||
getTableInstance().collapseAll();
|
||||
},
|
||||
getTableRef: () => {
|
||||
return getTableRef();
|
||||
}
|
||||
};
|
||||
|
||||
return [register, methods];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Ref } from 'vue';
|
||||
import type { BasicTableProps, TableActionType } from '../types/table';
|
||||
import { provide, inject, ComputedRef } from 'vue';
|
||||
|
||||
const key = Symbol('basic-table');
|
||||
|
||||
type Instance = TableActionType & {
|
||||
wrapRef: Ref<Nullable<HTMLElement>>;
|
||||
getBindValues: ComputedRef<Recordable>;
|
||||
};
|
||||
|
||||
type RetInstance = Omit<Instance, 'getBindValues'> & {
|
||||
getBindValues: ComputedRef<BasicTableProps>;
|
||||
};
|
||||
|
||||
export function createTableContext(instance: Instance) {
|
||||
provide(key, instance);
|
||||
}
|
||||
|
||||
export function useTableContext(): RetInstance {
|
||||
return inject(key) as RetInstance;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { computed, unref, ref, toRaw } from 'vue';
|
||||
import { ROW_KEY } from '../const';
|
||||
|
||||
export function useTableExpand(propsRef: ComputedRef<BasicTableProps>, tableData: Ref<Recordable[]>, emit: EmitType) {
|
||||
const expandedRowKeys = ref<string[]>([]);
|
||||
|
||||
const getAutoCreateKey = computed(() => {
|
||||
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
|
||||
});
|
||||
|
||||
const getRowKey = computed(() => {
|
||||
const { rowKey } = unref(propsRef);
|
||||
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
|
||||
});
|
||||
|
||||
const getExpandOption = computed(() => {
|
||||
const { isTreeTable } = unref(propsRef);
|
||||
if (!isTreeTable) return {};
|
||||
|
||||
return {
|
||||
expandedRowKeys: unref(expandedRowKeys),
|
||||
onExpandedRowsChange: (keys: string[]) => {
|
||||
expandedRowKeys.value = keys;
|
||||
emit('expanded-rows-change', keys);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function expandAll() {
|
||||
const keys = getAllKeys();
|
||||
expandedRowKeys.value = keys;
|
||||
}
|
||||
|
||||
function getAllKeys(data?: Recordable[]) {
|
||||
const keys: string[] = [];
|
||||
const { childrenColumnName } = unref(propsRef);
|
||||
toRaw(data || unref(tableData)).forEach((item) => {
|
||||
keys.push(item[unref(getRowKey) as string]);
|
||||
const children = item[childrenColumnName || 'children'];
|
||||
if (children?.length) {
|
||||
keys.push(...getAllKeys(children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
expandedRowKeys.value = [];
|
||||
}
|
||||
|
||||
return { getExpandOption, expandAll, collapseAll };
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { ComputedRef, Ref, Slots } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { unref, computed, h, nextTick, watchEffect } from 'vue';
|
||||
import TableFooter from '../components/TableFooter.vue';
|
||||
import { useEventListener } from '/@/hooks/event/useEventListener';
|
||||
|
||||
export function useTableFooter(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
slots: Slots,
|
||||
scrollRef: ComputedRef<{
|
||||
x: string | number | true;
|
||||
y: Nullable<number>;
|
||||
scrollToFirstRowOnChange: boolean;
|
||||
}>,
|
||||
tableElRef: Ref<ComponentRef>,
|
||||
getDataSourceRef: ComputedRef<Recordable>
|
||||
) {
|
||||
const getIsEmptyData = computed(() => {
|
||||
return (unref(getDataSourceRef) || []).length === 0;
|
||||
});
|
||||
|
||||
// 是否有展开行
|
||||
const hasExpandedRow = computed(() => Object.keys(slots).includes('expandedRowRender'))
|
||||
|
||||
const getFooterProps = computed((): Recordable | undefined => {
|
||||
const { summaryFunc, showSummary, summaryData, bordered } = unref(propsRef);
|
||||
return showSummary && !unref(getIsEmptyData) ? () => h(TableFooter, {
|
||||
bordered,
|
||||
summaryFunc,
|
||||
summaryData,
|
||||
scroll: unref(scrollRef),
|
||||
hasExpandedRow: hasExpandedRow.value
|
||||
}) : undefined;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
handleSummary();
|
||||
});
|
||||
|
||||
function handleSummary() {
|
||||
const { showSummary } = unref(propsRef);
|
||||
if (!showSummary || unref(getIsEmptyData)) return;
|
||||
|
||||
nextTick(() => {
|
||||
const tableEl = unref(tableElRef);
|
||||
if (!tableEl) return;
|
||||
const bodyDom = tableEl.$el.querySelector('.ant-table-content');
|
||||
useEventListener({
|
||||
el: bodyDom,
|
||||
name: 'scroll',
|
||||
listener: () => {
|
||||
const footerBodyDom = tableEl.$el.querySelector('.ant-table-footer .ant-table-content') as HTMLDivElement;
|
||||
if (!footerBodyDom || !bodyDom) return;
|
||||
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
|
||||
},
|
||||
wait: 0,
|
||||
options: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
return { getFooterProps };
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { ComputedRef, Slots } from 'vue';
|
||||
import type { BasicTableProps, FetchParams } from '../types/table';
|
||||
import { unref, computed } from 'vue';
|
||||
import type { FormProps } from '/@/components/Form';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
export function useTableForm(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
slots: Slots,
|
||||
fetch: (opt?: FetchParams | undefined) => Promise<void>,
|
||||
getLoading: ComputedRef<boolean | undefined>
|
||||
) {
|
||||
const getFormProps = computed((): Partial<FormProps> => {
|
||||
const { formConfig } = unref(propsRef);
|
||||
const { submitButtonOptions, autoSubmitOnEnter} = formConfig || {};
|
||||
return {
|
||||
showAdvancedButton: true,
|
||||
...formConfig,
|
||||
submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
|
||||
compact: true,
|
||||
//update-begin-author:liusq---date:20230605--for: [issues/568]设置 autoSubmitOnEnter: false 不生效 ---
|
||||
autoSubmitOnEnter: autoSubmitOnEnter,
|
||||
//update-end-author:liusq---date:20230605--for: [issues/568]设置 autoSubmitOnEnter: false 不生效 ---
|
||||
};
|
||||
});
|
||||
|
||||
const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
|
||||
const keys = Object.keys(slots);
|
||||
return keys.map((item) => (item.startsWith('form-') ? item : null)).filter((item) => !!item) as string[];
|
||||
});
|
||||
|
||||
function replaceFormSlotKey(key: string) {
|
||||
if (!key) return '';
|
||||
return key?.replace?.(/form\-/, '') ?? '';
|
||||
}
|
||||
|
||||
function handleSearchInfoChange(info: Recordable) {
|
||||
const { handleSearchInfoFn } = unref(propsRef);
|
||||
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
|
||||
info = handleSearchInfoFn(info) || info;
|
||||
}
|
||||
fetch({ searchInfo: info, page: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
getFormProps,
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
handleSearchInfoChange,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { ComputedRef, Slots } from 'vue';
|
||||
import type { BasicTableProps, InnerHandlers } from '../types/table';
|
||||
import { unref, computed, h } from 'vue';
|
||||
import TableHeader from '../components/TableHeader.vue';
|
||||
import { isString } from '/@/utils/is';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
|
||||
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots, handlers: InnerHandlers) {
|
||||
const getHeaderProps = computed((): Recordable => {
|
||||
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
|
||||
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
|
||||
if (hideTitle && !isString(title)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
title: hideTitle
|
||||
? null
|
||||
: () =>
|
||||
h(
|
||||
TableHeader,
|
||||
{
|
||||
title,
|
||||
titleHelpMessage,
|
||||
showTableSetting,
|
||||
tableSetting,
|
||||
onColumnsChange: handlers.onColumnsChange,
|
||||
} as Recordable,
|
||||
{
|
||||
...(slots.toolbar
|
||||
? {
|
||||
toolbar: () => getSlot(slots, 'toolbar'),
|
||||
}
|
||||
: {}),
|
||||
...(slots.tableTitle
|
||||
? {
|
||||
tableTitle: () => getSlot(slots, 'tableTitle'),
|
||||
}
|
||||
: {}),
|
||||
...(slots.headerTop
|
||||
? {
|
||||
headerTop: () => getSlot(slots, 'headerTop'),
|
||||
}
|
||||
: {}),
|
||||
//添加tableTop插槽
|
||||
...(slots.tableTop
|
||||
? {
|
||||
tableTop: () => getSlot(slots, 'tableTop'),
|
||||
}
|
||||
: {}),
|
||||
// 添加alertAfter插槽
|
||||
...(slots.alertAfter ? { alertAfter: () => getSlot(slots, 'alertAfter') } : {}),
|
||||
}
|
||||
),
|
||||
};
|
||||
});
|
||||
return { getHeaderProps };
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import type { BasicTableProps, TableRowSelection, BasicColumn } from '../types/table';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import { computed, unref, ref, nextTick, watch } from 'vue';
|
||||
import { getViewportOffset } from '/@/utils/domUtils';
|
||||
import { isBoolean } from '/@/utils/is';
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
import { useModalContext } from '/@/components/Modal';
|
||||
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import componentSetting from '/@/settings/componentSetting';
|
||||
|
||||
export function useTableScroll(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
tableElRef: Ref<ComponentRef>,
|
||||
columnsRef: ComputedRef<BasicColumn[]>,
|
||||
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>,
|
||||
getDataSourceRef: ComputedRef<Recordable[]>
|
||||
) {
|
||||
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
||||
|
||||
const modalFn = useModalContext();
|
||||
|
||||
// Greater than animation time 280
|
||||
const debounceRedoHeight = useDebounceFn(redoHeight, 100);
|
||||
|
||||
const getCanResize = computed(() => {
|
||||
const { canResize, scroll } = unref(propsRef);
|
||||
return canResize && !(scroll || {}).y;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [unref(getCanResize), unref(getDataSourceRef)?.length],
|
||||
() => {
|
||||
debounceRedoHeight();
|
||||
},
|
||||
{
|
||||
flush: 'post',
|
||||
}
|
||||
);
|
||||
|
||||
function redoHeight() {
|
||||
nextTick(() => {
|
||||
calcTableHeight();
|
||||
});
|
||||
}
|
||||
|
||||
function setHeight(heigh: number) {
|
||||
tableHeightRef.value = heigh;
|
||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||
modalFn?.redoModalHeight?.();
|
||||
}
|
||||
|
||||
// No need to repeat queries
|
||||
let paginationEl: HTMLElement | null;
|
||||
let footerEl: HTMLElement | null;
|
||||
let bodyEl: HTMLElement | null;
|
||||
|
||||
async function calcTableHeight() {
|
||||
const { resizeHeightOffset, pagination, maxHeight, minHeight } = unref(propsRef);
|
||||
const tableData = unref(getDataSourceRef);
|
||||
|
||||
const table = unref(tableElRef);
|
||||
if (!table) return;
|
||||
|
||||
const tableEl: Element = table.$el;
|
||||
if (!tableEl) return;
|
||||
|
||||
if (!bodyEl) {
|
||||
//update-begin-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
|
||||
bodyEl = tableEl.querySelector('.ant-table-tbody');
|
||||
//update-end-author:taoyan date:2023-2-11 for: issues/355 前端-jeecgboot-vue3 3.4.4版本,BasicTable高度自适应功能失效,设置BasicTable组件maxHeight失效; 原因已找到,请看详情
|
||||
if (!bodyEl) return;
|
||||
}
|
||||
|
||||
const hasScrollBarY = bodyEl.scrollHeight > bodyEl.clientHeight;
|
||||
const hasScrollBarX = bodyEl.scrollWidth > bodyEl.clientWidth;
|
||||
|
||||
if (hasScrollBarY) {
|
||||
tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.remove('hide-scrollbar-y');
|
||||
} else {
|
||||
!tableEl.classList.contains('hide-scrollbar-y') && tableEl.classList.add('hide-scrollbar-y');
|
||||
}
|
||||
|
||||
if (hasScrollBarX) {
|
||||
tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.remove('hide-scrollbar-x');
|
||||
} else {
|
||||
!tableEl.classList.contains('hide-scrollbar-x') && tableEl.classList.add('hide-scrollbar-x');
|
||||
}
|
||||
|
||||
bodyEl!.style.height = 'unset';
|
||||
|
||||
if (!unref(getCanResize) || ( !tableData || tableData.length === 0)) return;
|
||||
|
||||
await nextTick();
|
||||
//Add a delay to get the correct bottomIncludeBody paginationHeight footerHeight headerHeight
|
||||
|
||||
const headEl = tableEl.querySelector('.ant-table-thead');
|
||||
|
||||
if (!headEl) return;
|
||||
|
||||
// Table height from bottom
|
||||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||
// Table height from bottom height-custom offset
|
||||
|
||||
const paddingHeight = 32;
|
||||
// Pager height
|
||||
let paginationHeight = 2;
|
||||
if (!isBoolean(pagination)) {
|
||||
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
|
||||
if (paginationEl) {
|
||||
const offsetHeight = paginationEl.offsetHeight;
|
||||
paginationHeight += offsetHeight || 0;
|
||||
} else {
|
||||
// TODO First fix 24
|
||||
paginationHeight += 24;
|
||||
}
|
||||
} else {
|
||||
paginationHeight = -8;
|
||||
}
|
||||
|
||||
let footerHeight = 0;
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1137】BasicTable自适应高度计算没有减去尾部高度
|
||||
footerEl = tableEl.querySelector('.ant-table-footer');
|
||||
if (footerEl) {
|
||||
const offsetHeight = footerEl.offsetHeight;
|
||||
footerHeight = offsetHeight || 0;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1137】BasicTable自适应高度计算没有减去尾部高度
|
||||
|
||||
let headerHeight = 0;
|
||||
if (headEl) {
|
||||
headerHeight = (headEl as HTMLElement).offsetHeight;
|
||||
}
|
||||
|
||||
let height = bottomIncludeBody - (resizeHeightOffset || 0) - paddingHeight - paginationHeight - footerHeight - headerHeight;
|
||||
// update-begin--author:liaozhiyang---date:20240603---for【TV360X-861】列表查询区域不可往上滚动
|
||||
// 10+6(外层边距padding:10 + 内层padding-bottom:6)
|
||||
height -= 16;
|
||||
// update-end--author:liaozhiyang---date:20240603---for:【TV360X-861】列表查询区域不可往上滚动
|
||||
|
||||
height = (height < minHeight! ? (minHeight as number) : height) ?? height;
|
||||
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||||
setHeight(height);
|
||||
|
||||
bodyEl!.style.height = `${height}px`;
|
||||
}
|
||||
useWindowSizeFn(calcTableHeight, 280);
|
||||
onMountedOrActivated(() => {
|
||||
calcTableHeight();
|
||||
nextTick(() => {
|
||||
debounceRedoHeight();
|
||||
});
|
||||
});
|
||||
|
||||
const getScrollX = computed(() => {
|
||||
let width = 0;
|
||||
// update-begin--author:liaozhiyang---date:20230922---for:【QQYUN-6391】在线表单列表字段过多时,列头和数据对不齐
|
||||
// if (unref(rowSelectionRef)) {
|
||||
// width += 60;
|
||||
// }
|
||||
// update-end--author:liaozhiyang---date:20230922---for:【QQYUN-6391】在线表单列表字段过多时,列头和数据对不齐
|
||||
// update-begin--author:liaozhiyang---date:20230925---for:【issues/5411】BasicTable 配置maxColumnWidth 未生效
|
||||
const { maxColumnWidth } = unref(propsRef);
|
||||
// TODO props ?? 0;
|
||||
const NORMAL_WIDTH = maxColumnWidth ?? 150;
|
||||
// update-end--author:liaozhiyang---date:20230925---for:【issues/5411】BasicTable 配置maxColumnWidth 未生效
|
||||
|
||||
const columns = unref(columnsRef).filter((item) => !item.defaultHidden);
|
||||
columns.forEach((item) => {
|
||||
width += Number.parseInt(item.width as string) || 0;
|
||||
});
|
||||
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
|
||||
|
||||
const len = unsetWidthColumns.length;
|
||||
if (len !== 0) {
|
||||
width += len * NORMAL_WIDTH;
|
||||
}
|
||||
|
||||
const table = unref(tableElRef);
|
||||
const tableWidth = table?.$el?.offsetWidth ?? 0;
|
||||
return tableWidth > width ? '100%' : width;
|
||||
});
|
||||
|
||||
const getScrollRef = computed(() => {
|
||||
const tableHeight = unref(tableHeightRef);
|
||||
const { canResize, scroll } = unref(propsRef);
|
||||
const { table } = componentSetting;
|
||||
return {
|
||||
x: unref(getScrollX),
|
||||
y: canResize ? tableHeight : null,
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
scrollToFirstRowOnChange: table.scrollToFirstRowOnChange,
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
...scroll,
|
||||
};
|
||||
});
|
||||
|
||||
return { getScrollRef, redoHeight };
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { BasicTableProps, TableCustomRecord } from '../types/table';
|
||||
import { unref } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {
|
||||
function getRowClassName(record: TableCustomRecord, index: number) {
|
||||
const { striped, rowClassName } = unref(propsRef);
|
||||
const classNames: string[] = [];
|
||||
if (striped) {
|
||||
classNames.push((index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '');
|
||||
}
|
||||
if (rowClassName && isFunction(rowClassName)) {
|
||||
classNames.push(rowClassName(record, index));
|
||||
}
|
||||
return classNames.filter((cls) => !!cls).join(' ');
|
||||
}
|
||||
|
||||
return { getRowClassName };
|
||||
}
|
||||
147
hx-ai-intelligent/src/components/Table/src/props.ts
Normal file
147
hx-ai-intelligent/src/components/Table/src/props.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import type { PropType } from 'vue';
|
||||
import type { PaginationProps } from './types/pagination';
|
||||
import type { BasicColumn, FetchSetting, TableSetting, SorterResult, TableCustomRecord, TableRowSelection, SizeType } from './types/table';
|
||||
import type { FormProps } from '/@/components/Form';
|
||||
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export const basicProps = {
|
||||
clickToRowSelect: propTypes.bool.def(true),
|
||||
isTreeTable: propTypes.bool.def(false),
|
||||
tableSetting: propTypes.shape<TableSetting>({}),
|
||||
inset: propTypes.bool,
|
||||
sortFn: {
|
||||
type: Function as PropType<(sortInfo: SorterResult) => any>,
|
||||
default: DEFAULT_SORT_FN,
|
||||
},
|
||||
filterFn: {
|
||||
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
|
||||
default: DEFAULT_FILTER_FN,
|
||||
},
|
||||
showTableSetting: propTypes.bool,
|
||||
autoCreateKey: propTypes.bool.def(true),
|
||||
striped: propTypes.bool.def(false),
|
||||
showSummary: propTypes.bool,
|
||||
summaryFunc: {
|
||||
type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
|
||||
default: null,
|
||||
},
|
||||
summaryData: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
default: null,
|
||||
},
|
||||
indentSize: propTypes.number.def(24),
|
||||
canColDrag: propTypes.bool.def(true),
|
||||
api: {
|
||||
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
||||
default: null,
|
||||
},
|
||||
beforeFetch: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: null,
|
||||
},
|
||||
afterFetch: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: null,
|
||||
},
|
||||
handleSearchInfoFn: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: null,
|
||||
},
|
||||
fetchSetting: {
|
||||
type: Object as PropType<FetchSetting>,
|
||||
default: () => {
|
||||
return FETCH_SETTING;
|
||||
},
|
||||
},
|
||||
// 立即请求接口
|
||||
immediate: propTypes.bool.def(true),
|
||||
emptyDataIsShowTable: propTypes.bool.def(true),
|
||||
// 额外的请求参数
|
||||
searchInfo: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: null,
|
||||
},
|
||||
// 默认的排序参数
|
||||
defSort: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: null,
|
||||
},
|
||||
// 使用搜索表单
|
||||
useSearchForm: propTypes.bool,
|
||||
// 表单配置
|
||||
formConfig: {
|
||||
type: Object as PropType<Partial<FormProps>>,
|
||||
default: null,
|
||||
},
|
||||
columns: {
|
||||
type: [Array] as PropType<BasicColumn[]>,
|
||||
default: () => [],
|
||||
},
|
||||
showIndexColumn: propTypes.bool.def(true),
|
||||
indexColumnProps: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
showActionColumn: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
actionColumn: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
ellipsis: propTypes.bool.def(true),
|
||||
canResize: propTypes.bool.def(true),
|
||||
clearSelectOnPageChange: propTypes.bool,
|
||||
resizeHeightOffset: propTypes.number.def(0),
|
||||
rowSelection: {
|
||||
type: Object as PropType<TableRowSelection | null>,
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
|
||||
default: null,
|
||||
},
|
||||
titleHelpMessage: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
},
|
||||
minHeight: propTypes.number,
|
||||
maxHeight: propTypes.number,
|
||||
// 统一设置列最大宽度
|
||||
maxColumnWidth: propTypes.number,
|
||||
dataSource: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
default: null,
|
||||
},
|
||||
rowKey: {
|
||||
type: [String, Function] as PropType<string | ((record: Recordable) => string)>,
|
||||
default: '',
|
||||
},
|
||||
bordered: propTypes.bool,
|
||||
pagination: {
|
||||
type: [Object, Boolean] as PropType<PaginationProps | boolean>,
|
||||
default: null,
|
||||
},
|
||||
loading: propTypes.bool,
|
||||
rowClassName: {
|
||||
type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
|
||||
},
|
||||
scroll: {
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
type: Object as PropType<{ x?: number | true; y?: number; scrollToFirstRowOnChange?: boolean }>,
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
default: null,
|
||||
},
|
||||
beforeEditSubmit: {
|
||||
type: Function as PropType<(data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>>,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<SizeType>,
|
||||
default: DEFAULT_SIZE,
|
||||
},
|
||||
expandedRowKeys: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
198
hx-ai-intelligent/src/components/Table/src/types/column.ts
Normal file
198
hx-ai-intelligent/src/components/Table/src/types/column.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { VNodeChild } from 'vue';
|
||||
|
||||
export interface ColumnFilterItem {
|
||||
text?: string;
|
||||
value?: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export declare type SortOrder = 'ascend' | 'descend';
|
||||
|
||||
export interface RecordProps<T> {
|
||||
text: any;
|
||||
record: T;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface FilterDropdownProps {
|
||||
prefixCls?: string;
|
||||
setSelectedKeys?: (selectedKeys: string[]) => void;
|
||||
selectedKeys?: string[];
|
||||
confirm?: () => void;
|
||||
clearFilters?: () => void;
|
||||
filters?: ColumnFilterItem[];
|
||||
getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export declare type CustomRenderFunction<T> = (record: RecordProps<T>) => VNodeChild | JSX.Element;
|
||||
|
||||
export interface ColumnProps<T> {
|
||||
/**
|
||||
* specify how content is aligned
|
||||
* @default 'left'
|
||||
* @type string
|
||||
*/
|
||||
align?: 'left' | 'right' | 'center';
|
||||
|
||||
/**
|
||||
* ellipsize cell content, not working with sorter and filters for now.
|
||||
* tableLayout would be fixed when ellipsis is true.
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
ellipsis?: boolean;
|
||||
|
||||
/**
|
||||
* Span of this column's title
|
||||
* @type number
|
||||
*/
|
||||
colSpan?: number;
|
||||
|
||||
/**
|
||||
* Display field of the data record, could be set like a.b.c
|
||||
* @type string
|
||||
*/
|
||||
dataIndex?: string;
|
||||
|
||||
/**
|
||||
* Default filtered values
|
||||
* @type string[]
|
||||
*/
|
||||
defaultFilteredValue?: string[];
|
||||
|
||||
/**
|
||||
* Default order of sorted values: 'ascend' 'descend' null
|
||||
* @type string
|
||||
*/
|
||||
defaultSortOrder?: SortOrder;
|
||||
|
||||
/**
|
||||
* Customized filter overlay
|
||||
* @type any (slot)
|
||||
*/
|
||||
filterDropdown?: VNodeChild | JSX.Element | ((props: FilterDropdownProps) => VNodeChild | JSX.Element);
|
||||
|
||||
/**
|
||||
* Whether filterDropdown is visible
|
||||
* @type boolean
|
||||
*/
|
||||
filterDropdownOpen?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the dataSource is filtered
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
filtered?: boolean;
|
||||
|
||||
/**
|
||||
* Controlled filtered value, filter icon will highlight
|
||||
* @type string[]
|
||||
*/
|
||||
filteredValue?: string[];
|
||||
|
||||
/**
|
||||
* Customized filter icon
|
||||
* @default false
|
||||
* @type any
|
||||
*/
|
||||
filterIcon?: boolean | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Whether multiple filters can be selected
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
filterMultiple?: boolean;
|
||||
|
||||
/**
|
||||
* Filter menu config
|
||||
* @type object[]
|
||||
*/
|
||||
filters?: ColumnFilterItem[];
|
||||
|
||||
/**
|
||||
* Set column to be fixed: true(same as left) 'left' 'right'
|
||||
* @default false
|
||||
* @type boolean | string
|
||||
*/
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
|
||||
/**
|
||||
* Unique key of this column, you can ignore this prop if you've set a unique dataIndex
|
||||
* @type string
|
||||
*/
|
||||
key?: string;
|
||||
|
||||
/**
|
||||
* Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config
|
||||
* @type Function | ScopedSlot
|
||||
*/
|
||||
customRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Sort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to true
|
||||
* @type boolean | Function
|
||||
*/
|
||||
sorter?: boolean | Function;
|
||||
|
||||
/**
|
||||
* Order of sorted values: 'ascend' 'descend' false
|
||||
* @type boolean | string
|
||||
*/
|
||||
sortOrder?: boolean | SortOrder;
|
||||
|
||||
/**
|
||||
* supported sort way, could be 'ascend', 'descend'
|
||||
* @default ['ascend', 'descend']
|
||||
* @type string[]
|
||||
*/
|
||||
sortDirections?: SortOrder[];
|
||||
|
||||
/**
|
||||
* Title of this column
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Width of this column
|
||||
* @type string | number
|
||||
*/
|
||||
width?: string | number;
|
||||
|
||||
/**
|
||||
* Set props on per cell
|
||||
* @type Function
|
||||
*/
|
||||
customCell?: (record: T, rowIndex: number) => object;
|
||||
|
||||
/**
|
||||
* Set props on per header cell
|
||||
* @type object
|
||||
*/
|
||||
customHeaderCell?: (column: ColumnProps<T>) => object;
|
||||
// update-begin--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
customSummaryRender?: CustomRenderFunction<T> | VNodeChild | JSX.Element;
|
||||
// update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
|
||||
/**
|
||||
* Callback executed when the confirm filter button is clicked, Use as a filter event when using template or jsx
|
||||
* @type Function
|
||||
*/
|
||||
onFilter?: (value: any, record: T) => boolean;
|
||||
|
||||
/**
|
||||
* Callback executed when filterDropdownOpen is changed, Use as a filterDropdownVisible event when using template or jsx
|
||||
* @type Function
|
||||
*/
|
||||
onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
||||
|
||||
/**
|
||||
* When using columns, you can setting this property to configure the properties that support the slot,
|
||||
* such as slots: { filterIcon: 'XXX'}
|
||||
* @type object
|
||||
*/
|
||||
slots?: Recordable<string>;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type ComponentType = 'Input' | 'InputNumber' | 'Select' | 'ApiSelect' | 'ApiTreeSelect' | 'Checkbox' | 'Switch' | 'DatePicker' | 'TimePicker';
|
||||
@@ -0,0 +1,99 @@
|
||||
import Pagination from 'ant-design-vue/lib/pagination';
|
||||
import { VNodeChild } from 'vue';
|
||||
|
||||
interface PaginationRenderProps {
|
||||
page: number;
|
||||
type: 'page' | 'prev' | 'next';
|
||||
originalElement: any;
|
||||
}
|
||||
|
||||
export declare class PaginationConfig extends Pagination {
|
||||
position?: 'top' | 'bottom' | 'both';
|
||||
}
|
||||
export interface PaginationProps {
|
||||
/**
|
||||
* total number of data items
|
||||
* @default 0
|
||||
* @type number
|
||||
*/
|
||||
total?: number;
|
||||
|
||||
/**
|
||||
* default initial page number
|
||||
* @default 1
|
||||
* @type number
|
||||
*/
|
||||
defaultCurrent?: number;
|
||||
|
||||
/**
|
||||
* current page number
|
||||
* @type number
|
||||
*/
|
||||
current?: number;
|
||||
|
||||
/**
|
||||
* default number of data items per page
|
||||
* @default 10
|
||||
* @type number
|
||||
*/
|
||||
defaultPageSize?: number;
|
||||
|
||||
/**
|
||||
* number of data items per page
|
||||
* @type number
|
||||
*/
|
||||
pageSize?: number;
|
||||
|
||||
/**
|
||||
* Whether to hide pager on single page
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
hideOnSinglePage?: boolean;
|
||||
|
||||
/**
|
||||
* determine whether pageSize can be changed
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
showSizeChanger?: boolean;
|
||||
|
||||
/**
|
||||
* specify the sizeChanger options
|
||||
* @default ['10', '20', '30', '40']
|
||||
* @type string[]
|
||||
*/
|
||||
pageSizeOptions?: string[];
|
||||
|
||||
/**
|
||||
* determine whether you can jump to pages directly
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
showQuickJumper?: boolean | object;
|
||||
|
||||
/**
|
||||
* to display the total number and range
|
||||
* @type Function
|
||||
*/
|
||||
showTotal?: (total: number, range: [number, number]) => any;
|
||||
|
||||
/**
|
||||
* specify the size of Pagination, can be set to small
|
||||
* @default ''
|
||||
* @type string
|
||||
*/
|
||||
size?: string;
|
||||
|
||||
/**
|
||||
* whether to setting simple mode
|
||||
* @type boolean
|
||||
*/
|
||||
simple?: boolean;
|
||||
|
||||
/**
|
||||
* to customize item innerHTML
|
||||
* @type Function
|
||||
*/
|
||||
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
|
||||
}
|
||||
478
hx-ai-intelligent/src/components/Table/src/types/table.ts
Normal file
478
hx-ai-intelligent/src/components/Table/src/types/table.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
import type { VNodeChild } from 'vue';
|
||||
import type { PaginationProps } from './pagination';
|
||||
import type { FormProps } from '/@/components/Form';
|
||||
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
|
||||
import type { ColumnProps } from 'ant-design-vue/lib/table';
|
||||
|
||||
import { ComponentType } from './componentType';
|
||||
import { VueNode } from '/@/utils/propTypes';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
|
||||
export declare type SortOrder = 'ascend' | 'descend';
|
||||
|
||||
export interface TableCurrentDataSource<T = Recordable> {
|
||||
currentDataSource: T[];
|
||||
}
|
||||
|
||||
export interface TableRowSelection<T = any> extends ITableRowSelection {
|
||||
/**
|
||||
* Callback executed when selected rows change
|
||||
* @type Function
|
||||
*/
|
||||
onChange?: (selectedRowKeys: string[] | number[], selectedRows: T[]) => any;
|
||||
|
||||
/**
|
||||
* Callback executed when select/deselect one row
|
||||
* @type Function
|
||||
*/
|
||||
onSelect?: (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any;
|
||||
|
||||
/**
|
||||
* Callback executed when select/deselect all rows
|
||||
* @type Function
|
||||
*/
|
||||
onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => any;
|
||||
|
||||
/**
|
||||
* Callback executed when row selection is inverted
|
||||
* @type Function
|
||||
*/
|
||||
onSelectInvert?: (selectedRows: string[] | number[]) => any;
|
||||
}
|
||||
|
||||
export interface TableCustomRecord<T> {
|
||||
record?: T;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> {
|
||||
indent?: number;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export interface ColumnFilterItem {
|
||||
text?: string;
|
||||
value?: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export interface TableCustomRecord<T = Recordable> {
|
||||
record?: T;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface SorterResult {
|
||||
column: ColumnProps;
|
||||
order: SortOrder;
|
||||
field: string;
|
||||
columnKey: string;
|
||||
}
|
||||
|
||||
export interface FetchParams {
|
||||
searchInfo?: Recordable;
|
||||
page?: number;
|
||||
sortInfo?: Recordable;
|
||||
filterInfo?: Recordable;
|
||||
}
|
||||
|
||||
export interface GetColumnsParams {
|
||||
ignoreIndex?: boolean;
|
||||
ignoreAction?: boolean;
|
||||
sort?: boolean;
|
||||
}
|
||||
|
||||
export type SizeType = 'middle' | 'small' | 'large';
|
||||
|
||||
export interface TableActionType {
|
||||
reload: (opt?: FetchParams) => Promise<void>;
|
||||
getSelectRows: <T = Recordable>() => T[];
|
||||
clearSelectedRowKeys: () => void;
|
||||
expandAll: () => void;
|
||||
collapseAll: () => void;
|
||||
getSelectRowKeys: () => string[];
|
||||
deleteSelectRowByKey: (key: string) => void;
|
||||
setPagination: (info: Partial<PaginationProps>) => void;
|
||||
setTableData: <T = Recordable>(values: T[]) => void;
|
||||
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
|
||||
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
|
||||
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
|
||||
findTableDataRecord: (rowKey: string | number) => Recordable | void;
|
||||
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
|
||||
setColumns: (columns: BasicColumn[] | string[]) => void;
|
||||
getDataSource: <T = Recordable>() => T[];
|
||||
getRawDataSource: <T = Recordable>() => T;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setProps: (props: Partial<BasicTableProps>) => void;
|
||||
redoHeight: () => void;
|
||||
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
|
||||
getPaginationRef: () => PaginationProps | boolean;
|
||||
getSize: () => SizeType;
|
||||
getRowSelection: () => TableRowSelection<Recordable>;
|
||||
getCacheColumns: () => BasicColumn[];
|
||||
emit?: EmitType;
|
||||
updateTableData: (index: number, key: string, value: any) => Recordable;
|
||||
setShowPagination: (show: boolean) => Promise<void>;
|
||||
getShowPagination: () => boolean;
|
||||
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
|
||||
}
|
||||
|
||||
export interface FetchSetting {
|
||||
// 请求接口当前页数
|
||||
pageField: string;
|
||||
// 每页显示多少条
|
||||
sizeField: string;
|
||||
// 请求结果列表字段 支持 a.b.c
|
||||
listField: string;
|
||||
// 请求结果总数字段 支持 a.b.c
|
||||
totalField: string;
|
||||
}
|
||||
|
||||
export interface TableSetting {
|
||||
// 是否显示刷新按钮
|
||||
redo?: boolean;
|
||||
// 是否显示尺寸调整按钮
|
||||
size?: boolean;
|
||||
// 是否显示字段调整按钮
|
||||
setting?: boolean;
|
||||
// 缓存“字段调整”配置的key,用于页面上有多个表格需要区分的情况
|
||||
cacheKey?: string;
|
||||
// 是否显示全屏按钮
|
||||
fullScreen?: boolean;
|
||||
}
|
||||
|
||||
export interface BasicTableProps<T = any> {
|
||||
// 点击行选中
|
||||
clickToRowSelect?: boolean;
|
||||
isTreeTable?: boolean;
|
||||
// 自定义排序方法
|
||||
sortFn?: (sortInfo: SorterResult) => any;
|
||||
// 排序方法
|
||||
filterFn?: (data: Partial<Recordable<string[]>>) => any;
|
||||
// 取消表格的默认padding
|
||||
inset?: boolean;
|
||||
// 显示表格设置
|
||||
showTableSetting?: boolean;
|
||||
// 表格上方操作按钮设置
|
||||
tableSetting?: TableSetting;
|
||||
// 斑马纹
|
||||
striped?: boolean;
|
||||
// 是否自动生成key
|
||||
autoCreateKey?: boolean;
|
||||
// 计算合计行的方法
|
||||
summaryFunc?: (...arg: any) => Recordable[];
|
||||
// 自定义合计表格内容
|
||||
summaryData?: Recordable[];
|
||||
// 是否显示合计行
|
||||
showSummary?: boolean;
|
||||
// 是否可拖拽列
|
||||
canColDrag?: boolean;
|
||||
// 接口请求对象
|
||||
api?: (...arg: any) => Promise<any>;
|
||||
// 请求之前处理参数
|
||||
beforeFetch?: Fn;
|
||||
// 自定义处理接口返回参数
|
||||
afterFetch?: Fn;
|
||||
// 查询条件请求之前处理
|
||||
handleSearchInfoFn?: Fn;
|
||||
// 请求接口配置
|
||||
fetchSetting?: Partial<FetchSetting>;
|
||||
// 立即请求接口
|
||||
immediate?: boolean;
|
||||
// 在开起搜索表单的时候,如果没有数据是否显示表格
|
||||
emptyDataIsShowTable?: boolean;
|
||||
// 额外的请求参数
|
||||
searchInfo?: Recordable;
|
||||
// 默认的排序参数
|
||||
defSort?: Recordable;
|
||||
// 使用搜索表单
|
||||
useSearchForm?: boolean;
|
||||
// 表单配置
|
||||
formConfig?: Partial<FormProps>;
|
||||
// 列配置
|
||||
columns: BasicColumn[];
|
||||
// 统一设置列最大宽度
|
||||
maxColumnWidth?: number;
|
||||
// 是否显示序号列
|
||||
showIndexColumn?: boolean;
|
||||
// 序号列配置
|
||||
indexColumnProps?: BasicColumn;
|
||||
// 是否显示操作列
|
||||
showActionColumn?: boolean;
|
||||
// 操作列配置
|
||||
actionColumn?: BasicColumn;
|
||||
// 文本超过宽度是否显示。。。
|
||||
ellipsis?: boolean;
|
||||
// 是否可以自适应高度
|
||||
canResize?: boolean;
|
||||
// 自适应高度偏移, 计算结果-偏移量
|
||||
resizeHeightOffset?: number;
|
||||
// 在分页改变的时候清空选项
|
||||
clearSelectOnPageChange?: boolean;
|
||||
//
|
||||
rowKey?: string | ((record: Recordable) => string);
|
||||
// 数据
|
||||
dataSource?: Recordable[];
|
||||
// 标题右侧提示
|
||||
titleHelpMessage?: string | string[];
|
||||
// 表格最小高度
|
||||
minHeight?: number;
|
||||
// 表格滚动最大高度
|
||||
maxHeight?: number;
|
||||
// 是否显示边框
|
||||
bordered?: boolean;
|
||||
// 分页配置
|
||||
pagination?: PaginationProps | boolean;
|
||||
// loading加载
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* The column contains children to display
|
||||
* @default 'children'
|
||||
* @type string | string[]
|
||||
*/
|
||||
childrenColumnName?: string;
|
||||
|
||||
/**
|
||||
* Override default table elements
|
||||
* @type object
|
||||
*/
|
||||
components?: object;
|
||||
|
||||
/**
|
||||
* Expand all rows initially
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
defaultExpandAllRows?: boolean;
|
||||
|
||||
/**
|
||||
* Initial expanded row keys
|
||||
* @type string[]
|
||||
*/
|
||||
defaultExpandedRowKeys?: string[];
|
||||
|
||||
/**
|
||||
* Current expanded row keys
|
||||
* @type string[]
|
||||
*/
|
||||
expandedRowKeys?: string[];
|
||||
|
||||
/**
|
||||
* Expanded container render for each row
|
||||
* @type Function
|
||||
*/
|
||||
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Customize row expand Icon.
|
||||
* @type Function | VNodeChild
|
||||
*/
|
||||
expandIcon?: Function | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Whether to expand row by clicking anywhere in the whole row
|
||||
* @default false
|
||||
* @type boolean
|
||||
*/
|
||||
expandRowByClick?: boolean;
|
||||
|
||||
/**
|
||||
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
|
||||
*/
|
||||
expandIconColumnIndex?: number;
|
||||
|
||||
/**
|
||||
* Table footer renderer
|
||||
* @type Function | VNodeChild
|
||||
*/
|
||||
footer?: Function | VNodeChild | JSX.Element;
|
||||
|
||||
/**
|
||||
* Indent size in pixels of tree data
|
||||
* @default 15
|
||||
* @type number
|
||||
*/
|
||||
indentSize?: number;
|
||||
|
||||
/**
|
||||
* i18n text including filter, sort, empty text, etc
|
||||
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
|
||||
* @type object
|
||||
*/
|
||||
locale?: object;
|
||||
|
||||
/**
|
||||
* Row's className
|
||||
* @type Function
|
||||
*/
|
||||
rowClassName?: (record: TableCustomRecord<T>, index: number) => string;
|
||||
|
||||
/**
|
||||
* Row selection config
|
||||
* @type object
|
||||
*/
|
||||
rowSelection?: TableRowSelection;
|
||||
|
||||
/**
|
||||
* Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
|
||||
* It is recommended to set a number for x, if you want to set it to true,
|
||||
* you need to add style .ant-table td { white-space: nowrap; }.
|
||||
* @type object
|
||||
*/
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
scroll?: { x?: number | true | 'max-content'; y?: number; scrollToFirstRowOnChange?: boolean };
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1188】BasicTable加上scrollToFirstRowOnChange类型定义
|
||||
|
||||
/**
|
||||
* Whether to show table header
|
||||
* @default true
|
||||
* @type boolean
|
||||
*/
|
||||
showHeader?: boolean;
|
||||
|
||||
/**
|
||||
* Size of table
|
||||
* @default 'default'
|
||||
* @type string
|
||||
*/
|
||||
size?: SizeType;
|
||||
|
||||
/**
|
||||
* Table title renderer
|
||||
* @type Function | ScopedSlot
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
|
||||
|
||||
/**
|
||||
* Set props on per header row
|
||||
* @type Function
|
||||
*/
|
||||
customHeaderRow?: (column: ColumnProps, index: number) => object;
|
||||
|
||||
/**
|
||||
* Set props on per row
|
||||
* @type Function
|
||||
*/
|
||||
customRow?: (record: T, index: number) => object;
|
||||
|
||||
/**
|
||||
* `table-layout` attribute of table element
|
||||
* `fixed` when header/columns are fixed, or using `column.ellipsis`
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
|
||||
* @version 1.5.0
|
||||
*/
|
||||
tableLayout?: 'auto' | 'fixed' | string;
|
||||
|
||||
/**
|
||||
* the render container of dropdowns in table
|
||||
* @param triggerNode
|
||||
* @version 1.5.0
|
||||
*/
|
||||
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
|
||||
|
||||
/**
|
||||
* Data can be changed again before rendering.
|
||||
* The default configuration of general user empty data.
|
||||
* You can configured globally through [ConfigProvider](https://antdv.com/components/config-provider-cn/)
|
||||
*
|
||||
* @version 1.5.4
|
||||
*/
|
||||
transformCellText?: Function;
|
||||
|
||||
/**
|
||||
* Callback executed before editable cell submit value, not for row-editor
|
||||
*
|
||||
* The cell will not submit data while callback return false
|
||||
*/
|
||||
beforeEditSubmit?: (data: { record: Recordable; index: number; key: string | number; value: any }) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Callback executed when pagination, filters or sorter is changed
|
||||
* @param pagination
|
||||
* @param filters
|
||||
* @param sorter
|
||||
* @param currentDataSource
|
||||
*/
|
||||
onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void;
|
||||
|
||||
/**
|
||||
* Callback executed when the row expand icon is clicked
|
||||
*
|
||||
* @param expanded
|
||||
* @param record
|
||||
*/
|
||||
onExpand?: (expande: boolean, record: T) => void;
|
||||
|
||||
/**
|
||||
* Callback executed when the expanded rows change
|
||||
* @param expandedRows
|
||||
*/
|
||||
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
|
||||
|
||||
onColumnsChange?: (data: ColumnChangeParam[]) => void;
|
||||
}
|
||||
|
||||
export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map<string | number, any>;
|
||||
|
||||
// @ts-ignore
|
||||
export interface BasicColumn extends ColumnProps<Recordable> {
|
||||
children?: BasicColumn[];
|
||||
filters?: {
|
||||
text: string;
|
||||
value: string;
|
||||
children?: unknown[] | (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
|
||||
}[];
|
||||
|
||||
//
|
||||
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
|
||||
customTitle?: VueNode;
|
||||
|
||||
slots?: Recordable;
|
||||
// slots的备份,兼容老的写法,转成新写法避免控制台警告
|
||||
slotsBak?: Recordable;
|
||||
|
||||
// Whether to hide the column by default, it can be displayed in the column configuration
|
||||
defaultHidden?: boolean;
|
||||
|
||||
// Help text for table column header
|
||||
helpMessage?: string | string[];
|
||||
|
||||
format?: CellFormat;
|
||||
|
||||
// Editable
|
||||
edit?: boolean;
|
||||
editRow?: boolean;
|
||||
editable?: boolean;
|
||||
editComponent?: ComponentType;
|
||||
editComponentProps?: Recordable;
|
||||
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
|
||||
editValueMap?: (value: any) => string;
|
||||
onEditRow?: () => void;
|
||||
// 权限编码控制是否显示
|
||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||
//compType-用于记录类型
|
||||
compType?: string;
|
||||
// update-begin--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
customSummaryRender?: (opt: {
|
||||
value: any;
|
||||
text: any;
|
||||
record: Recordable;
|
||||
index: number;
|
||||
renderIndex?: number;
|
||||
column: BasicColumn;
|
||||
}) => any | VNodeChild | JSX.Element;
|
||||
// update-end--author:liaozhiyang---date:20240425---for:【pull/1201】添加antd的TableSummary功能兼容老的summary(表尾合计)
|
||||
}
|
||||
|
||||
export type ColumnChangeParam = {
|
||||
dataIndex: string;
|
||||
fixed: boolean | 'left' | 'right' | undefined;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
export interface InnerHandlers {
|
||||
onColumnsChange: (data: ColumnChangeParam[]) => void;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
||||
import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
export interface ActionItem extends ButtonProps {
|
||||
onClick?: Fn;
|
||||
label?: string;
|
||||
color?: 'success' | 'error' | 'warning';
|
||||
icon?: string;
|
||||
popConfirm?: PopConfirm;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
// 权限编码控制是否显示
|
||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: boolean | ((action: ActionItem) => boolean);
|
||||
tooltip?: string | TooltipProps;
|
||||
// 自定义类名
|
||||
class?: string | Record<string, boolean> | any[];
|
||||
// 自定义图标颜色
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
export interface PopConfirm {
|
||||
title: string;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
confirm: Fn;
|
||||
cancel?: Fn;
|
||||
icon?: string;
|
||||
placement?: string;
|
||||
overlayClassName?: string;
|
||||
}
|
||||
104
hx-ai-intelligent/src/components/icon/IconPicker.vue
Normal file
104
hx-ai-intelligent/src/components/icon/IconPicker.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<a-modal :bodyStyle="{ padding: '24px'}" :visible="iconOpen" :width="800" @ok="iconHandleOk(iconChecked)" @cancel="closeOpenIcon" title="选择图标">
|
||||
<a-input
|
||||
v-model:value="searchValue"
|
||||
placeholder="请输入英文关键词进行搜索"
|
||||
@change="filterIcon"
|
||||
/>
|
||||
<div class="icon-box">
|
||||
<div
|
||||
v-for="(item,index) in iconArr"
|
||||
:key="index"
|
||||
@click="sendData(item)"
|
||||
class="icon-content"
|
||||
:style="{ background: iconChecked === item ? '#1890ff' : ''}"
|
||||
>
|
||||
<component :is="$icons[item]" />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch,defineProps} from 'vue';
|
||||
import { NsMessage } from '/nerv-lib/saas';
|
||||
import icons from './icons.json';
|
||||
const props = defineProps(['iconHandleOk']);
|
||||
const iconArr = ref<string[]>(icons);
|
||||
const searchValue = ref('');
|
||||
const iconOpen = ref<boolean>(false);
|
||||
const iconChecked =ref('');//选中数据
|
||||
/**
|
||||
* 进行搜索过滤
|
||||
*/
|
||||
const filterIcon = () => {
|
||||
if (searchValue.value){
|
||||
iconArr.value = icons.filter(item => item.toLowerCase().includes(searchValue.value.toLowerCase()) )
|
||||
}else{
|
||||
iconArr.value = icons;
|
||||
}
|
||||
}
|
||||
watch(iconOpen,()=>{
|
||||
searchValue.value = ''
|
||||
iconArr.value = icons;
|
||||
})
|
||||
const sendData = (data:any) => {
|
||||
iconChecked.value = data;
|
||||
}
|
||||
const iconHandleOk = (e: MouseEvent) => {
|
||||
if(!iconChecked.value){
|
||||
NsMessage.error('请选择图标');
|
||||
return;
|
||||
}else{
|
||||
props.iconHandleOk(iconChecked.value);//调用父组件传递来的函数
|
||||
iconOpen.value = false;
|
||||
iconChecked.value = '';
|
||||
}
|
||||
};
|
||||
// 关闭图标选择弹窗
|
||||
const closeOpenIcon = () => {
|
||||
iconOpen.value = false;
|
||||
iconChecked.value = '';
|
||||
};
|
||||
// 暴露给父组件
|
||||
const isIconOpenToggle = (data:any)=>{
|
||||
if(data){
|
||||
iconChecked.value = data;//记住上一次选择的数据
|
||||
}
|
||||
iconOpen.value = true;
|
||||
}
|
||||
const isIconData = ()=>{
|
||||
if(!iconChecked.value){
|
||||
NsMessage.warn('请先选择图标');
|
||||
iconOpen.value = true;
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
isIconOpenToggle,
|
||||
isIconData
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="less">
|
||||
.icon-box{
|
||||
overflow: auto;
|
||||
font-size: 20px;
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
padding-top:10px;
|
||||
}
|
||||
|
||||
.icon-content{
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
margin: 6px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon-content:hover{
|
||||
background: rgba(26, 144, 255,.3);
|
||||
}
|
||||
</style>
|
||||
790
hx-ai-intelligent/src/components/icon/icons.json
Normal file
790
hx-ai-intelligent/src/components/icon/icons.json
Normal file
@@ -0,0 +1,790 @@
|
||||
[
|
||||
"AccountBookFilled",
|
||||
"AccountBookOutlined",
|
||||
"AccountBookTwoTone",
|
||||
"AimOutlined",
|
||||
"AlertFilled",
|
||||
"AlertOutlined",
|
||||
"AlertTwoTone",
|
||||
"AlibabaOutlined",
|
||||
"AlignCenterOutlined",
|
||||
"AlignLeftOutlined",
|
||||
"AlignRightOutlined",
|
||||
"AlipayCircleFilled",
|
||||
"AlipayCircleOutlined",
|
||||
"AlipayOutlined",
|
||||
"AlipaySquareFilled",
|
||||
"AliwangwangFilled",
|
||||
"AliwangwangOutlined",
|
||||
"AliyunOutlined",
|
||||
"AmazonCircleFilled",
|
||||
"AmazonOutlined",
|
||||
"AmazonSquareFilled",
|
||||
"AndroidFilled",
|
||||
"AndroidOutlined",
|
||||
"AntCloudOutlined",
|
||||
"AntDesignOutlined",
|
||||
"ApartmentOutlined",
|
||||
"ApiFilled",
|
||||
"ApiOutlined",
|
||||
"ApiTwoTone",
|
||||
"AppleFilled",
|
||||
"AppleOutlined",
|
||||
"AppstoreAddOutlined",
|
||||
"AppstoreFilled",
|
||||
"AppstoreOutlined",
|
||||
"AppstoreTwoTone",
|
||||
"AreaChartOutlined",
|
||||
"ArrowDownOutlined",
|
||||
"ArrowLeftOutlined",
|
||||
"ArrowRightOutlined",
|
||||
"ArrowUpOutlined",
|
||||
"ArrowsAltOutlined",
|
||||
"AudioFilled",
|
||||
"AudioMutedOutlined",
|
||||
"AudioOutlined",
|
||||
"AudioTwoTone",
|
||||
"AuditOutlined",
|
||||
"BackwardFilled",
|
||||
"BackwardOutlined",
|
||||
"BankFilled",
|
||||
"BankOutlined",
|
||||
"BankTwoTone",
|
||||
"BarChartOutlined",
|
||||
"BarcodeOutlined",
|
||||
"BarsOutlined",
|
||||
"BehanceCircleFilled",
|
||||
"BehanceOutlined",
|
||||
"BehanceSquareFilled",
|
||||
"BehanceSquareOutlined",
|
||||
"BellFilled",
|
||||
"BellOutlined",
|
||||
"BellTwoTone",
|
||||
"BgColorsOutlined",
|
||||
"BlockOutlined",
|
||||
"BoldOutlined",
|
||||
"BookFilled",
|
||||
"BookOutlined",
|
||||
"BookTwoTone",
|
||||
"BorderBottomOutlined",
|
||||
"BorderHorizontalOutlined",
|
||||
"BorderInnerOutlined",
|
||||
"BorderLeftOutlined",
|
||||
"BorderOuterOutlined",
|
||||
"BorderOutlined",
|
||||
"BorderRightOutlined",
|
||||
"BorderTopOutlined",
|
||||
"BorderVerticleOutlined",
|
||||
"BorderlessTableOutlined",
|
||||
"BoxPlotFilled",
|
||||
"BoxPlotOutlined",
|
||||
"BoxPlotTwoTone",
|
||||
"BranchesOutlined",
|
||||
"BugFilled",
|
||||
"BugOutlined",
|
||||
"BugTwoTone",
|
||||
"BuildFilled",
|
||||
"BuildOutlined",
|
||||
"BuildTwoTone",
|
||||
"BulbFilled",
|
||||
"BulbOutlined",
|
||||
"BulbTwoTone",
|
||||
"CalculatorFilled",
|
||||
"CalculatorOutlined",
|
||||
"CalculatorTwoTone",
|
||||
"CalendarFilled",
|
||||
"CalendarOutlined",
|
||||
"CalendarTwoTone",
|
||||
"CameraFilled",
|
||||
"CameraOutlined",
|
||||
"CameraTwoTone",
|
||||
"CarFilled",
|
||||
"CarOutlined",
|
||||
"CarTwoTone",
|
||||
"CaretDownFilled",
|
||||
"CaretDownOutlined",
|
||||
"CaretLeftFilled",
|
||||
"CaretLeftOutlined",
|
||||
"CaretRightFilled",
|
||||
"CaretRightOutlined",
|
||||
"CaretUpFilled",
|
||||
"CaretUpOutlined",
|
||||
"CarryOutFilled",
|
||||
"CarryOutOutlined",
|
||||
"CarryOutTwoTone",
|
||||
"CheckCircleFilled",
|
||||
"CheckCircleOutlined",
|
||||
"CheckCircleTwoTone",
|
||||
"CheckOutlined",
|
||||
"CheckSquareFilled",
|
||||
"CheckSquareOutlined",
|
||||
"CheckSquareTwoTone",
|
||||
"ChromeFilled",
|
||||
"ChromeOutlined",
|
||||
"CiCircleFilled",
|
||||
"CiCircleOutlined",
|
||||
"CiCircleTwoTone",
|
||||
"CiOutlined",
|
||||
"CiTwoTone",
|
||||
"ClearOutlined",
|
||||
"ClockCircleFilled",
|
||||
"ClockCircleOutlined",
|
||||
"ClockCircleTwoTone",
|
||||
"CloseCircleFilled",
|
||||
"CloseCircleOutlined",
|
||||
"CloseCircleTwoTone",
|
||||
"CloseOutlined",
|
||||
"CloseSquareFilled",
|
||||
"CloseSquareOutlined",
|
||||
"CloseSquareTwoTone",
|
||||
"CloudDownloadOutlined",
|
||||
"CloudFilled",
|
||||
"CloudOutlined",
|
||||
"CloudServerOutlined",
|
||||
"CloudSyncOutlined",
|
||||
"CloudTwoTone",
|
||||
"CloudUploadOutlined",
|
||||
"ClusterOutlined",
|
||||
"CodeFilled",
|
||||
"CodeOutlined",
|
||||
"CodeSandboxCircleFilled",
|
||||
"CodeSandboxOutlined",
|
||||
"CodeSandboxSquareFilled",
|
||||
"CodeTwoTone",
|
||||
"CodepenCircleFilled",
|
||||
"CodepenCircleOutlined",
|
||||
"CodepenOutlined",
|
||||
"CodepenSquareFilled",
|
||||
"CoffeeOutlined",
|
||||
"ColumnHeightOutlined",
|
||||
"ColumnWidthOutlined",
|
||||
"CommentOutlined",
|
||||
"CompassFilled",
|
||||
"CompassOutlined",
|
||||
"CompassTwoTone",
|
||||
"CompressOutlined",
|
||||
"ConsoleSqlOutlined",
|
||||
"ContactsFilled",
|
||||
"ContactsOutlined",
|
||||
"ContactsTwoTone",
|
||||
"ContainerFilled",
|
||||
"ContainerOutlined",
|
||||
"ContainerTwoTone",
|
||||
"ControlFilled",
|
||||
"ControlOutlined",
|
||||
"ControlTwoTone",
|
||||
"CopyFilled",
|
||||
"CopyOutlined",
|
||||
"CopyTwoTone",
|
||||
"CopyrightCircleFilled",
|
||||
"CopyrightCircleOutlined",
|
||||
"CopyrightCircleTwoTone",
|
||||
"CopyrightOutlined",
|
||||
"CopyrightTwoTone",
|
||||
"CreditCardFilled",
|
||||
"CreditCardOutlined",
|
||||
"CreditCardTwoTone",
|
||||
"CrownFilled",
|
||||
"CrownOutlined",
|
||||
"CrownTwoTone",
|
||||
"CustomerServiceFilled",
|
||||
"CustomerServiceOutlined",
|
||||
"CustomerServiceTwoTone",
|
||||
"DashOutlined",
|
||||
"DashboardFilled",
|
||||
"DashboardOutlined",
|
||||
"DashboardTwoTone",
|
||||
"DatabaseFilled",
|
||||
"DatabaseOutlined",
|
||||
"DatabaseTwoTone",
|
||||
"DeleteColumnOutlined",
|
||||
"DeleteFilled",
|
||||
"DeleteOutlined",
|
||||
"DeleteRowOutlined",
|
||||
"DeleteTwoTone",
|
||||
"DeliveredProcedureOutlined",
|
||||
"DeploymentUnitOutlined",
|
||||
"DesktopOutlined",
|
||||
"DiffFilled",
|
||||
"DiffOutlined",
|
||||
"DiffTwoTone",
|
||||
"DingdingOutlined",
|
||||
"DingtalkCircleFilled",
|
||||
"DingtalkOutlined",
|
||||
"DingtalkSquareFilled",
|
||||
"DisconnectOutlined",
|
||||
"DislikeFilled",
|
||||
"DislikeOutlined",
|
||||
"DislikeTwoTone",
|
||||
"DollarCircleFilled",
|
||||
"DollarCircleOutlined",
|
||||
"DollarCircleTwoTone",
|
||||
"DollarOutlined",
|
||||
"DollarTwoTone",
|
||||
"DotChartOutlined",
|
||||
"DoubleLeftOutlined",
|
||||
"DoubleRightOutlined",
|
||||
"DownCircleFilled",
|
||||
"DownCircleOutlined",
|
||||
"DownCircleTwoTone",
|
||||
"DownOutlined",
|
||||
"DownSquareFilled",
|
||||
"DownSquareOutlined",
|
||||
"DownSquareTwoTone",
|
||||
"DownloadOutlined",
|
||||
"DragOutlined",
|
||||
"DribbbleCircleFilled",
|
||||
"DribbbleOutlined",
|
||||
"DribbbleSquareFilled",
|
||||
"DribbbleSquareOutlined",
|
||||
"DropboxCircleFilled",
|
||||
"DropboxOutlined",
|
||||
"DropboxSquareFilled",
|
||||
"EditFilled",
|
||||
"EditOutlined",
|
||||
"EditTwoTone",
|
||||
"EllipsisOutlined",
|
||||
"EnterOutlined",
|
||||
"EnvironmentFilled",
|
||||
"EnvironmentOutlined",
|
||||
"EnvironmentTwoTone",
|
||||
"EuroCircleFilled",
|
||||
"EuroCircleOutlined",
|
||||
"EuroCircleTwoTone",
|
||||
"EuroOutlined",
|
||||
"EuroTwoTone",
|
||||
"ExceptionOutlined",
|
||||
"ExclamationCircleFilled",
|
||||
"ExclamationCircleOutlined",
|
||||
"ExclamationCircleTwoTone",
|
||||
"ExclamationOutlined",
|
||||
"ExpandAltOutlined",
|
||||
"ExpandOutlined",
|
||||
"ExperimentFilled",
|
||||
"ExperimentOutlined",
|
||||
"ExperimentTwoTone",
|
||||
"ExportOutlined",
|
||||
"EyeFilled",
|
||||
"EyeInvisibleFilled",
|
||||
"EyeInvisibleOutlined",
|
||||
"EyeInvisibleTwoTone",
|
||||
"EyeOutlined",
|
||||
"EyeTwoTone",
|
||||
"FacebookFilled",
|
||||
"FacebookOutlined",
|
||||
"FallOutlined",
|
||||
"FastBackwardFilled",
|
||||
"FastBackwardOutlined",
|
||||
"FastForwardFilled",
|
||||
"FastForwardOutlined",
|
||||
"FieldBinaryOutlined",
|
||||
"FieldNumberOutlined",
|
||||
"FieldStringOutlined",
|
||||
"FieldTimeOutlined",
|
||||
"FileAddFilled",
|
||||
"FileAddOutlined",
|
||||
"FileAddTwoTone",
|
||||
"FileDoneOutlined",
|
||||
"FileExcelFilled",
|
||||
"FileExcelOutlined",
|
||||
"FileExcelTwoTone",
|
||||
"FileExclamationFilled",
|
||||
"FileExclamationOutlined",
|
||||
"FileExclamationTwoTone",
|
||||
"FileFilled",
|
||||
"FileGifOutlined",
|
||||
"FileImageFilled",
|
||||
"FileImageOutlined",
|
||||
"FileImageTwoTone",
|
||||
"FileJpgOutlined",
|
||||
"FileMarkdownFilled",
|
||||
"FileMarkdownOutlined",
|
||||
"FileMarkdownTwoTone",
|
||||
"FileOutlined",
|
||||
"FilePdfFilled",
|
||||
"FilePdfOutlined",
|
||||
"FilePdfTwoTone",
|
||||
"FilePptFilled",
|
||||
"FilePptOutlined",
|
||||
"FilePptTwoTone",
|
||||
"FileProtectOutlined",
|
||||
"FileSearchOutlined",
|
||||
"FileSyncOutlined",
|
||||
"FileTextFilled",
|
||||
"FileTextOutlined",
|
||||
"FileTextTwoTone",
|
||||
"FileTwoTone",
|
||||
"FileUnknownFilled",
|
||||
"FileUnknownOutlined",
|
||||
"FileUnknownTwoTone",
|
||||
"FileWordFilled",
|
||||
"FileWordOutlined",
|
||||
"FileWordTwoTone",
|
||||
"FileZipFilled",
|
||||
"FileZipOutlined",
|
||||
"FileZipTwoTone",
|
||||
"FilterFilled",
|
||||
"FilterOutlined",
|
||||
"FilterTwoTone",
|
||||
"FireFilled",
|
||||
"FireOutlined",
|
||||
"FireTwoTone",
|
||||
"FlagFilled",
|
||||
"FlagOutlined",
|
||||
"FlagTwoTone",
|
||||
"FolderAddFilled",
|
||||
"FolderAddOutlined",
|
||||
"FolderAddTwoTone",
|
||||
"FolderFilled",
|
||||
"FolderOpenFilled",
|
||||
"FolderOpenOutlined",
|
||||
"FolderOpenTwoTone",
|
||||
"FolderOutlined",
|
||||
"FolderTwoTone",
|
||||
"FolderViewOutlined",
|
||||
"FontColorsOutlined",
|
||||
"FontSizeOutlined",
|
||||
"ForkOutlined",
|
||||
"FormOutlined",
|
||||
"FormatPainterFilled",
|
||||
"FormatPainterOutlined",
|
||||
"ForwardFilled",
|
||||
"ForwardOutlined",
|
||||
"FrownFilled",
|
||||
"FrownOutlined",
|
||||
"FrownTwoTone",
|
||||
"FullscreenExitOutlined",
|
||||
"FullscreenOutlined",
|
||||
"FunctionOutlined",
|
||||
"FundFilled",
|
||||
"FundOutlined",
|
||||
"FundProjectionScreenOutlined",
|
||||
"FundTwoTone",
|
||||
"FundViewOutlined",
|
||||
"FunnelPlotFilled",
|
||||
"FunnelPlotOutlined",
|
||||
"FunnelPlotTwoTone",
|
||||
"GatewayOutlined",
|
||||
"GifOutlined",
|
||||
"GiftFilled",
|
||||
"GiftOutlined",
|
||||
"GiftTwoTone",
|
||||
"GithubFilled",
|
||||
"GithubOutlined",
|
||||
"GitlabFilled",
|
||||
"GitlabOutlined",
|
||||
"GlobalOutlined",
|
||||
"GoldFilled",
|
||||
"GoldOutlined",
|
||||
"GoldTwoTone",
|
||||
"GoldenFilled",
|
||||
"GoogleCircleFilled",
|
||||
"GoogleOutlined",
|
||||
"GooglePlusCircleFilled",
|
||||
"GooglePlusOutlined",
|
||||
"GooglePlusSquareFilled",
|
||||
"GoogleSquareFilled",
|
||||
"GroupOutlined",
|
||||
"HddFilled",
|
||||
"HddOutlined",
|
||||
"HddTwoTone",
|
||||
"HeartFilled",
|
||||
"HeartOutlined",
|
||||
"HeartTwoTone",
|
||||
"HeatMapOutlined",
|
||||
"HighlightFilled",
|
||||
"HighlightOutlined",
|
||||
"HighlightTwoTone",
|
||||
"HistoryOutlined",
|
||||
"HomeFilled",
|
||||
"HomeOutlined",
|
||||
"HomeTwoTone",
|
||||
"HourglassFilled",
|
||||
"HourglassOutlined",
|
||||
"HourglassTwoTone",
|
||||
"Html5Filled",
|
||||
"Html5Outlined",
|
||||
"Html5TwoTone",
|
||||
"IdcardFilled",
|
||||
"IdcardOutlined",
|
||||
"IdcardTwoTone",
|
||||
"IeCircleFilled",
|
||||
"IeOutlined",
|
||||
"IeSquareFilled",
|
||||
"ImportOutlined",
|
||||
"InboxOutlined",
|
||||
"InfoCircleFilled",
|
||||
"InfoCircleOutlined",
|
||||
"InfoCircleTwoTone",
|
||||
"InfoOutlined",
|
||||
"InsertRowAboveOutlined",
|
||||
"InsertRowBelowOutlined",
|
||||
"InsertRowLeftOutlined",
|
||||
"InsertRowRightOutlined",
|
||||
"InstagramFilled",
|
||||
"InstagramOutlined",
|
||||
"InsuranceFilled",
|
||||
"InsuranceOutlined",
|
||||
"InsuranceTwoTone",
|
||||
"InteractionFilled",
|
||||
"InteractionOutlined",
|
||||
"InteractionTwoTone",
|
||||
"IssuesCloseOutlined",
|
||||
"ItalicOutlined",
|
||||
"KeyOutlined",
|
||||
"LaptopOutlined",
|
||||
"LayoutFilled",
|
||||
"LayoutOutlined",
|
||||
"LayoutTwoTone",
|
||||
"LeftCircleFilled",
|
||||
"LeftCircleOutlined",
|
||||
"LeftCircleTwoTone",
|
||||
"LeftOutlined",
|
||||
"LeftSquareFilled",
|
||||
"LeftSquareOutlined",
|
||||
"LeftSquareTwoTone",
|
||||
"LikeFilled",
|
||||
"LikeOutlined",
|
||||
"LikeTwoTone",
|
||||
"LineChartOutlined",
|
||||
"LineHeightOutlined",
|
||||
"LineOutlined",
|
||||
"LinkOutlined",
|
||||
"LinkedinFilled",
|
||||
"LinkedinOutlined",
|
||||
"Loading3QuartersOutlined",
|
||||
"LoadingOutlined",
|
||||
"LockFilled",
|
||||
"LockOutlined",
|
||||
"LockTwoTone",
|
||||
"LoginOutlined",
|
||||
"LogoutOutlined",
|
||||
"MacCommandFilled",
|
||||
"MacCommandOutlined",
|
||||
"MailFilled",
|
||||
"MailOutlined",
|
||||
"MailTwoTone",
|
||||
"ManOutlined",
|
||||
"MedicineBoxFilled",
|
||||
"MedicineBoxOutlined",
|
||||
"MedicineBoxTwoTone",
|
||||
"MediumCircleFilled",
|
||||
"MediumOutlined",
|
||||
"MediumSquareFilled",
|
||||
"MediumWorkmarkOutlined",
|
||||
"MehFilled",
|
||||
"MehOutlined",
|
||||
"MehTwoTone",
|
||||
"MenuFoldOutlined",
|
||||
"MenuOutlined",
|
||||
"MenuUnfoldOutlined",
|
||||
"MergeCellsOutlined",
|
||||
"MessageFilled",
|
||||
"MessageOutlined",
|
||||
"MessageTwoTone",
|
||||
"MinusCircleFilled",
|
||||
"MinusCircleOutlined",
|
||||
"MinusCircleTwoTone",
|
||||
"MinusOutlined",
|
||||
"MinusSquareFilled",
|
||||
"MinusSquareOutlined",
|
||||
"MinusSquareTwoTone",
|
||||
"MobileFilled",
|
||||
"MobileOutlined",
|
||||
"MobileTwoTone",
|
||||
"MoneyCollectFilled",
|
||||
"MoneyCollectOutlined",
|
||||
"MoneyCollectTwoTone",
|
||||
"MonitorOutlined",
|
||||
"MoreOutlined",
|
||||
"NodeCollapseOutlined",
|
||||
"NodeExpandOutlined",
|
||||
"NodeIndexOutlined",
|
||||
"NotificationFilled",
|
||||
"NotificationOutlined",
|
||||
"NotificationTwoTone",
|
||||
"NumberOutlined",
|
||||
"OneToOneOutlined",
|
||||
"OrderedListOutlined",
|
||||
"PaperClipOutlined",
|
||||
"PartitionOutlined",
|
||||
"PauseCircleFilled",
|
||||
"PauseCircleOutlined",
|
||||
"PauseCircleTwoTone",
|
||||
"PauseOutlined",
|
||||
"PayCircleFilled",
|
||||
"PayCircleOutlined",
|
||||
"PercentageOutlined",
|
||||
"PhoneFilled",
|
||||
"PhoneOutlined",
|
||||
"PhoneTwoTone",
|
||||
"PicCenterOutlined",
|
||||
"PicLeftOutlined",
|
||||
"PicRightOutlined",
|
||||
"PictureFilled",
|
||||
"PictureOutlined",
|
||||
"PictureTwoTone",
|
||||
"PieChartFilled",
|
||||
"PieChartOutlined",
|
||||
"PieChartTwoTone",
|
||||
"PlayCircleFilled",
|
||||
"PlayCircleOutlined",
|
||||
"PlayCircleTwoTone",
|
||||
"PlaySquareFilled",
|
||||
"PlaySquareOutlined",
|
||||
"PlaySquareTwoTone",
|
||||
"PlusCircleFilled",
|
||||
"PlusCircleOutlined",
|
||||
"PlusCircleTwoTone",
|
||||
"PlusOutlined",
|
||||
"PlusSquareFilled",
|
||||
"PlusSquareOutlined",
|
||||
"PlusSquareTwoTone",
|
||||
"PoundCircleFilled",
|
||||
"PoundCircleOutlined",
|
||||
"PoundCircleTwoTone",
|
||||
"PoundOutlined",
|
||||
"PoweroffOutlined",
|
||||
"PrinterFilled",
|
||||
"PrinterOutlined",
|
||||
"PrinterTwoTone",
|
||||
"ProfileFilled",
|
||||
"ProfileOutlined",
|
||||
"ProfileTwoTone",
|
||||
"ProjectFilled",
|
||||
"ProjectOutlined",
|
||||
"ProjectTwoTone",
|
||||
"PropertySafetyFilled",
|
||||
"PropertySafetyOutlined",
|
||||
"PropertySafetyTwoTone",
|
||||
"PullRequestOutlined",
|
||||
"PushpinFilled",
|
||||
"PushpinOutlined",
|
||||
"PushpinTwoTone",
|
||||
"QqCircleFilled",
|
||||
"QqOutlined",
|
||||
"QqSquareFilled",
|
||||
"QrcodeOutlined",
|
||||
"QuestionCircleFilled",
|
||||
"QuestionCircleOutlined",
|
||||
"QuestionCircleTwoTone",
|
||||
"QuestionOutlined",
|
||||
"RadarChartOutlined",
|
||||
"RadiusBottomleftOutlined",
|
||||
"RadiusBottomrightOutlined",
|
||||
"RadiusSettingOutlined",
|
||||
"RadiusUpleftOutlined",
|
||||
"RadiusUprightOutlined",
|
||||
"ReadFilled",
|
||||
"ReadOutlined",
|
||||
"ReconciliationFilled",
|
||||
"ReconciliationOutlined",
|
||||
"ReconciliationTwoTone",
|
||||
"RedEnvelopeFilled",
|
||||
"RedEnvelopeOutlined",
|
||||
"RedEnvelopeTwoTone",
|
||||
"RedditCircleFilled",
|
||||
"RedditOutlined",
|
||||
"RedditSquareFilled",
|
||||
"RedoOutlined",
|
||||
"ReloadOutlined",
|
||||
"RestFilled",
|
||||
"RestOutlined",
|
||||
"RestTwoTone",
|
||||
"RetweetOutlined",
|
||||
"RightCircleFilled",
|
||||
"RightCircleOutlined",
|
||||
"RightCircleTwoTone",
|
||||
"RightOutlined",
|
||||
"RightSquareFilled",
|
||||
"RightSquareOutlined",
|
||||
"RightSquareTwoTone",
|
||||
"RiseOutlined",
|
||||
"RobotFilled",
|
||||
"RobotOutlined",
|
||||
"RocketFilled",
|
||||
"RocketOutlined",
|
||||
"RocketTwoTone",
|
||||
"RollbackOutlined",
|
||||
"RotateLeftOutlined",
|
||||
"RotateRightOutlined",
|
||||
"SafetyCertificateFilled",
|
||||
"SafetyCertificateOutlined",
|
||||
"SafetyCertificateTwoTone",
|
||||
"SafetyOutlined",
|
||||
"SaveFilled",
|
||||
"SaveOutlined",
|
||||
"SaveTwoTone",
|
||||
"ScanOutlined",
|
||||
"ScheduleFilled",
|
||||
"ScheduleOutlined",
|
||||
"ScheduleTwoTone",
|
||||
"ScissorOutlined",
|
||||
"SearchOutlined",
|
||||
"SecurityScanFilled",
|
||||
"SecurityScanOutlined",
|
||||
"SecurityScanTwoTone",
|
||||
"SelectOutlined",
|
||||
"SendOutlined",
|
||||
"SettingFilled",
|
||||
"SettingOutlined",
|
||||
"SettingTwoTone",
|
||||
"ShakeOutlined",
|
||||
"ShareAltOutlined",
|
||||
"ShopFilled",
|
||||
"ShopOutlined",
|
||||
"ShopTwoTone",
|
||||
"ShoppingCartOutlined",
|
||||
"ShoppingFilled",
|
||||
"ShoppingOutlined",
|
||||
"ShoppingTwoTone",
|
||||
"ShrinkOutlined",
|
||||
"SignalFilled",
|
||||
"SisternodeOutlined",
|
||||
"SketchCircleFilled",
|
||||
"SketchOutlined",
|
||||
"SketchSquareFilled",
|
||||
"SkinFilled",
|
||||
"SkinOutlined",
|
||||
"SkinTwoTone",
|
||||
"SkypeFilled",
|
||||
"SkypeOutlined",
|
||||
"SlackCircleFilled",
|
||||
"SlackOutlined",
|
||||
"SlackSquareFilled",
|
||||
"SlackSquareOutlined",
|
||||
"SlidersFilled",
|
||||
"SlidersOutlined",
|
||||
"SlidersTwoTone",
|
||||
"SmallDashOutlined",
|
||||
"SmileFilled",
|
||||
"SmileOutlined",
|
||||
"SmileTwoTone",
|
||||
"SnippetsFilled",
|
||||
"SnippetsOutlined",
|
||||
"SnippetsTwoTone",
|
||||
"SolutionOutlined",
|
||||
"SortAscendingOutlined",
|
||||
"SortDescendingOutlined",
|
||||
"SoundFilled",
|
||||
"SoundOutlined",
|
||||
"SoundTwoTone",
|
||||
"SplitCellsOutlined",
|
||||
"StarFilled",
|
||||
"StarOutlined",
|
||||
"StarTwoTone",
|
||||
"StepBackwardFilled",
|
||||
"StepBackwardOutlined",
|
||||
"StepForwardFilled",
|
||||
"StepForwardOutlined",
|
||||
"StockOutlined",
|
||||
"StopFilled",
|
||||
"StopOutlined",
|
||||
"StopTwoTone",
|
||||
"StrikethroughOutlined",
|
||||
"SubnodeOutlined",
|
||||
"SwapLeftOutlined",
|
||||
"SwapOutlined",
|
||||
"SwapRightOutlined",
|
||||
"SwitcherFilled",
|
||||
"SwitcherOutlined",
|
||||
"SwitcherTwoTone",
|
||||
"SyncOutlined",
|
||||
"TableOutlined",
|
||||
"TabletFilled",
|
||||
"TabletOutlined",
|
||||
"TabletTwoTone",
|
||||
"TagFilled",
|
||||
"TagOutlined",
|
||||
"TagTwoTone",
|
||||
"TagsFilled",
|
||||
"TagsOutlined",
|
||||
"TagsTwoTone",
|
||||
"TaobaoCircleFilled",
|
||||
"TaobaoCircleOutlined",
|
||||
"TaobaoOutlined",
|
||||
"TaobaoSquareFilled",
|
||||
"TeamOutlined",
|
||||
"ThunderboltFilled",
|
||||
"ThunderboltOutlined",
|
||||
"ThunderboltTwoTone",
|
||||
"ToTopOutlined",
|
||||
"ToolFilled",
|
||||
"ToolOutlined",
|
||||
"ToolTwoTone",
|
||||
"TrademarkCircleFilled",
|
||||
"TrademarkCircleOutlined",
|
||||
"TrademarkCircleTwoTone",
|
||||
"TrademarkOutlined",
|
||||
"TransactionOutlined",
|
||||
"TranslationOutlined",
|
||||
"TrophyFilled",
|
||||
"TrophyOutlined",
|
||||
"TrophyTwoTone",
|
||||
"TwitterCircleFilled",
|
||||
"TwitterOutlined",
|
||||
"TwitterSquareFilled",
|
||||
"UnderlineOutlined",
|
||||
"UndoOutlined",
|
||||
"UngroupOutlined",
|
||||
"UnlockFilled",
|
||||
"UnlockOutlined",
|
||||
"UnlockTwoTone",
|
||||
"UnorderedListOutlined",
|
||||
"UpCircleFilled",
|
||||
"UpCircleOutlined",
|
||||
"UpCircleTwoTone",
|
||||
"UpOutlined",
|
||||
"UpSquareFilled",
|
||||
"UpSquareOutlined",
|
||||
"UpSquareTwoTone",
|
||||
"UploadOutlined",
|
||||
"UsbFilled",
|
||||
"UsbOutlined",
|
||||
"UsbTwoTone",
|
||||
"UserAddOutlined",
|
||||
"UserDeleteOutlined",
|
||||
"UserOutlined",
|
||||
"UserSwitchOutlined",
|
||||
"UsergroupAddOutlined",
|
||||
"UsergroupDeleteOutlined",
|
||||
"VerifiedOutlined",
|
||||
"VerticalAlignBottomOutlined",
|
||||
"VerticalAlignMiddleOutlined",
|
||||
"VerticalAlignTopOutlined",
|
||||
"VerticalLeftOutlined",
|
||||
"VerticalRightOutlined",
|
||||
"VideoCameraAddOutlined",
|
||||
"VideoCameraFilled",
|
||||
"VideoCameraOutlined",
|
||||
"VideoCameraTwoTone",
|
||||
"WalletFilled",
|
||||
"WalletOutlined",
|
||||
"WalletTwoTone",
|
||||
"WarningFilled",
|
||||
"WarningOutlined",
|
||||
"WarningTwoTone",
|
||||
"WechatFilled",
|
||||
"WechatOutlined",
|
||||
"WeiboCircleFilled",
|
||||
"WeiboCircleOutlined",
|
||||
"WeiboOutlined",
|
||||
"WeiboSquareFilled",
|
||||
"WeiboSquareOutlined",
|
||||
"WhatsAppOutlined",
|
||||
"WifiOutlined",
|
||||
"WindowsFilled",
|
||||
"WindowsOutlined",
|
||||
"WomanOutlined",
|
||||
"YahooFilled",
|
||||
"YahooOutlined",
|
||||
"YoutubeFilled",
|
||||
"YoutubeOutlined",
|
||||
"YuqueFilled",
|
||||
"YuqueOutlined",
|
||||
"ZhihuCircleFilled",
|
||||
"ZhihuOutlined",
|
||||
"ZhihuSquareFilled",
|
||||
"ZoomInOutlined",
|
||||
"ZoomOutOutlined"
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createApp,nextTick } from 'vue';
|
||||
import App from '/@/App.vue';
|
||||
import { saasInit } from '/nerv-lib/saas';
|
||||
import { apiModule } from '/@/api';
|
||||
@@ -6,8 +6,16 @@ import { appConfig } from '/@/config';
|
||||
import './theme/global.less';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { setupGlobDirectives } from '/@/directives';
|
||||
|
||||
import * as Icons from '@ant-design/icons-vue'
|
||||
const app = createApp(App);
|
||||
// 屏蔽黄色警告信息
|
||||
app.config.warnHandler = () => null;
|
||||
nextTick(() => {
|
||||
// 配置全局对象
|
||||
app.config.globalProperties.$icons = Icons
|
||||
// Antd 注入全部图标(这样注入之后,就可以全局直接使用 icon 组件,不需要每个页面去引入了)
|
||||
for (const key in Icons) { app.component(key, Icons[key as keyof typeof Icons]) }
|
||||
})
|
||||
app.component('LeftOutlined', LeftOutlined);
|
||||
|
||||
// Register global directive
|
||||
|
||||
@@ -63,6 +63,25 @@ const organizationManage = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'authorityMenu',//dyfadd 测试权限菜单展示数据,稍后删除
|
||||
name: 'AuthorityMenu',
|
||||
meta: { title: '权限保存获取', hideChildren: true, icon: 'zuzhiguanli' },
|
||||
component: Base,
|
||||
redirect: { name: 'AuthorityMenuIndex' },
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'AuthorityMenuIndex',
|
||||
component: () => import('/nerv-lib/saas/view/menuManage/index.vue'),//E:\2024-project\SaaS-lib\lib\saas\view\menuManage\index.vue
|
||||
meta: {
|
||||
title: '权限保存获取',
|
||||
keepAlive: false,
|
||||
operates: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
export default organizationManage;
|
||||
|
||||
10
hx-ai-intelligent/src/shims-vue.d.ts
vendored
Normal file
10
hx-ai-intelligent/src/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$filters: any,
|
||||
$icons:any,
|
||||
icon:any
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import { http } from '/nerv-lib/util';
|
||||
import { getEnum } from '/@/api';
|
||||
import { NsMessage } from '/nerv-lib/component';
|
||||
|
||||
const enumData: any = await getEnum({ params: { enumType: 'CtrlDeviceType' } });
|
||||
// const enumData: any = await getEnum({ params: { enumType: 'CtrlDeviceType' } });
|
||||
//由于打包生产环境编译时await报错,故改为return形式
|
||||
const enumData: any = ()=>{
|
||||
return getEnum({ params: { enumType: 'CtrlDeviceType' } })
|
||||
}
|
||||
export const tableConfig = (
|
||||
orgId: any,
|
||||
projectId: any,
|
||||
|
||||
173
hx-ai-intelligent/src/view/systemManage/systemMenu/config.ts
Normal file
173
hx-ai-intelligent/src/view/systemManage/systemMenu/config.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { http } from '/nerv-lib/util';
|
||||
import { ref } from 'vue';
|
||||
import { menuS } from '/@/api/menuSystem';
|
||||
// interface DataItem {
|
||||
// key: number;
|
||||
// menuName: string;
|
||||
// menuType: number;
|
||||
// menuIcon: string;
|
||||
// menuRouter:string;
|
||||
// children?: DataItem[];
|
||||
// }
|
||||
// const data: DataItem[] = [
|
||||
// {
|
||||
// key: 1,
|
||||
// menuName: 'John Brown sr.',
|
||||
// menuType: 60,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard',
|
||||
// children: [
|
||||
// {
|
||||
// key: 11,
|
||||
// menuName: 'John Brown',
|
||||
// menuType: 42,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard1',
|
||||
// },
|
||||
// {
|
||||
// key: 12,
|
||||
// menuName: 'John Brown',
|
||||
// menuType: 42,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard3',
|
||||
// children: [
|
||||
// {
|
||||
// key: 121,
|
||||
// menuName: 'John Brown',
|
||||
// menuType: 42,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard4',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 13,
|
||||
// menuName: 'John Brown sr.',
|
||||
// menuType: 60,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard',
|
||||
// children: [
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 2,
|
||||
// menuName: 'John Brown sr.',
|
||||
// menuType: 60,
|
||||
// menuIcon:'icon',
|
||||
// menuRouter: '/dashboard',
|
||||
// },
|
||||
// ];
|
||||
export const tableConfig = (orgId:any,SystemAddEdit:any) => {
|
||||
return ref({
|
||||
title: '系统菜单',
|
||||
api: menuS.queryMenuPage,
|
||||
params: { orgId },
|
||||
pagination: false,
|
||||
headerActions: [
|
||||
{
|
||||
label: '新增',
|
||||
name: 'systemMenuAdd',
|
||||
type: 'primary',
|
||||
handle: () => {
|
||||
console.log("12345")
|
||||
SystemAddEdit.value.toggle(1);
|
||||
// editConfigureDeviceAlarms.value.toggle(null, configureDeviceAlarmsData.value);
|
||||
},
|
||||
},
|
||||
],
|
||||
// rowSelection: {
|
||||
// checkStrictly: false,
|
||||
// onChange: (selectedRowKeys: (string | number)[], selectedRows: DataItem[]) => {
|
||||
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
||||
// },
|
||||
// onSelect: (record: DataItem, selected: boolean, selectedRows: DataItem[]) => {
|
||||
// console.log(record, selected, selectedRows);
|
||||
// },
|
||||
// onSelectAll: (selected: boolean, selectedRows: DataItem[], changeRows: DataItem[]) => {
|
||||
// console.log(selected, selectedRows, changeRows);
|
||||
// },
|
||||
// },
|
||||
columns:[
|
||||
{
|
||||
title: '菜单名称',
|
||||
dataIndex: 'menuName',
|
||||
key: 'menuName',
|
||||
},
|
||||
{
|
||||
title: '菜单类型',
|
||||
dataIndex: 'menuType',
|
||||
key: 'menuType',
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
dataIndex: 'menuIcon',
|
||||
key: 'menuIcon',
|
||||
},
|
||||
{
|
||||
title: '路由',
|
||||
dataIndex: 'menuRouter',
|
||||
key: 'menuRouter',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
key: 'sort',
|
||||
}
|
||||
],
|
||||
formConfig: {
|
||||
schemas: [
|
||||
{
|
||||
field: 'menuName',
|
||||
label: '菜单名称',
|
||||
component: 'NsInput',
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
placeholder: '请输入菜单名称',
|
||||
},
|
||||
}
|
||||
],
|
||||
params: {},
|
||||
},
|
||||
columnActions: {
|
||||
title: '操作',
|
||||
actions: [
|
||||
{
|
||||
label: '新增',
|
||||
name: 'userAdd',
|
||||
handle: (record: any) => {
|
||||
console.log("我是编辑区域11111")
|
||||
console.log(record)
|
||||
SystemAddEdit.value.getMenuList();//请求列表的菜单数据
|
||||
SystemAddEdit.value.toggle(1,record);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
name: 'userEdit',
|
||||
handle: (record: any) => {
|
||||
SystemAddEdit.value.toggle(2,record);
|
||||
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
name: 'userDelete',
|
||||
dynamicParams: { id: 'id' },
|
||||
confirm: true,
|
||||
isReload: true,
|
||||
handle: (data: any) => {
|
||||
// http.post(energyAlarms.del, { id: data.id, orgId: data.orgId }).then(() => {
|
||||
// NsMessage.success('操作成功');
|
||||
// mainEnergyAlarmConfig.value?.nsTableRef.reload();
|
||||
// mainRef.value?.nsTableRef.reload();
|
||||
// });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
rowKey: 'key',
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<!-- 系统菜单的新增、编辑 duyf-->
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="500"
|
||||
:visible="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
destroyOnClose
|
||||
:title="drawerTitele"
|
||||
@close="onClose">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-form-item label="菜单类型:" name="menuType">
|
||||
<a-radio-group v-model:value="menuRadioType">
|
||||
<a-radio-button :value="1" @click="chanageMenuType">一级菜单</a-radio-button>
|
||||
<a-radio-button :value="2" @click="chanageMenuType">子菜单</a-radio-button>
|
||||
<a-radio-button :value="3" @click="chanageMenuType">按钮/权限</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单名称:" name="menuName" v-if="menuNameIsshow">
|
||||
<a-input allowClear v-model:value="formState.menuName" maxlength="255" placeholder="请输入菜单名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单编码:" name="menuCode" v-if="menuCodeIsshow">
|
||||
<a-input allowClear v-model:value="formState.menuCode" maxlength="64" placeholder="请输入菜单编码" />
|
||||
</a-form-item>
|
||||
<a-form-item label="按钮/权限:" name="perissionBtn" v-if="btnPermissIsshow">
|
||||
<a-input v-model:value="formState.perissionBtn" maxlength="255" placeholder="请输入按钮/权限" />
|
||||
</a-form-item>
|
||||
<a-form-item label="上级菜单:" name="previousMenu" v-if="preMenuIsshow">
|
||||
<a-tree-select
|
||||
v-model:value="formState.previousMenu"
|
||||
:tree-line="true"
|
||||
:tree-data="treeData"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单路由:" name="routePath">
|
||||
<a-input v-model:value="formState.routePath" placeholder="请输入路由地址"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单图标:" name="menuIcon" class="iconMenu">
|
||||
<a-input v-model:value="formState.menuIcon" placeholder="点击选择菜单图标" />
|
||||
<SlackOutlined @click="showIconMenu"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="授权标识:" name="authorizationMark" v-if="authorMarkIsshow">
|
||||
<a-input v-model:value="formState.authorizationMark" placeholder="请输入授权标识" />
|
||||
</a-form-item>
|
||||
<a-form-item label="排序:" name="menuSort" v-if="sortIsshow">
|
||||
<a-input type="number" v-model:value="formState.menuSort" min="0" maxlength="7" placeholder="请输入排序号" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
|
||||
<a-button type="primary" @click="onSubmit">确定</a-button>
|
||||
</template>
|
||||
</a-drawer>
|
||||
|
||||
<icon-picker :iconHandleOk="getSonData" ref="refIconPicker"></icon-picker>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref,toRaw,defineExpose,reactive} from 'vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import type { TreeSelectProps } from 'ant-design-vue';
|
||||
import { SlackOutlined } from '@ant-design/icons-vue';
|
||||
import iconPicker from '../../../components/icon/IconPicker.vue';
|
||||
import { NsMessage } from '/nerv-lib/component';
|
||||
const orgId = ref('');
|
||||
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
|
||||
orgId.value = result;
|
||||
const menuRadioType = ref(1);//菜单类型默认值
|
||||
const treeData = ref<TreeSelectProps['treeData']>([]);//存储菜单数据
|
||||
|
||||
const drawerTitele = ref('');
|
||||
const formRef = ref();
|
||||
const visible = ref(false);
|
||||
const btnPermissIsshow = ref(false);//按钮权限是否显示
|
||||
const menuCodeIsshow = ref(true);//菜单编码是否显示
|
||||
const preMenuIsshow = ref(false);//上级菜单是否显示
|
||||
const authorMarkIsshow = ref(false);//授权标识是否显示
|
||||
const menuNameIsshow = ref(true);//菜单名称是否显示
|
||||
const sortIsshow = ref(true);//排序是否显示
|
||||
const menuRouteIsshow = ref(true);//菜单路由是否显示
|
||||
const refIconPicker = ref();
|
||||
interface FormState {
|
||||
orgId:any,
|
||||
menuName: string;
|
||||
previousMenu:string;
|
||||
routePath:string;
|
||||
menuSort:string;
|
||||
perissionBtn:string;
|
||||
authorizationMark:string;
|
||||
menuIcon:string;
|
||||
menuCode:string;
|
||||
type:string;//菜单类型
|
||||
}
|
||||
const formState = ref<FormState>({
|
||||
orgId:'',
|
||||
menuName:'',
|
||||
previousMenu:'',
|
||||
routePath:'',
|
||||
menuSort:'',
|
||||
perissionBtn:'',
|
||||
authorizationMark:'',
|
||||
menuIcon:'',
|
||||
menuCode:'',
|
||||
type:''
|
||||
})
|
||||
const labelCol = { span: 5 };
|
||||
const wrapperCol = { span: 19 };
|
||||
|
||||
// 定义form表单的必填
|
||||
var rules: Record<string, Rule[]> = reactive({
|
||||
menuName: [{ required: true, message: '请输入菜单名称', trigger: 'change' }],
|
||||
menuCode: [{ required: true, message: '请输入菜单编码', trigger: 'change' }],
|
||||
perissionBtn: [{ required: true, message: '请输入按钮/权限', trigger: 'change' }],
|
||||
previousMenu: [{ required: true, message: '请选择上级菜单', trigger: 'change' }],
|
||||
routePath:[{ required: true, message: '请输入菜单路由', trigger: 'change' }],
|
||||
});
|
||||
const emit = defineEmits(['editObject']);
|
||||
// 关闭新增抽屉
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
// formState.value = {
|
||||
// orgId: orgId.value,
|
||||
// }
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
//菜单类型按钮切换
|
||||
const chanageMenuType = (e:any)=>{
|
||||
let currentType = e.target.value;
|
||||
if(currentType==1){//一级菜单
|
||||
menuNameIsshow.value = true;
|
||||
btnPermissIsshow.value = false;
|
||||
preMenuIsshow.value = false;
|
||||
authorMarkIsshow.value = false;
|
||||
sortIsshow.value = true;
|
||||
menuCodeIsshow.value = true;
|
||||
menuRouteIsshow.value = true;
|
||||
}else if(currentType==2){//二级菜单
|
||||
menuNameIsshow.value = true;
|
||||
preMenuIsshow.value = true;
|
||||
btnPermissIsshow.value = false;
|
||||
authorMarkIsshow.value = false;
|
||||
sortIsshow.value = true;
|
||||
menuCodeIsshow.value = true;
|
||||
menuRouteIsshow.value = true;
|
||||
}else{//按钮
|
||||
menuNameIsshow.value = false;
|
||||
preMenuIsshow.value = true;
|
||||
btnPermissIsshow.value = true;
|
||||
authorMarkIsshow.value = true;
|
||||
sortIsshow.value = false;
|
||||
menuCodeIsshow.value = false;
|
||||
menuRouteIsshow.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确定提交
|
||||
const onSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
console.log('values', formState, toRaw(formState));
|
||||
console.log(formState)
|
||||
console.log(toRaw(formState))
|
||||
// emit("editObject",formState)
|
||||
})
|
||||
}
|
||||
//获取菜单列表
|
||||
const getMenuList= ()=>{
|
||||
let temporaryData = [
|
||||
{
|
||||
key: 0,
|
||||
menuName: '首页',
|
||||
menuType: 60,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard',
|
||||
sort:0,
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
menuName: '碳排管理',
|
||||
menuType: 60,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard',
|
||||
sort:1,
|
||||
children: [
|
||||
{
|
||||
key: 11,
|
||||
menuName: '碳排统计',
|
||||
menuType: 42,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard1',
|
||||
sort:2,
|
||||
},
|
||||
{
|
||||
key: 12,
|
||||
menuName: '碳排因子库',
|
||||
menuType: 42,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard3',
|
||||
sort:3,
|
||||
children: [
|
||||
{
|
||||
key: 121,
|
||||
menuName: '新增按钮',
|
||||
menuType: 42,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard4',
|
||||
sort:4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 13,
|
||||
menuName: '碳盘查',
|
||||
menuType: 60,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard',
|
||||
sort:5,
|
||||
children: [
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
menuName: '系统管理',
|
||||
menuType: 60,
|
||||
menuIcon:'icon',
|
||||
menuRouter: '/dashboard',
|
||||
sort:6,
|
||||
},
|
||||
]
|
||||
treeData.value = temporaryData.map(item=>({
|
||||
value: item.key,
|
||||
label: item.menuName,
|
||||
children: item.children ? item.children.map(child => ({
|
||||
value: child.key,
|
||||
label: child.menuName,
|
||||
children: child.children ? child.children.map(childchild => ({
|
||||
value: childchild.key,
|
||||
label: childchild.menuName,
|
||||
})):[]
|
||||
})):[]
|
||||
}))
|
||||
// fetch(group.queryDeviceGroupTree, { energyType: 'ELECTRICITY_USAGE',orgId: orgId.value }).then((res) => {
|
||||
// treeData.value = res.data
|
||||
// treeData.value = treeData.value.map(item => ({
|
||||
// value: item.id,
|
||||
// label: item.pointName,
|
||||
// children: item.children ? item.children.map(child => ({
|
||||
// value: child.id,
|
||||
// label: child.pointName
|
||||
// })) : []
|
||||
// }));
|
||||
// });
|
||||
}
|
||||
//展示icon图标集
|
||||
const showIconMenu = (data:any)=>{
|
||||
console.log("展示icon图标集合")
|
||||
refIconPicker.value.isIconOpenToggle(formState.value.menuIcon);
|
||||
|
||||
}
|
||||
const getSonData = (data:any) => {
|
||||
// formState.icon = data;
|
||||
console.log("子组件中的数据:",data)
|
||||
if(data){
|
||||
formState.value.menuIcon = data;
|
||||
}else{
|
||||
refIconPicker.value.isIconData();
|
||||
}
|
||||
}
|
||||
const toggle = (type:any,record:any)=>{
|
||||
visible.value = true;
|
||||
if(type==1){//新增
|
||||
drawerTitele.value = '新增菜单';
|
||||
}else if(type==2){//编辑
|
||||
drawerTitele.value = '编辑菜单';
|
||||
console.log(record)
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
toggle,
|
||||
getMenuList
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.iconMenu :deep(.ant-form-item-control-input-content){
|
||||
display: flex;
|
||||
.ant-input{
|
||||
border-top-right-radius:0;
|
||||
border-bottom-right-radius:0;
|
||||
}
|
||||
.anticon{
|
||||
padding:7px 10px 0;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius:4px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius:0;
|
||||
cursor: pointer;
|
||||
border-left:0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
hx-ai-intelligent/src/view/systemManage/systemMenu/index.vue
Normal file
43
hx-ai-intelligent/src/view/systemManage/systemMenu/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<!-- <a-table :columns="columns" :data-source="data" :row-selection="rowSelection" /> -->
|
||||
<div style="padding:10px auto;">
|
||||
<ns-view-list-table v-bind="config" :model="data" ref="mainRef">
|
||||
</ns-view-list-table>
|
||||
</div>
|
||||
<!-- 新增or编辑界面 -->
|
||||
<editSystemMenu ref="SystemAddEdit" @editObject="editObject" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { tableConfig } from './config';
|
||||
import { computed, nextTick, onMounted, ref ,unref} from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import editSystemMenu from './editSystemMenu.vue';
|
||||
const orgId = ref('');
|
||||
const result = JSON.parse(sessionStorage.getItem('ORGID')!);
|
||||
orgId.value = result;
|
||||
const SystemAddEdit = ref({});
|
||||
const config = tableConfig(orgId.value,SystemAddEdit);
|
||||
|
||||
// 表格相关数据
|
||||
const data = ref([]);//子组件数据修改时,父组件也跟着改变。
|
||||
const mainRef = ref();//可以定义一个函数使用mainRef.value来调用子组件的方法。
|
||||
defineOptions({
|
||||
name: 'systemMenuIndex', // 与页面路由name一致缓存才可生效
|
||||
});
|
||||
onMounted(() => {
|
||||
});
|
||||
//编辑新增成功时刷新表格
|
||||
const editObject = (val:any)=>{
|
||||
mainRef.value?.nsTableRef.reload();
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ns-table-search),
|
||||
:deep(.ns-part-tree),
|
||||
:deep(.ns-table-main) {
|
||||
box-shadow: @ns-content-box-shadow;
|
||||
}
|
||||
:deep(.ns-basic-table){
|
||||
padding-bottom:15px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user