vxe-table.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import type { Recordable } from '@vben/types';
  2. import { h } from 'vue';
  3. import { IconifyIcon } from '@vben/icons';
  4. import { $te } from '@vben/locales';
  5. import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
  6. import { get, isFunction, isString } from '@vben/utils';
  7. import { objectOmit } from '@vueuse/core';
  8. import { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue';
  9. import { $t } from '#/locales';
  10. import { useVbenForm } from './form';
  11. setupVbenVxeTable({
  12. configVxeTable: (vxeUI) => {
  13. vxeUI.setConfig({
  14. grid: {
  15. align: 'center',
  16. border: false,
  17. columnConfig: {
  18. resizable: true,
  19. },
  20. formConfig: {
  21. // 全局禁用vxe-table的表单配置,使用formOptions
  22. enabled: false,
  23. },
  24. minHeight: 180,
  25. proxyConfig: {
  26. autoLoad: true,
  27. response: {
  28. result: 'items',
  29. total: 'total',
  30. list: '',
  31. },
  32. showActiveMsg: true,
  33. showResponseMsg: false,
  34. },
  35. round: true,
  36. showOverflow: true,
  37. size: 'small',
  38. },
  39. });
  40. /**
  41. * 解决vxeTable在热更新时可能会出错的问题
  42. */
  43. vxeUI.renderer.forEach((_item, key) => {
  44. if (key.startsWith('Cell')) {
  45. vxeUI.renderer.delete(key);
  46. }
  47. });
  48. // 表格配置项可以用 cellRender: { name: 'CellImage' },
  49. vxeUI.renderer.add('CellImage', {
  50. renderTableDefault(_renderOpts, params) {
  51. const { column, row } = params;
  52. return h(Image, { src: row[column.field] });
  53. },
  54. });
  55. // 表格配置项可以用 cellRender: { name: 'CellLink' },
  56. vxeUI.renderer.add('CellLink', {
  57. renderTableDefault(renderOpts) {
  58. const { props } = renderOpts;
  59. return h(
  60. Button,
  61. { size: 'small', type: 'link' },
  62. { default: () => props?.text },
  63. );
  64. },
  65. });
  66. // 单元格渲染: Tag
  67. vxeUI.renderer.add('CellTag', {
  68. renderTableDefault({ options, props }, { column, row }) {
  69. const value = get(row, column.field);
  70. const tagOptions = options ?? [
  71. { color: 'success', label: $t('common.enabled'), value: 1 },
  72. { color: 'error', label: $t('common.disabled'), value: 0 },
  73. ];
  74. const tagItem = tagOptions.find((item) => item.value === value);
  75. return h(
  76. Tag,
  77. {
  78. ...props,
  79. ...objectOmit(tagItem ?? {}, ['label']),
  80. },
  81. { default: () => tagItem?.label ?? value },
  82. );
  83. },
  84. });
  85. vxeUI.renderer.add('CellSwitch', {
  86. renderTableDefault({ attrs, props }, { column, row }) {
  87. const loadingKey = `__loading_${column.field}`;
  88. const finallyProps = {
  89. checkedChildren: $t('common.enabled'),
  90. checkedValue: 1,
  91. unCheckedChildren: $t('common.disabled'),
  92. unCheckedValue: 0,
  93. ...props,
  94. checked: row[column.field],
  95. loading: row[loadingKey] ?? false,
  96. 'onUpdate:checked': onChange,
  97. };
  98. async function onChange(newVal: any) {
  99. row[loadingKey] = true;
  100. try {
  101. const result = await attrs?.beforeChange?.(newVal, row);
  102. if (result !== false) {
  103. row[column.field] = newVal;
  104. }
  105. } finally {
  106. row[loadingKey] = false;
  107. }
  108. }
  109. return h(Switch, finallyProps);
  110. },
  111. });
  112. /**
  113. * 注册表格的操作按钮渲染器
  114. */
  115. vxeUI.renderer.add('CellOperation', {
  116. renderTableDefault({ attrs, options, props }, { column, row }) {
  117. const defaultProps = { size: 'small', type: 'link', ...props };
  118. let align = 'end';
  119. switch (column.align) {
  120. case 'center': {
  121. align = 'center';
  122. break;
  123. }
  124. case 'left': {
  125. align = 'start';
  126. break;
  127. }
  128. default: {
  129. align = 'end';
  130. break;
  131. }
  132. }
  133. const presets: Recordable<Recordable<any>> = {
  134. delete: {
  135. danger: true,
  136. text: $t('common.delete'),
  137. },
  138. edit: {
  139. text: $t('common.edit'),
  140. },
  141. };
  142. const operations: Array<Recordable<any>> = (
  143. options || ['edit', 'delete']
  144. )
  145. .map((opt) => {
  146. if (isString(opt)) {
  147. return presets[opt]
  148. ? { code: opt, ...presets[opt], ...defaultProps }
  149. : {
  150. code: opt,
  151. text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt,
  152. ...defaultProps,
  153. };
  154. } else {
  155. return { ...defaultProps, ...presets[opt.code], ...opt };
  156. }
  157. })
  158. .map((opt) => {
  159. const optBtn: Recordable<any> = {};
  160. Object.keys(opt).forEach((key) => {
  161. optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key];
  162. });
  163. return optBtn;
  164. })
  165. .filter((opt) => opt.show !== false);
  166. function renderBtn(opt: Recordable<any>, listen = true) {
  167. return h(
  168. Button,
  169. {
  170. ...props,
  171. ...opt,
  172. icon: undefined,
  173. onClick: listen
  174. ? () =>
  175. attrs?.onClick?.({
  176. code: opt.code,
  177. row,
  178. })
  179. : undefined,
  180. },
  181. {
  182. default: () => {
  183. const content = [];
  184. if (opt.icon) {
  185. content.push(
  186. h(IconifyIcon, { class: 'size-5', icon: opt.icon }),
  187. );
  188. }
  189. content.push(opt.text);
  190. return content;
  191. },
  192. },
  193. );
  194. }
  195. function renderConfirm(opt: Recordable<any>) {
  196. return h(
  197. Popconfirm,
  198. {
  199. getPopupContainer(el) {
  200. return el.closest('tbody') || document.body;
  201. },
  202. placement: 'topLeft',
  203. title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
  204. ...props,
  205. ...opt,
  206. icon: undefined,
  207. onConfirm: () => {
  208. attrs?.onClick?.({
  209. code: opt.code,
  210. row,
  211. });
  212. },
  213. },
  214. {
  215. default: () => renderBtn({ ...opt }, false),
  216. description: () =>
  217. h(
  218. 'div',
  219. { class: 'truncate' },
  220. $t('ui.actionMessage.deleteConfirm', [
  221. row[attrs?.nameField || 'name'],
  222. ]),
  223. ),
  224. },
  225. );
  226. }
  227. const btns = operations.map((opt) =>
  228. opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt),
  229. );
  230. return h(
  231. 'div',
  232. {
  233. class: 'flex table-operations',
  234. style: { justifyContent: align },
  235. },
  236. btns,
  237. );
  238. },
  239. });
  240. // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
  241. // vxeUI.formats.add
  242. },
  243. useVbenForm,
  244. });
  245. export { useVbenVxeGrid };
  246. export type OnActionClickParams<T = Recordable<any>> = {
  247. code: string;
  248. row: T;
  249. };
  250. export type OnActionClickFn<T = Recordable<any>> = (
  251. params: OnActionClickParams<T>,
  252. ) => void;
  253. export type * from '@vben/plugins/vxe-table';