use-vxe-grid.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <script lang="ts" setup>
  2. import type { VbenFormProps } from '@vben-core/form-ui';
  3. import type {
  4. VxeGridInstance,
  5. VxeGridProps as VxeTableGridProps,
  6. } from 'vxe-table';
  7. import type { ExtendedVxeGridApi, VxeGridProps } from './types';
  8. import {
  9. computed,
  10. nextTick,
  11. onMounted,
  12. toRaw,
  13. useSlots,
  14. useTemplateRef,
  15. watch,
  16. } from 'vue';
  17. import { usePriorityValues } from '@vben/hooks';
  18. import { EmptyIcon } from '@vben/icons';
  19. import { $t } from '@vben/locales';
  20. import { usePreferences } from '@vben/preferences';
  21. import { cloneDeep, cn, mergeWithArrayOverride } from '@vben/utils';
  22. import { VbenLoading } from '@vben-core/shadcn-ui';
  23. import { VxeGrid, VxeUI } from 'vxe-table';
  24. import { useTableForm } from './init';
  25. import 'vxe-table/styles/cssvar.scss';
  26. import 'vxe-pc-ui/styles/cssvar.scss';
  27. import './theme.css';
  28. interface Props extends VxeGridProps {
  29. api: ExtendedVxeGridApi;
  30. }
  31. const props = withDefaults(defineProps<Props>(), {});
  32. const gridRef = useTemplateRef<VxeGridInstance>('gridRef');
  33. const state = props.api?.useStore?.();
  34. const {
  35. gridOptions,
  36. class: className,
  37. gridClass,
  38. gridEvents,
  39. formOptions,
  40. } = usePriorityValues(props, state);
  41. const { isMobile } = usePreferences();
  42. const slots = useSlots();
  43. const [Form, formApi] = useTableForm({
  44. handleSubmit: async () => {
  45. const formValues = formApi.form.values;
  46. props.api.reload(formValues);
  47. },
  48. handleReset: async () => {
  49. await formApi.resetForm();
  50. const formValues = formApi.form.values;
  51. props.api.reload(formValues);
  52. },
  53. commonConfig: {
  54. componentProps: {
  55. class: 'w-full',
  56. },
  57. },
  58. showCollapseButton: true,
  59. submitButtonOptions: {
  60. content: $t('common.query'),
  61. },
  62. wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
  63. });
  64. const showToolbar = computed(() => {
  65. return !!slots['toolbar-actions']?.() || !!slots['toolbar-tools']?.();
  66. });
  67. const options = computed(() => {
  68. const slotActions = slots['toolbar-actions']?.();
  69. const slotTools = slots['toolbar-tools']?.();
  70. const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
  71. const forceUseToolbarOptions = showToolbar.value
  72. ? {
  73. toolbarConfig: {
  74. slots: {
  75. ...(slotActions ? { buttons: 'toolbar-actions' } : {}),
  76. ...(slotTools ? { tools: 'toolbar-tools' } : {}),
  77. },
  78. },
  79. }
  80. : {};
  81. const mergedOptions: VxeTableGridProps = cloneDeep(
  82. mergeWithArrayOverride(
  83. {},
  84. forceUseToolbarOptions,
  85. toRaw(gridOptions.value),
  86. globalGridConfig,
  87. ),
  88. );
  89. if (mergedOptions.proxyConfig) {
  90. const { ajax } = mergedOptions.proxyConfig;
  91. mergedOptions.proxyConfig.enabled = !!ajax;
  92. // 不自动加载数据, 由组件控制
  93. mergedOptions.proxyConfig.autoLoad = false;
  94. }
  95. if (!showToolbar.value && mergedOptions.toolbarConfig) {
  96. mergedOptions.toolbarConfig.enabled = false;
  97. }
  98. if (mergedOptions.pagerConfig) {
  99. const mobileLayouts = [
  100. 'PrevJump',
  101. 'PrevPage',
  102. 'Number',
  103. 'NextPage',
  104. 'NextJump',
  105. ] as any;
  106. const layouts = [
  107. 'Total',
  108. 'Sizes',
  109. 'Home',
  110. ...mobileLayouts,
  111. 'End',
  112. ] as readonly string[];
  113. mergedOptions.pagerConfig = mergeWithArrayOverride(
  114. {},
  115. mergedOptions.pagerConfig,
  116. {
  117. pageSize: 20,
  118. background: true,
  119. pageSizes: [10, 20, 30, 50, 100, 200],
  120. className: 'mt-2 w-full',
  121. layouts: isMobile.value ? mobileLayouts : layouts,
  122. size: 'mini' as const,
  123. },
  124. );
  125. }
  126. if (mergedOptions.formConfig) {
  127. mergedOptions.formConfig.enabled = false;
  128. }
  129. return mergedOptions;
  130. });
  131. const events = computed(() => {
  132. return {
  133. ...gridEvents.value,
  134. };
  135. });
  136. const delegatedSlots = computed(() => {
  137. const resultSlots: string[] = [];
  138. for (const key of Object.keys(slots)) {
  139. if (!['empty', 'form', 'loading'].includes(key)) {
  140. resultSlots.push(key);
  141. }
  142. }
  143. return resultSlots;
  144. });
  145. const delegatedFormSlots = computed(() => {
  146. const resultSlots: string[] = [];
  147. for (const key of Object.keys(slots)) {
  148. if (key.startsWith('form-')) {
  149. resultSlots.push(key);
  150. }
  151. }
  152. return resultSlots;
  153. });
  154. async function init() {
  155. await nextTick();
  156. const globalGridConfig = VxeUI?.getConfig()?.grid ?? {};
  157. const defaultGridOptions: VxeTableGridProps = mergeWithArrayOverride(
  158. {},
  159. toRaw(gridOptions.value),
  160. toRaw(globalGridConfig),
  161. );
  162. // 内部主动加载数据,防止form的默认值影响
  163. const autoLoad = defaultGridOptions.proxyConfig?.autoLoad;
  164. const enableProxyConfig = options.value.proxyConfig?.enabled;
  165. if (enableProxyConfig && autoLoad) {
  166. props.api.reload(formApi.form.values);
  167. }
  168. // form 由 vben-form代替,所以不适配formConfig,这里给出警告
  169. const formConfig = gridOptions.value?.formConfig;
  170. if (formConfig) {
  171. console.warn(
  172. '[Vben Vxe Table]: The formConfig in the grid is not supported, please use the `formOptions` props',
  173. );
  174. }
  175. }
  176. watch(
  177. formOptions,
  178. () => {
  179. formApi.setState((prev) => {
  180. const finalFormOptions: VbenFormProps = mergeWithArrayOverride(
  181. {},
  182. formOptions.value,
  183. prev,
  184. );
  185. return {
  186. ...finalFormOptions,
  187. collapseTriggerResize: !!finalFormOptions.showCollapseButton,
  188. };
  189. });
  190. },
  191. {
  192. immediate: true,
  193. },
  194. );
  195. onMounted(() => {
  196. props.api?.mount?.(gridRef.value, formApi);
  197. init();
  198. });
  199. </script>
  200. <template>
  201. <div :class="cn('bg-card h-full rounded-md', className)">
  202. <VxeGrid
  203. ref="gridRef"
  204. :class="
  205. cn(
  206. 'p-2',
  207. {
  208. 'pt-0': showToolbar && !formOptions,
  209. },
  210. gridClass,
  211. )
  212. "
  213. v-bind="options"
  214. v-on="events"
  215. >
  216. <template
  217. v-for="slotName in delegatedSlots"
  218. :key="slotName"
  219. #[slotName]="slotProps"
  220. >
  221. <slot :name="slotName" v-bind="slotProps"></slot>
  222. </template>
  223. <template #form>
  224. <div v-if="formOptions" class="relative rounded py-3 pb-4">
  225. <slot name="form">
  226. <Form>
  227. <template
  228. v-for="slotName in delegatedFormSlots"
  229. :key="slotName"
  230. #[slotName]="slotProps"
  231. >
  232. <slot :name="slotName" v-bind="slotProps"></slot>
  233. </template>
  234. </Form>
  235. </slot>
  236. <div
  237. class="bg-background-deep z-100 absolute -left-2 bottom-1 h-2 w-[calc(100%+1rem)] overflow-hidden md:bottom-2 md:h-3"
  238. ></div>
  239. </div>
  240. </template>
  241. <template #loading>
  242. <slot name="loading">
  243. <VbenLoading :spinning="true" />
  244. </slot>
  245. </template>
  246. <template #empty>
  247. <slot name="empty">
  248. <EmptyIcon class="mx-auto" />
  249. <div class="mt-2">{{ $t('common.noData') }}</div>
  250. </slot>
  251. </template>
  252. </VxeGrid>
  253. </div>
  254. </template>