Showing
19 changed files
with
973 additions
and
21 deletions
src/components/CardList/index.ts
0 → 100644
1 | +<script setup lang="ts"> | ||
2 | + import { BasicForm, FormActionType, useForm } from '/@/components/Form'; | ||
3 | + import { List, ListProps, Button, Tooltip } from 'ant-design-vue'; | ||
4 | + import { computed, Ref, ref, toRaw, unref, useSlots, watch } from 'vue'; | ||
5 | + import { useCardListScroll } from './hooks/useCardListScroll'; | ||
6 | + import { BasicCardListPropsType, CardListActionType } from './types'; | ||
7 | + import CardListLayout from './components/CardListLayout.vue'; | ||
8 | + import { usePagination } from './hooks/usePagination'; | ||
9 | + import { useLoading } from './hooks/useLoading'; | ||
10 | + import { useCardListData } from './hooks/useCardListData'; | ||
11 | + import { useListGrid } from './hooks/useListGrid'; | ||
12 | + import { FETCH_SETTING } from '../../Table/src/const'; | ||
13 | + import { useCardForm } from './hooks/useCardForm'; | ||
14 | + import { Icon } from '/@/components/Icon'; | ||
15 | + | ||
16 | + const emit = defineEmits<{ | ||
17 | + (eventName: 'fetchSuccess', result: { items: Recordable[]; total: number }): void; | ||
18 | + (eventName: 'fetchError', error: Error): void; | ||
19 | + ( | ||
20 | + eventName: 'register', | ||
21 | + cardListActionType: CardListActionType, | ||
22 | + formActions: FormActionType | ||
23 | + ): void; | ||
24 | + }>(); | ||
25 | + | ||
26 | + const slots = useSlots(); | ||
27 | + | ||
28 | + const props = withDefaults(defineProps<BasicCardListPropsType>(), { | ||
29 | + showCardListHeader: true, | ||
30 | + showCardListSetting: true, | ||
31 | + useSearchForm: false, | ||
32 | + gutter: 16, | ||
33 | + immediate: true, | ||
34 | + fetchSetting: () => FETCH_SETTING, | ||
35 | + autoCreateKey: true, | ||
36 | + }); | ||
37 | + | ||
38 | + const tableData = ref<Recordable[]>([]); | ||
39 | + | ||
40 | + const listElRef = ref<Nullable<ComponentElRef>>(null); | ||
41 | + | ||
42 | + const innerPropsRef = ref<BasicCardListPropsType>(); | ||
43 | + | ||
44 | + const [registerForm, formActions] = useForm(); | ||
45 | + | ||
46 | + const getProps = computed<BasicCardListPropsType>(() => { | ||
47 | + return { ...props, ...(unref(innerPropsRef) || {}) } as BasicCardListPropsType; | ||
48 | + }); | ||
49 | + | ||
50 | + const { getListGrid, cardListLayout } = useListGrid(getProps); | ||
51 | + | ||
52 | + const { getPaginationInfo, getPagination, setPagination } = usePagination( | ||
53 | + getProps, | ||
54 | + cardListLayout | ||
55 | + ); | ||
56 | + | ||
57 | + const { loading, setLoading, getLoading } = useLoading(); | ||
58 | + | ||
59 | + const { getDataSourceRef, getRowKey, fetch, reload } = useCardListData(getProps, formActions, { | ||
60 | + setLoading, | ||
61 | + getPaginationInfo, | ||
62 | + emit, | ||
63 | + tableData, | ||
64 | + setPagination, | ||
65 | + }); | ||
66 | + | ||
67 | + const { redoHeight, containerHeight } = useCardListScroll( | ||
68 | + listElRef as Ref<Nullable<ComponentElRef>>, | ||
69 | + getProps, | ||
70 | + getDataSourceRef | ||
71 | + ); | ||
72 | + | ||
73 | + const { replaceFormSlotKey, getFormProps, getFormSlotKeys } = useCardForm( | ||
74 | + getProps, | ||
75 | + slots, | ||
76 | + fetch, | ||
77 | + getLoading | ||
78 | + ); | ||
79 | + | ||
80 | + function handleCardListChange() { | ||
81 | + reload(); | ||
82 | + } | ||
83 | + | ||
84 | + const getBindData = computed<ListProps>(() => { | ||
85 | + const dataSource = unref(getDataSourceRef); | ||
86 | + | ||
87 | + return { | ||
88 | + dataSource, | ||
89 | + grid: unref(getListGrid), | ||
90 | + loading: unref(loading), | ||
91 | + pagination: { | ||
92 | + ...toRaw(unref(getPaginationInfo)), | ||
93 | + onChange: (current: number, pageSize: number) => { | ||
94 | + setPagination({ current, pageSize }); | ||
95 | + handleCardListChange(); | ||
96 | + }, | ||
97 | + }, | ||
98 | + rowKey: unref(getRowKey), | ||
99 | + }; | ||
100 | + }); | ||
101 | + | ||
102 | + watch(cardListLayout, () => { | ||
103 | + const pageSize = cardListLayout.row * cardListLayout.col; | ||
104 | + setPagination({ pageSize }); | ||
105 | + handleCardListChange(); | ||
106 | + }); | ||
107 | + | ||
108 | + const CartListActionType: CardListActionType = { | ||
109 | + setProps, | ||
110 | + setLoading, | ||
111 | + setPagination, | ||
112 | + getPagination, | ||
113 | + reload, | ||
114 | + }; | ||
115 | + | ||
116 | + function setProps(props: Partial<BasicCardListPropsType> = {}) { | ||
117 | + innerPropsRef.value = { ...(unref(innerPropsRef) || {}), ...props } as BasicCardListPropsType; | ||
118 | + } | ||
119 | + | ||
120 | + emit('register', CartListActionType, formActions); | ||
121 | +</script> | ||
122 | + | ||
123 | +<template> | ||
124 | + <main class="basic-card-list p-4 flex flex-col w-full h-full"> | ||
125 | + <section class="w-full bg-light-50 dark:bg-dark-900 p-4 mb-4"> | ||
126 | + <BasicForm | ||
127 | + @register="registerForm" | ||
128 | + v-bind="getFormProps" | ||
129 | + @advanced-change="redoHeight" | ||
130 | + @reset="reload()" | ||
131 | + @submit="reload()" | ||
132 | + > | ||
133 | + <template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys"> | ||
134 | + <slot :name="item" v-bind="data"></slot> | ||
135 | + </template> | ||
136 | + </BasicForm> | ||
137 | + </section> | ||
138 | + | ||
139 | + <section class="w-full bg-light-50 dark:bg-dark-900 p-4 flex-auto h-full"> | ||
140 | + <List ref="listElRef" v-bind="getBindData"> | ||
141 | + <template #header> | ||
142 | + <section class="flex justify-between items-center"> | ||
143 | + <div> | ||
144 | + <span v-if="title"></span> | ||
145 | + <slot v-else name="name"></slot> | ||
146 | + </div> | ||
147 | + <div class="flex flex-auto justify-end mr-4"> | ||
148 | + <slot name="toolbar"></slot> | ||
149 | + </div> | ||
150 | + <div v-if="showCardListSetting" class="flex gap-4"> | ||
151 | + <CardListLayout v-model:row="cardListLayout.row" v-model:col="cardListLayout.col" /> | ||
152 | + <Tooltip title="刷新"> | ||
153 | + <Button type="primary" @click="reload()" :loading="loading"> | ||
154 | + <Icon icon="ant-design:reload-outlined" :size="20" /> | ||
155 | + </Button> | ||
156 | + </Tooltip> | ||
157 | + </div> | ||
158 | + </section> | ||
159 | + </template> | ||
160 | + <template #renderItem="{ item }"> | ||
161 | + <List.Item> | ||
162 | + <slot name="renderItem" :item="item" :totalHeight="containerHeight"></slot> | ||
163 | + </List.Item> | ||
164 | + </template> | ||
165 | + </List> | ||
166 | + </section> | ||
167 | + </main> | ||
168 | +</template> | ||
169 | + | ||
170 | +<style lang="less" scoped> | ||
171 | + .basic-card-list { | ||
172 | + :deep(.ant-list) { | ||
173 | + .ant-spin-container { | ||
174 | + overflow-x: hidden; | ||
175 | + overflow-y: auto; | ||
176 | + | ||
177 | + .ant-row { | ||
178 | + row-gap: 16px; | ||
179 | + | ||
180 | + .ant-list-item { | ||
181 | + margin-bottom: 0; | ||
182 | + } | ||
183 | + } | ||
184 | + } | ||
185 | + | ||
186 | + .ant-list-header { | ||
187 | + border-bottom: transparent; | ||
188 | + } | ||
189 | + | ||
190 | + .ant-list-pagination { | ||
191 | + margin-top: 16px; | ||
192 | + height: 25px; | ||
193 | + } | ||
194 | + | ||
195 | + .ant-list-empty-text { | ||
196 | + height: 100%; | ||
197 | + display: flex; | ||
198 | + align-items: center; | ||
199 | + justify-content: center; | ||
200 | + } | ||
201 | + } | ||
202 | + } | ||
203 | +</style> |
1 | +<script setup lang="ts"> | ||
2 | + import { Button, Popover } from 'ant-design-vue'; | ||
3 | + import { reactive, ref, watch } from 'vue'; | ||
4 | + import { Icon } from '/@/components/Icon'; | ||
5 | + | ||
6 | + const emit = defineEmits(['update:row', 'update:col']); | ||
7 | + | ||
8 | + const props = withDefaults( | ||
9 | + defineProps<{ | ||
10 | + row?: number; | ||
11 | + col?: number; | ||
12 | + }>(), | ||
13 | + { | ||
14 | + row: 2, | ||
15 | + col: 5, | ||
16 | + } | ||
17 | + ); | ||
18 | + | ||
19 | + const MAX_ROW = 4; | ||
20 | + const MAX_COL = 9; | ||
21 | + | ||
22 | + const visible = ref(false); | ||
23 | + | ||
24 | + const selectedLayout = reactive({ row: props.row, col: props.col }); | ||
25 | + | ||
26 | + const handleOver = (row: number, col: number) => { | ||
27 | + Object.assign(selectedLayout, { row, col }); | ||
28 | + }; | ||
29 | + | ||
30 | + const handleSelectConfirm = () => { | ||
31 | + const { row, col } = selectedLayout; | ||
32 | + emit('update:row', row); | ||
33 | + emit('update:col', col); | ||
34 | + visible.value = false; | ||
35 | + }; | ||
36 | + | ||
37 | + watch( | ||
38 | + () => [props.row, props.col], | ||
39 | + () => { | ||
40 | + selectedLayout.row = props.row; | ||
41 | + selectedLayout.col = props.col; | ||
42 | + } | ||
43 | + ); | ||
44 | +</script> | ||
45 | + | ||
46 | +<template> | ||
47 | + <Popover v-model:visible="visible"> | ||
48 | + <template #content> | ||
49 | + <section class="flex flex-wrap w-36"> | ||
50 | + <table class="border-collapse" @click="handleSelectConfirm"> | ||
51 | + <tr v-for="row in MAX_ROW" :key="row" class="border border-gray-300 border-solid"> | ||
52 | + <td | ||
53 | + v-for="col in MAX_COL" | ||
54 | + :class="selectedLayout.col >= col && selectedLayout.row >= row ? 'bg-blue-500' : ''" | ||
55 | + :key="col" | ||
56 | + class="w-4 h-4 border border-gray-300 border-solid cursor-pointer" | ||
57 | + @mouseover="handleOver(row, col)" | ||
58 | + > | ||
59 | + </td> | ||
60 | + </tr> | ||
61 | + </table> | ||
62 | + <div class="text-center w-full"> | ||
63 | + {{ selectedLayout.col }} x {{ selectedLayout.row }} 布局 | ||
64 | + </div> | ||
65 | + </section> | ||
66 | + </template> | ||
67 | + <Button type="primary"> | ||
68 | + <Icon icon="ant-design:layout-filled" :size="20" /> | ||
69 | + </Button> | ||
70 | + </Popover> | ||
71 | +</template> |
1 | +import type { ComputedRef, ExtractPropTypes, Slots } from 'vue'; | ||
2 | +import { unref, computed } from 'vue'; | ||
3 | +import { isFunction } from '/@/utils/is'; | ||
4 | +import { BasicCardListPropsType } from '../types'; | ||
5 | +import { FetchParams } from './useCardListData'; | ||
6 | +import { basicProps } from '/@/components/Form/src/props'; | ||
7 | + | ||
8 | +export function useCardForm( | ||
9 | + propsRef: ComputedRef<BasicCardListPropsType>, | ||
10 | + slots: Slots, | ||
11 | + fetch: (opt?: FetchParams | undefined) => Promise<void>, | ||
12 | + getLoading: ComputedRef<boolean | undefined> | ||
13 | +) { | ||
14 | + const getFormProps = computed((): ExtractPropTypes<typeof basicProps> => { | ||
15 | + const { formConfig } = unref(propsRef); | ||
16 | + const { submitButtonOptions } = formConfig || {}; | ||
17 | + return { | ||
18 | + showAdvancedButton: true, | ||
19 | + ...formConfig, | ||
20 | + submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions }, | ||
21 | + compact: true, | ||
22 | + } as any; | ||
23 | + }); | ||
24 | + | ||
25 | + const getFormSlotKeys: ComputedRef<string[]> = computed(() => { | ||
26 | + const keys = Object.keys(slots); | ||
27 | + return keys | ||
28 | + .map((item) => (item.startsWith('form-') ? item : null)) | ||
29 | + .filter((item) => !!item) as string[]; | ||
30 | + }); | ||
31 | + | ||
32 | + function replaceFormSlotKey(key: string) { | ||
33 | + if (!key) return ''; | ||
34 | + return key?.replace?.(/form\-/, '') ?? ''; | ||
35 | + } | ||
36 | + | ||
37 | + function handleSearchInfoChange(info: Recordable) { | ||
38 | + const { handleSearchInfoFn } = unref(propsRef); | ||
39 | + if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) { | ||
40 | + info = handleSearchInfoFn(info) || info; | ||
41 | + } | ||
42 | + fetch({ searchInfo: info, page: 1 }); | ||
43 | + } | ||
44 | + | ||
45 | + return { | ||
46 | + getFormProps, | ||
47 | + replaceFormSlotKey, | ||
48 | + getFormSlotKeys, | ||
49 | + handleSearchInfoChange, | ||
50 | + }; | ||
51 | +} |
1 | +import { WatchStopHandle, onUnmounted, ref, unref, watch } from 'vue'; | ||
2 | +import { BasicCardListPropsType, CardListActionType } from '../types'; | ||
3 | +import { isProdMode } from '/@/utils/env'; | ||
4 | +import { error } from '/@/utils/log'; | ||
5 | +import { FormActionType } from '/@/components/Form'; | ||
6 | +import { getDynamicProps } from '/@/utils'; | ||
7 | +import { PaginationProps } from 'ant-design-vue'; | ||
8 | +import { FetchParams } from './useCardListData'; | ||
9 | + | ||
10 | +type UseTableMethod = CardListActionType & { | ||
11 | + getForm: () => FormActionType; | ||
12 | +}; | ||
13 | + | ||
14 | +export function useCardList( | ||
15 | + cardListProps?: BasicCardListPropsType | ||
16 | +): [(instance: CardListActionType, formInstance: FormActionType) => void, CardListActionType] { | ||
17 | + const cardListRef = ref<Nullable<CardListActionType>>(null); | ||
18 | + const loadedRef = ref<Nullable<boolean>>(false); | ||
19 | + const formRef = ref<Nullable<FormActionType>>(null); | ||
20 | + | ||
21 | + let stopWatch: WatchStopHandle; | ||
22 | + | ||
23 | + function register(instance: CardListActionType, formInstance: FormActionType) { | ||
24 | + isProdMode() && | ||
25 | + onUnmounted(() => { | ||
26 | + cardListRef.value = null; | ||
27 | + loadedRef.value = null; | ||
28 | + }); | ||
29 | + | ||
30 | + if (unref(loadedRef) && isProdMode() && instance === unref(cardListRef)) return; | ||
31 | + | ||
32 | + cardListRef.value = instance; | ||
33 | + formRef.value = formInstance; | ||
34 | + cardListProps && instance.setProps(getDynamicProps(cardListProps)); | ||
35 | + loadedRef.value = true; | ||
36 | + | ||
37 | + stopWatch?.(); | ||
38 | + | ||
39 | + stopWatch = watch( | ||
40 | + () => cardListProps, | ||
41 | + () => { | ||
42 | + cardListProps && instance.setProps(getDynamicProps(cardListProps)); | ||
43 | + }, | ||
44 | + { | ||
45 | + immediate: true, | ||
46 | + deep: true, | ||
47 | + } | ||
48 | + ); | ||
49 | + } | ||
50 | + | ||
51 | + function getTableInstance(): CardListActionType { | ||
52 | + const table = unref(cardListRef); | ||
53 | + if (!table) { | ||
54 | + error( | ||
55 | + 'The CardList instance has not been obtained yet, please make sure the table is presented when performing the table operation!' | ||
56 | + ); | ||
57 | + } | ||
58 | + return table as CardListActionType; | ||
59 | + } | ||
60 | + | ||
61 | + const cardListActionType: UseTableMethod = { | ||
62 | + getForm: () => { | ||
63 | + return unref(formRef) as unknown as FormActionType; | ||
64 | + }, | ||
65 | + setLoading: (loading: boolean) => { | ||
66 | + return getTableInstance().setLoading(loading); | ||
67 | + }, | ||
68 | + setProps: (props: Partial<BasicCardListPropsType>) => { | ||
69 | + getTableInstance().setProps(props); | ||
70 | + }, | ||
71 | + getPagination: () => { | ||
72 | + return getTableInstance().getPagination(); | ||
73 | + }, | ||
74 | + setPagination: (pagination: Partial<PaginationProps>) => { | ||
75 | + return getTableInstance().setPagination(pagination); | ||
76 | + }, | ||
77 | + reload: (opt?: FetchParams) => { | ||
78 | + return getTableInstance().reload(opt); | ||
79 | + }, | ||
80 | + }; | ||
81 | + | ||
82 | + return [register, cardListActionType]; | ||
83 | +} |
1 | +import { Ref, computed, onMounted, ref, unref, watch, watchEffect } from 'vue'; | ||
2 | +import { BasicCardListPropsType, CardListEmitType, UseLoading, UsePaginationType } from '../types'; | ||
3 | +import { FormActionType } from '/@/components/Form'; | ||
4 | +import { isBoolean, isFunction } from '/@/utils/is'; | ||
5 | +import { FETCH_SETTING, ROW_KEY } from '/@/components/Table/src/const'; | ||
6 | +import { PaginationProps } from 'ant-design-vue'; | ||
7 | +import { cloneDeep, get } from 'lodash-es'; | ||
8 | +import { buildUUID } from '/@/utils/uuid'; | ||
9 | +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | ||
10 | + | ||
11 | +interface ActionType { | ||
12 | + setLoading: UseLoading['setLoading']; | ||
13 | + getPaginationInfo: UsePaginationType['getPaginationInfo']; | ||
14 | + setPagination: UsePaginationType['setPagination']; | ||
15 | + tableData: Ref<Recordable[]>; | ||
16 | + emit: CardListEmitType; | ||
17 | +} | ||
18 | + | ||
19 | +export interface FetchParams { | ||
20 | + searchInfo?: Recordable; | ||
21 | + page?: number; | ||
22 | + sortInfo?: Recordable; | ||
23 | + filterInfo?: Recordable; | ||
24 | +} | ||
25 | + | ||
26 | +export function useCardListData( | ||
27 | + propsRef: Ref<BasicCardListPropsType>, | ||
28 | + formActionType: FormActionType, | ||
29 | + actionType: ActionType | ||
30 | +) { | ||
31 | + const { getFieldsValue } = unref(formActionType)! || {}; | ||
32 | + const { getPaginationInfo, setLoading, setPagination, tableData, emit } = actionType; | ||
33 | + | ||
34 | + const dataSourceRef = ref<Recordable[]>([]); | ||
35 | + const rawDataSourceRef = ref<Recordable>({}); | ||
36 | + | ||
37 | + watchEffect(() => { | ||
38 | + tableData.value = unref(dataSourceRef); | ||
39 | + }); | ||
40 | + | ||
41 | + watch( | ||
42 | + () => unref(propsRef).dataSource, | ||
43 | + () => { | ||
44 | + const { dataSource, api } = unref(propsRef); | ||
45 | + !api && dataSource && (dataSourceRef.value = dataSource); | ||
46 | + }, | ||
47 | + { | ||
48 | + immediate: true, | ||
49 | + } | ||
50 | + ); | ||
51 | + | ||
52 | + function setTableKey(items: any[]) { | ||
53 | + if (!items || !Array.isArray(items)) return; | ||
54 | + items.forEach((item) => { | ||
55 | + if (!item[ROW_KEY]) { | ||
56 | + item[ROW_KEY] = buildUUID(); | ||
57 | + } | ||
58 | + if (item.children && item.children.length) { | ||
59 | + setTableKey(item.children); | ||
60 | + } | ||
61 | + }); | ||
62 | + } | ||
63 | + | ||
64 | + const getAutoCreateKey = computed(() => { | ||
65 | + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; | ||
66 | + }); | ||
67 | + | ||
68 | + const getRowKey = computed(() => { | ||
69 | + const { rowKey } = unref(propsRef); | ||
70 | + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; | ||
71 | + }); | ||
72 | + | ||
73 | + const getDataSourceRef = computed(() => { | ||
74 | + const dataSource = unref(dataSourceRef); | ||
75 | + if (!dataSource || dataSource.length === 0) { | ||
76 | + return unref(dataSourceRef); | ||
77 | + } | ||
78 | + if (unref(getAutoCreateKey)) { | ||
79 | + const firstItem = dataSource[0]; | ||
80 | + const lastItem = dataSource[dataSource.length - 1]; | ||
81 | + | ||
82 | + if (firstItem && lastItem) { | ||
83 | + if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) { | ||
84 | + const data = cloneDeep(unref(dataSourceRef)); | ||
85 | + data.forEach((item) => { | ||
86 | + if (!item[ROW_KEY]) { | ||
87 | + item[ROW_KEY] = buildUUID(); | ||
88 | + } | ||
89 | + if (item.children && item.children.length) { | ||
90 | + setTableKey(item.children); | ||
91 | + } | ||
92 | + }); | ||
93 | + dataSourceRef.value = data; | ||
94 | + } | ||
95 | + } | ||
96 | + } | ||
97 | + return unref(dataSourceRef); | ||
98 | + }); | ||
99 | + | ||
100 | + async function fetch(opt?: FetchParams) { | ||
101 | + const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = | ||
102 | + unref(propsRef); | ||
103 | + | ||
104 | + if (!api || !isFunction(api)) return; | ||
105 | + | ||
106 | + try { | ||
107 | + setLoading(true); | ||
108 | + const { pageField, sizeField, listField, totalField } = Object.assign( | ||
109 | + {}, | ||
110 | + FETCH_SETTING, | ||
111 | + fetchSetting | ||
112 | + ); | ||
113 | + let pageParams: Recordable = {}; | ||
114 | + | ||
115 | + const { current = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps; | ||
116 | + | ||
117 | + if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) { | ||
118 | + pageParams = {}; | ||
119 | + } else { | ||
120 | + pageParams[pageField] = current; | ||
121 | + pageParams[sizeField] = pageSize; | ||
122 | + } | ||
123 | + | ||
124 | + let params: Recordable = { | ||
125 | + ...pageParams, | ||
126 | + ...(useSearchForm ? getFieldsValue() : {}), | ||
127 | + ...searchInfo, | ||
128 | + }; | ||
129 | + | ||
130 | + if (beforeFetch && isFunction(beforeFetch)) { | ||
131 | + params = (await beforeFetch(params)) || params; | ||
132 | + } | ||
133 | + | ||
134 | + const res = await api(params); | ||
135 | + rawDataSourceRef.value = res; | ||
136 | + | ||
137 | + const isArrayResult = Array.isArray(res); | ||
138 | + | ||
139 | + let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); | ||
140 | + const resultTotal: number = isArrayResult ? 0 : get(res, totalField); | ||
141 | + | ||
142 | + // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行 | ||
143 | + if (resultTotal) { | ||
144 | + const currentTotalPage = Math.ceil(resultTotal / pageSize); | ||
145 | + if (current > currentTotalPage) { | ||
146 | + setPagination({ | ||
147 | + current: currentTotalPage, | ||
148 | + }); | ||
149 | + fetch(opt); | ||
150 | + } | ||
151 | + } | ||
152 | + | ||
153 | + if (afterFetch && isFunction(afterFetch)) { | ||
154 | + resultItems = (await afterFetch(resultItems, res)) || resultItems; | ||
155 | + } | ||
156 | + dataSourceRef.value = resultItems; | ||
157 | + setPagination({ | ||
158 | + total: resultTotal || 0, | ||
159 | + }); | ||
160 | + if (opt && opt.page) { | ||
161 | + setPagination({ | ||
162 | + current: opt.page || 1, | ||
163 | + }); | ||
164 | + } | ||
165 | + emit('fetchSuccess', { | ||
166 | + items: unref(resultItems), | ||
167 | + total: resultTotal, | ||
168 | + }); | ||
169 | + } catch (error) { | ||
170 | + emit('fetchError', error as Error); | ||
171 | + dataSourceRef.value = []; | ||
172 | + setPagination({ | ||
173 | + total: 0, | ||
174 | + }); | ||
175 | + } finally { | ||
176 | + setLoading(false); | ||
177 | + } | ||
178 | + } | ||
179 | + | ||
180 | + async function reload(opt?: FetchParams) { | ||
181 | + await fetch(opt); | ||
182 | + } | ||
183 | + | ||
184 | + onMounted(() => { | ||
185 | + useTimeoutFn(() => { | ||
186 | + unref(propsRef).immediate && fetch(); | ||
187 | + }, 16); | ||
188 | + }); | ||
189 | + | ||
190 | + return { | ||
191 | + reload, | ||
192 | + fetch, | ||
193 | + getRowKey, | ||
194 | + getDataSourceRef, | ||
195 | + }; | ||
196 | +} |
1 | +import { Ref, ref, unref, watch } from 'vue'; | ||
2 | +import { getBoundingClientRect } from '/@/utils/domUtils'; | ||
3 | +import { BasicCardListPropsType } from '../types'; | ||
4 | +import { useDebounceFn } from '@vueuse/core'; | ||
5 | + | ||
6 | +export function useCardListScroll( | ||
7 | + cardListElRef: Ref<Nullable<ComponentElRef>>, | ||
8 | + propsRef: Ref<BasicCardListPropsType>, | ||
9 | + getDataSourceRef: Ref<Recordable[]> | ||
10 | +) { | ||
11 | + const containerHeight = ref(0); | ||
12 | + | ||
13 | + const redoHeight = () => { | ||
14 | + const listEl = unref(cardListElRef)?.$el; | ||
15 | + if (!listEl) return; | ||
16 | + | ||
17 | + const listContainerEl = listEl.querySelector('.ant-spin-container') as HTMLDivElement; | ||
18 | + | ||
19 | + if (!listContainerEl) return; | ||
20 | + | ||
21 | + const rect = getBoundingClientRect(listContainerEl); | ||
22 | + | ||
23 | + if (!rect) return; | ||
24 | + | ||
25 | + const { top } = rect as DOMRect; | ||
26 | + | ||
27 | + const { offsetHeight: otherOffsetHeight = 0 } = unref(propsRef); | ||
28 | + | ||
29 | + const totalHeight = document.documentElement.clientHeight; | ||
30 | + | ||
31 | + const offsetHeight = 32; | ||
32 | + | ||
33 | + const paginationHeight = 25 + 16; | ||
34 | + | ||
35 | + const residualHeight = totalHeight - top - offsetHeight - paginationHeight - otherOffsetHeight; | ||
36 | + | ||
37 | + listContainerEl.style.maxHeight = `${residualHeight}px`; | ||
38 | + listContainerEl.style.height = `${residualHeight}px`; | ||
39 | + | ||
40 | + containerHeight.value = residualHeight; | ||
41 | + }; | ||
42 | + | ||
43 | + const debounceRedoHeight = useDebounceFn(redoHeight, 100); | ||
44 | + | ||
45 | + watch( | ||
46 | + () => [unref(getDataSourceRef)?.length], | ||
47 | + () => { | ||
48 | + debounceRedoHeight(); | ||
49 | + }, | ||
50 | + { | ||
51 | + flush: 'post', | ||
52 | + } | ||
53 | + ); | ||
54 | + | ||
55 | + return { redoHeight, containerHeight }; | ||
56 | +} |
1 | +import { Ref, computed, reactive, unref } from 'vue'; | ||
2 | +import { BasicCardListPropsType, ListGridType } from '../types'; | ||
3 | +import { getListGridByColumn } from '../utils'; | ||
4 | + | ||
5 | +export function useListGrid(getProps: Ref<BasicCardListPropsType>) { | ||
6 | + const cardListLayout = reactive({ row: 2, col: 5 }); | ||
7 | + | ||
8 | + const getListGrid = computed<ListGridType>(() => { | ||
9 | + const { col } = cardListLayout; | ||
10 | + const { gutter = 16 } = unref(getProps); | ||
11 | + return { | ||
12 | + column: col, | ||
13 | + gutter, | ||
14 | + ...getListGridByColumn(col), | ||
15 | + } as ListGridType; | ||
16 | + }); | ||
17 | + | ||
18 | + return { | ||
19 | + getListGrid, | ||
20 | + cardListLayout, | ||
21 | + }; | ||
22 | +} |
1 | +import { computed, ref, unref } from 'vue'; | ||
2 | + | ||
3 | +export function useLoading() { | ||
4 | + const loading = ref(false); | ||
5 | + | ||
6 | + const setLoading = (status: boolean) => (loading.value = status); | ||
7 | + | ||
8 | + const getLoading = computed(() => unref(loading)); | ||
9 | + | ||
10 | + return { | ||
11 | + loading, | ||
12 | + getLoading, | ||
13 | + setLoading, | ||
14 | + }; | ||
15 | +} |
1 | +import { PaginationProps } from 'ant-design-vue'; | ||
2 | +import { Ref, computed, ref, unref } from 'vue'; | ||
3 | +import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; | ||
4 | +import { BasicCardListPropsType } from '../types'; | ||
5 | +import { isBoolean } from '/@/utils/is'; | ||
6 | + | ||
7 | +interface ItemRender { | ||
8 | + page: number; | ||
9 | + type: 'page' | 'prev' | 'next'; | ||
10 | + originalElement: any; | ||
11 | +} | ||
12 | + | ||
13 | +function itemRender({ page, type, originalElement }: ItemRender) { | ||
14 | + if (type === 'prev') { | ||
15 | + return page === 0 ? null : <LeftOutlined />; | ||
16 | + } else if (type === 'next') { | ||
17 | + return page === 1 ? null : <RightOutlined />; | ||
18 | + } | ||
19 | + return originalElement; | ||
20 | +} | ||
21 | + | ||
22 | +export function usePagination( | ||
23 | + propsRef: Ref<BasicCardListPropsType>, | ||
24 | + cardLayoutRef: Record<'row' | 'col', number> | ||
25 | +) { | ||
26 | + const configRef = ref<PaginationProps>({ | ||
27 | + hideOnSinglePage: false, | ||
28 | + }); | ||
29 | + | ||
30 | + const getPaginationInfo = computed(() => { | ||
31 | + const { pagination } = unref(propsRef); | ||
32 | + const { col, row } = cardLayoutRef; | ||
33 | + | ||
34 | + return { | ||
35 | + current: 1, | ||
36 | + pageSize: col * row, | ||
37 | + size: 'small', | ||
38 | + defaultPageSize: col * row, | ||
39 | + showTotal: (total: number) => `共 ${total} 条数据`, | ||
40 | + showSizeChanger: false, | ||
41 | + itemRender: itemRender, | ||
42 | + showQuickJumper: true, | ||
43 | + ...(isBoolean(pagination) ? {} : pagination), | ||
44 | + ...unref(configRef), | ||
45 | + } as Partial<PaginationProps>; | ||
46 | + }); | ||
47 | + | ||
48 | + const setPagination = (info: Partial<PaginationProps>) => { | ||
49 | + const paginationInfo = unref(getPaginationInfo); | ||
50 | + configRef.value = { | ||
51 | + ...(!isBoolean(paginationInfo) ? paginationInfo : {}), | ||
52 | + ...info, | ||
53 | + }; | ||
54 | + }; | ||
55 | + | ||
56 | + function getPagination() { | ||
57 | + return unref(getPaginationInfo); | ||
58 | + } | ||
59 | + | ||
60 | + return { getPaginationInfo, setPagination, getPagination }; | ||
61 | +} |
src/components/CardList/src/types.ts
0 → 100644
1 | +import { PaginationProps } from 'ant-design-vue'; | ||
2 | +import { FormProps } from '../../Form'; | ||
3 | +import { FetchSetting } from '../../Table'; | ||
4 | +import { useLoading } from './hooks/useLoading'; | ||
5 | +import { usePagination } from './hooks/usePagination'; | ||
6 | +import { FetchParams, useCardListData } from './hooks/useCardListData'; | ||
7 | + | ||
8 | +// export interface CardList | ||
9 | + | ||
10 | +export interface BasicCardListPropsType<T = Recordable> { | ||
11 | + title?: string; | ||
12 | + formConfig?: FormProps; | ||
13 | + showCardListSetting?: boolean; | ||
14 | + useSearchForm?: boolean; | ||
15 | + api?: (params?: any) => Promise<any>; | ||
16 | + dataSource?: any[]; | ||
17 | + beforeFetch?: (params: FetchParams) => Promise<Recordable>; | ||
18 | + pagination?: PaginationProps; | ||
19 | + offsetHeight?: number; | ||
20 | + gutter?: number; | ||
21 | + searchInfo?: Recordable; | ||
22 | + fetchSetting?: FetchSetting; | ||
23 | + afterFetch?: (items: T[], result: any) => Promise<any[]>; | ||
24 | + autoCreateKey?: boolean; | ||
25 | + rowKey?: (item: T) => string | number; | ||
26 | + immediate?: boolean; | ||
27 | + handleSearchInfoFn?: Fn; | ||
28 | +} | ||
29 | + | ||
30 | +export type ListGridType = Record< | ||
31 | + 'column' | 'gutter' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', | ||
32 | + number | ||
33 | +>; | ||
34 | + | ||
35 | +export type UseLoading = ReturnType<typeof useLoading>; | ||
36 | + | ||
37 | +export type UsePaginationType = ReturnType<typeof usePagination>; | ||
38 | +export type UseCardListDataType = ReturnType<typeof useCardListData>; | ||
39 | + | ||
40 | +export interface CardListActionType { | ||
41 | + setProps: (props: Partial<BasicCardListPropsType>) => void; | ||
42 | + setLoading: UseLoading['setLoading']; | ||
43 | + setPagination: UsePaginationType['setPagination']; | ||
44 | + getPagination: UsePaginationType['getPagination']; | ||
45 | + reload: UseCardListDataType['reload']; | ||
46 | +} | ||
47 | + | ||
48 | +export interface CardListEmitType { | ||
49 | + (eventName: 'fetchSuccess', result: { items: Recordable[]; total: number }): void; | ||
50 | + (eventName: 'fetchError', error: Error): void; | ||
51 | +} | ||
52 | + | ||
53 | +export interface CardListRenderItem<T = Recordable> { | ||
54 | + item: T; | ||
55 | + totalHeight: number; | ||
56 | +} |
src/components/CardList/src/utils/index.ts
0 → 100644
1 | +import { ListGridType } from '../types'; | ||
2 | + | ||
3 | +export const getListGridByColumn = (col: number): Omit<ListGridType, 'gutter' | 'column'> => { | ||
4 | + return { | ||
5 | + xxl: col, | ||
6 | + xl: col, | ||
7 | + lg: Math.min(col - 1, 2), | ||
8 | + md: Math.min(col - 1, 2), | ||
9 | + sm: Math.min(col - 2, 2), | ||
10 | + xs: Math.min(col - 2, 2), | ||
11 | + }; | ||
12 | +}; |
1 | export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; | 1 | export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; |
2 | export { default as CardLayoutButton } from './CardLayoutButton.vue'; | 2 | export { default as CardLayoutButton } from './CardLayoutButton.vue'; |
3 | export { default as AuthIcon } from './AuthIcon.vue'; | 3 | export { default as AuthIcon } from './AuthIcon.vue'; |
4 | +export { default as AuthDropDown } from './AuthDropDown.vue'; | ||
4 | export { | 5 | export { |
5 | EnumTableCardMode, | 6 | EnumTableCardMode, |
6 | EnumTableChartMode, | 7 | EnumTableChartMode, |
1 | +import { WatchStopHandle, onUnmounted, ref, unref, watch } from 'vue'; | ||
2 | +import { OrganizationTreeActionType } from '../src/types'; | ||
3 | +import { isProdMode } from '/@/utils/env'; | ||
4 | +import { error } from '/@/utils/log'; | ||
5 | +import { OrganizationTreePropsType } from '../src/props'; | ||
6 | +import { getDynamicProps } from '/@/utils'; | ||
7 | + | ||
8 | +export function useOrganizationTree( | ||
9 | + organizationTreeProps?: Partial<OrganizationTreePropsType> | ||
10 | +): [(instance: OrganizationTreeActionType) => void, OrganizationTreeActionType] { | ||
11 | + const organizationTreeRef = ref<Nullable<OrganizationTreeActionType>>(null); | ||
12 | + const loadedRef = ref<Nullable<boolean>>(false); | ||
13 | + let stopWatch: WatchStopHandle; | ||
14 | + function register(instance: OrganizationTreeActionType) { | ||
15 | + isProdMode() && | ||
16 | + onUnmounted(() => { | ||
17 | + organizationTreeRef.value = null; | ||
18 | + loadedRef.value = null; | ||
19 | + }); | ||
20 | + | ||
21 | + if (unref(loadedRef) && isProdMode() && instance === unref(organizationTreeRef)) return; | ||
22 | + | ||
23 | + organizationTreeRef.value = instance; | ||
24 | + loadedRef.value = true; | ||
25 | + | ||
26 | + stopWatch?.(); | ||
27 | + | ||
28 | + stopWatch = watch( | ||
29 | + () => organizationTreeProps, | ||
30 | + () => { | ||
31 | + organizationTreeProps && instance.setProps(getDynamicProps(organizationTreeProps)); | ||
32 | + }, | ||
33 | + { | ||
34 | + immediate: true, | ||
35 | + deep: true, | ||
36 | + } | ||
37 | + ); | ||
38 | + } | ||
39 | + | ||
40 | + function getOrganizationTreeInstance(): OrganizationTreeActionType { | ||
41 | + const organizationTree = unref(organizationTreeRef); | ||
42 | + if (!organizationTree) { | ||
43 | + error( | ||
44 | + 'The organization tree instance has not been obtained yet, please make sure the table is presented when performing the table operation!' | ||
45 | + ); | ||
46 | + } | ||
47 | + return organizationTree as OrganizationTreeActionType; | ||
48 | + } | ||
49 | + | ||
50 | + const organizationTreeActionType: OrganizationTreeActionType = { | ||
51 | + getSelectKey: () => getOrganizationTreeInstance().getSelectKey(), | ||
52 | + clearSelected: () => getOrganizationTreeInstance().clearSelected(), | ||
53 | + setProps: (props: Partial<OrganizationTreePropsType>) => | ||
54 | + getOrganizationTreeInstance().setProps(props), | ||
55 | + }; | ||
56 | + | ||
57 | + return [register, organizationTreeActionType]; | ||
58 | +} |
1 | import { useResetOrganizationTree } from './hooks/useOrganization'; | 1 | import { useResetOrganizationTree } from './hooks/useOrganization'; |
2 | import OrganizationIdTree from './src/OrganizationIdTree.vue'; | 2 | import OrganizationIdTree from './src/OrganizationIdTree.vue'; |
3 | +export { useOrganizationTree } from './hooks/useOrganizationTree'; | ||
3 | 4 | ||
4 | export { OrganizationIdTree, useResetOrganizationTree }; | 5 | export { OrganizationIdTree, useResetOrganizationTree }; |
@@ -17,41 +17,41 @@ | @@ -17,41 +17,41 @@ | ||
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
19 | <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> | 19 | <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> |
20 | - <BasicTree | ||
21 | - title="组织列表" | ||
22 | - toolbar | ||
23 | - search | ||
24 | - :clickRowToExpand="false" | ||
25 | - :treeData="treeData" | ||
26 | - :expandedKeys="treeExpandData" | ||
27 | - :replaceFields="{ key: 'id', title: 'name' }" | ||
28 | - :selectedKeys="selectedKeys" | ||
29 | - @select="handleSelect" | ||
30 | - v-bind="$attrs" | ||
31 | - /> | 20 | + <BasicTree v-bind="getBindData" /> |
32 | </div> | 21 | </div> |
33 | </div> | 22 | </div> |
34 | </template> | 23 | </template> |
35 | <script lang="ts" setup name="OrganizationIdTree"> | 24 | <script lang="ts" setup name="OrganizationIdTree"> |
36 | - import { onMounted, ref, unref } from 'vue'; | 25 | + import { computed, onMounted, ref, unref, useAttrs } from 'vue'; |
37 | import { BasicTree, TreeItem } from '/@/components/Tree'; | 26 | import { BasicTree, TreeItem } from '/@/components/Tree'; |
38 | import { getOrganizationList } from '/@/api/system/system'; | 27 | import { getOrganizationList } from '/@/api/system/system'; |
39 | import { CaretRightOutlined } from '@ant-design/icons-vue'; | 28 | import { CaretRightOutlined } from '@ant-design/icons-vue'; |
40 | import { getBoundingClientRect } from '/@/utils/domUtils'; | 29 | import { getBoundingClientRect } from '/@/utils/domUtils'; |
30 | + import { BasicTreePropsType, OrganizationTreeActionType } from './types'; | ||
31 | + import { OrganizationTreePropsType } from './props'; | ||
32 | + | ||
33 | + const props = defineProps<OrganizationTreePropsType>(); | ||
34 | + | ||
35 | + const attrs = useAttrs(); | ||
41 | 36 | ||
42 | const tree = ref<Nullable<HTMLDivElement>>(); | 37 | const tree = ref<Nullable<HTMLDivElement>>(); |
43 | - const emit = defineEmits(['select']); | 38 | + const emit = defineEmits(['select', 'register']); |
44 | const treeData = ref<TreeItem[]>([]); | 39 | const treeData = ref<TreeItem[]>([]); |
45 | const selectedKeys = ref<string[]>(); | 40 | const selectedKeys = ref<string[]>(); |
46 | - const treeExpandData = ref([]); | 41 | + const treeExpandData = ref<string[]>([]); |
42 | + | ||
43 | + const innerProps = ref<OrganizationTreePropsType>({}); | ||
44 | + | ||
47 | //获取所有父级id | 45 | //获取所有父级id |
48 | - function findForAllId(data = [], arr = []) { | 46 | + function findForAllId(data: Recordable[] = [], arr: string[] = []) { |
49 | for (const item of data) { | 47 | for (const item of data) { |
50 | arr.push(item.id); | 48 | arr.push(item.id); |
51 | } | 49 | } |
52 | return arr; | 50 | return arr; |
53 | } | 51 | } |
54 | - function handleSelect(keys) { | 52 | + |
53 | + function handleSelect(keys: string[]) { | ||
54 | + selectedKeys.value = keys; | ||
55 | emit('select', keys[0]); | 55 | emit('select', keys[0]); |
56 | } | 56 | } |
57 | function resetOrganization() { | 57 | function resetOrganization() { |
@@ -81,9 +81,41 @@ | @@ -81,9 +81,41 @@ | ||
81 | 81 | ||
82 | setTreeHeight(); | 82 | setTreeHeight(); |
83 | }); | 83 | }); |
84 | + | ||
84 | defineExpose({ | 85 | defineExpose({ |
85 | resetOrganization, | 86 | resetOrganization, |
86 | }); | 87 | }); |
88 | + | ||
89 | + const getProps = computed<OrganizationTreePropsType>(() => ({ ...props, ...unref(innerProps) })); | ||
90 | + | ||
91 | + const getBindData = computed(() => { | ||
92 | + return { | ||
93 | + title: '组织列表', | ||
94 | + toolbar: true, | ||
95 | + search: true, | ||
96 | + clickRowToExpand: false, | ||
97 | + treeData: unref(treeData), | ||
98 | + expandedKeys: unref(treeExpandData), | ||
99 | + replaceFields: { key: 'id', title: 'name' }, | ||
100 | + selectedKeys: unref(selectedKeys), | ||
101 | + ...attrs, | ||
102 | + ...unref(getProps), | ||
103 | + onSelect: (keys: string[]) => { | ||
104 | + handleSelect(keys); | ||
105 | + unref(getProps)?.onSelect?.(keys?.[0]); | ||
106 | + }, | ||
107 | + } as BasicTreePropsType; | ||
108 | + }); | ||
109 | + | ||
110 | + const setProps = (props: Partial<OrganizationTreePropsType>) => { | ||
111 | + innerProps.value = { ...(props || {}) }; | ||
112 | + }; | ||
113 | + | ||
114 | + emit('register', { | ||
115 | + clearSelected: resetOrganization, | ||
116 | + getSelectKey: () => unref(selectedKeys)?.[0], | ||
117 | + setProps, | ||
118 | + } as OrganizationTreeActionType); | ||
87 | </script> | 119 | </script> |
88 | 120 | ||
89 | <style scoped lang="less"> | 121 | <style scoped lang="less"> |
1 | +import { ExtractPropTypes } from 'vue'; | ||
2 | +import { basicProps } from '/@/components/Tree/src/props'; | ||
3 | +import { OrganizationTreePropsType } from './props'; | ||
4 | + | ||
5 | +export interface OrganizationTreeActionType { | ||
6 | + clearSelected(): void; | ||
7 | + getSelectKey(): undefined | string; | ||
8 | + setProps(props: Partial<OrganizationTreePropsType>): any; | ||
9 | +} | ||
10 | + | ||
11 | +export type BasicTreePropsType = Partial<ExtractPropTypes<typeof basicProps>>; |
1 | import type { ComputedRef, Ref } from 'vue'; | 1 | import type { ComputedRef, Ref } from 'vue'; |
2 | -import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
3 | 2 | ||
4 | -export type DynamicProps<T> = { | ||
5 | - [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>; | ||
6 | -}; | 3 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; |
7 | 4 | ||
8 | export interface ModalParamsType<T = Recordable> { | 5 | export interface ModalParamsType<T = Recordable> { |
9 | mode: DataActionModeEnum; | 6 | mode: DataActionModeEnum; |
@@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { | @@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { | ||
11 | [key: string]: any; | 8 | [key: string]: any; |
12 | } | 9 | } |
13 | 10 | ||
11 | +export type DynamicProps<T> = { | ||
12 | + [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>; | ||
13 | +}; | ||
14 | + | ||
14 | export interface DefineComponentsBasicExpose<T = Recordable> { | 15 | export interface DefineComponentsBasicExpose<T = Recordable> { |
15 | getFieldsValue: () => T; | 16 | getFieldsValue: () => T; |
16 | setFieldsValue: (value: T) => any; | 17 | setFieldsValue: (value: T) => any; |
17 | validate?: () => Promise<any>; | 18 | validate?: () => Promise<any>; |
18 | resetFieldsValue?: (...args) => any; | 19 | resetFieldsValue?: (...args) => any; |
19 | } | 20 | } |
21 | + | ||
22 | +declare global { | ||
23 | + interface CardListRenderItem<T = Recordable> { | ||
24 | + item: T; | ||
25 | + totalHeight: number; | ||
26 | + } | ||
27 | + | ||
28 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
29 | + | ||
30 | + interface ModalParamsType<T = Recordable> { | ||
31 | + mode: DataActionModeEnum; | ||
32 | + record: T; | ||
33 | + [key: string]: any; | ||
34 | + } | ||
35 | +} | ||
36 | + | ||
37 | +export {}; |