index.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /**
  2. * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
  3. * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
  4. */
  5. import type { Component } from 'vue';
  6. import type { BaseFormComponentType } from '@vben/common-ui';
  7. import type { Recordable } from '@vben/types';
  8. import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
  9. import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
  10. import { $t } from '@vben/locales';
  11. import { ElNotification } from 'element-plus';
  12. const ElButton = defineAsyncComponent(() =>
  13. Promise.all([
  14. import('element-plus/es/components/button/index'),
  15. import('element-plus/es/components/button/style/css'),
  16. ]).then(([res]) => res.ElButton),
  17. );
  18. const ElCheckbox = defineAsyncComponent(() =>
  19. Promise.all([
  20. import('element-plus/es/components/checkbox/index'),
  21. import('element-plus/es/components/checkbox/style/css'),
  22. ]).then(([res]) => res.ElCheckbox),
  23. );
  24. const ElCheckboxButton = defineAsyncComponent(() =>
  25. Promise.all([
  26. import('element-plus/es/components/checkbox/index'),
  27. import('element-plus/es/components/checkbox-button/style/css'),
  28. ]).then(([res]) => res.ElCheckboxButton),
  29. );
  30. const ElCheckboxGroup = defineAsyncComponent(() =>
  31. Promise.all([
  32. import('element-plus/es/components/checkbox/index'),
  33. import('element-plus/es/components/checkbox-group/style/css'),
  34. ]).then(([res]) => res.ElCheckboxGroup),
  35. );
  36. const ElDatePicker = defineAsyncComponent(() =>
  37. Promise.all([
  38. import('element-plus/es/components/date-picker/index'),
  39. import('element-plus/es/components/date-picker/style/css'),
  40. ]).then(([res]) => res.ElDatePicker),
  41. );
  42. const ElDivider = defineAsyncComponent(() =>
  43. Promise.all([
  44. import('element-plus/es/components/divider/index'),
  45. import('element-plus/es/components/divider/style/css'),
  46. ]).then(([res]) => res.ElDivider),
  47. );
  48. const ElInput = defineAsyncComponent(() =>
  49. Promise.all([
  50. import('element-plus/es/components/input/index'),
  51. import('element-plus/es/components/input/style/css'),
  52. ]).then(([res]) => res.ElInput),
  53. );
  54. const ElInputNumber = defineAsyncComponent(() =>
  55. Promise.all([
  56. import('element-plus/es/components/input-number/index'),
  57. import('element-plus/es/components/input-number/style/css'),
  58. ]).then(([res]) => res.ElInputNumber),
  59. );
  60. const ElRadio = defineAsyncComponent(() =>
  61. Promise.all([
  62. import('element-plus/es/components/radio/index'),
  63. import('element-plus/es/components/radio/style/css'),
  64. ]).then(([res]) => res.ElRadio),
  65. );
  66. const ElRadioButton = defineAsyncComponent(() =>
  67. Promise.all([
  68. import('element-plus/es/components/radio/index'),
  69. import('element-plus/es/components/radio-button/style/css'),
  70. ]).then(([res]) => res.ElRadioButton),
  71. );
  72. const ElRadioGroup = defineAsyncComponent(() =>
  73. Promise.all([
  74. import('element-plus/es/components/radio/index'),
  75. import('element-plus/es/components/radio-group/style/css'),
  76. ]).then(([res]) => res.ElRadioGroup),
  77. );
  78. const ElSelectV2 = defineAsyncComponent(() =>
  79. Promise.all([
  80. import('element-plus/es/components/select-v2/index'),
  81. import('element-plus/es/components/select-v2/style/css'),
  82. ]).then(([res]) => res.ElSelectV2),
  83. );
  84. const ElSpace = defineAsyncComponent(() =>
  85. Promise.all([
  86. import('element-plus/es/components/space/index'),
  87. import('element-plus/es/components/space/style/css'),
  88. ]).then(([res]) => res.ElSpace),
  89. );
  90. const ElSwitch = defineAsyncComponent(() =>
  91. Promise.all([
  92. import('element-plus/es/components/switch/index'),
  93. import('element-plus/es/components/switch/style/css'),
  94. ]).then(([res]) => res.ElSwitch),
  95. );
  96. const ElTimePicker = defineAsyncComponent(() =>
  97. Promise.all([
  98. import('element-plus/es/components/time-picker/index'),
  99. import('element-plus/es/components/time-picker/style/css'),
  100. ]).then(([res]) => res.ElTimePicker),
  101. );
  102. const ElTreeSelect = defineAsyncComponent(() =>
  103. Promise.all([
  104. import('element-plus/es/components/tree-select/index'),
  105. import('element-plus/es/components/tree-select/style/css'),
  106. ]).then(([res]) => res.ElTreeSelect),
  107. );
  108. const ElUpload = defineAsyncComponent(() =>
  109. Promise.all([
  110. import('element-plus/es/components/upload/index'),
  111. import('element-plus/es/components/upload/style/css'),
  112. ]).then(([res]) => res.ElUpload),
  113. );
  114. const withDefaultPlaceholder = <T extends Component>(
  115. component: T,
  116. type: 'input' | 'select',
  117. componentProps: Recordable<any> = {},
  118. ) => {
  119. return defineComponent({
  120. name: component.name,
  121. inheritAttrs: false,
  122. setup: (props: any, { attrs, expose, slots }) => {
  123. const placeholder =
  124. props?.placeholder ||
  125. attrs?.placeholder ||
  126. $t(`ui.placeholder.${type}`);
  127. // 透传组件暴露的方法
  128. const innerRef = ref();
  129. expose(
  130. new Proxy(
  131. {},
  132. {
  133. get: (_target, key) => innerRef.value?.[key],
  134. has: (_target, key) => key in (innerRef.value || {}),
  135. },
  136. ),
  137. );
  138. return () =>
  139. h(
  140. component,
  141. { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
  142. slots,
  143. );
  144. },
  145. });
  146. };
  147. // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
  148. export type ComponentType =
  149. | 'ApiSelect'
  150. | 'ApiTreeSelect'
  151. | 'Checkbox'
  152. | 'CheckboxGroup'
  153. | 'DatePicker'
  154. | 'Divider'
  155. | 'IconPicker'
  156. | 'Input'
  157. | 'InputNumber'
  158. | 'RadioGroup'
  159. | 'Select'
  160. | 'Space'
  161. | 'Switch'
  162. | 'TimePicker'
  163. | 'TreeSelect'
  164. | 'Upload'
  165. | BaseFormComponentType;
  166. async function initComponentAdapter() {
  167. const components: Partial<Record<ComponentType, Component>> = {
  168. // 如果你的组件体积比较大,可以使用异步加载
  169. // Button: () =>
  170. // import('xxx').then((res) => res.Button),
  171. ApiSelect: withDefaultPlaceholder(
  172. {
  173. ...ApiComponent,
  174. name: 'ApiSelect',
  175. },
  176. 'select',
  177. {
  178. component: ElSelectV2,
  179. loadingSlot: 'loading',
  180. visibleEvent: 'onVisibleChange',
  181. },
  182. ),
  183. ApiTreeSelect: withDefaultPlaceholder(
  184. {
  185. ...ApiComponent,
  186. name: 'ApiTreeSelect',
  187. },
  188. 'select',
  189. {
  190. component: ElTreeSelect,
  191. props: { label: 'label', children: 'children' },
  192. nodeKey: 'value',
  193. loadingSlot: 'loading',
  194. optionsPropName: 'data',
  195. visibleEvent: 'onVisibleChange',
  196. },
  197. ),
  198. Checkbox: ElCheckbox,
  199. CheckboxGroup: (props, { attrs, slots }) => {
  200. let defaultSlot;
  201. if (Reflect.has(slots, 'default')) {
  202. defaultSlot = slots.default;
  203. } else {
  204. const { options, isButton } = attrs;
  205. if (Array.isArray(options)) {
  206. defaultSlot = () =>
  207. options.map((option) =>
  208. h(isButton ? ElCheckboxButton : ElCheckbox, option),
  209. );
  210. }
  211. }
  212. return h(
  213. ElCheckboxGroup,
  214. { ...props, ...attrs },
  215. { ...slots, default: defaultSlot },
  216. );
  217. },
  218. // 自定义默认按钮
  219. DefaultButton: (props, { attrs, slots }) => {
  220. return h(ElButton, { ...props, attrs, type: 'info' }, slots);
  221. },
  222. // 自定义主要按钮
  223. PrimaryButton: (props, { attrs, slots }) => {
  224. return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
  225. },
  226. Divider: ElDivider,
  227. IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
  228. iconSlot: 'append',
  229. modelValueProp: 'model-value',
  230. inputComponent: ElInput,
  231. }),
  232. Input: withDefaultPlaceholder(ElInput, 'input'),
  233. InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
  234. RadioGroup: (props, { attrs, slots }) => {
  235. let defaultSlot;
  236. if (Reflect.has(slots, 'default')) {
  237. defaultSlot = slots.default;
  238. } else {
  239. const { options } = attrs;
  240. if (Array.isArray(options)) {
  241. defaultSlot = () =>
  242. options.map((option) =>
  243. h(attrs.isButton ? ElRadioButton : ElRadio, option),
  244. );
  245. }
  246. }
  247. return h(
  248. ElRadioGroup,
  249. { ...props, ...attrs },
  250. { ...slots, default: defaultSlot },
  251. );
  252. },
  253. Select: (props, { attrs, slots }) => {
  254. return h(ElSelectV2, { ...props, attrs }, slots);
  255. },
  256. Space: ElSpace,
  257. Switch: ElSwitch,
  258. TimePicker: (props, { attrs, slots }) => {
  259. const { name, id, isRange } = props;
  260. const extraProps: Recordable<any> = {};
  261. if (isRange) {
  262. if (name && !Array.isArray(name)) {
  263. extraProps.name = [name, `${name}_end`];
  264. }
  265. if (id && !Array.isArray(id)) {
  266. extraProps.id = [id, `${id}_end`];
  267. }
  268. }
  269. return h(
  270. ElTimePicker,
  271. {
  272. ...props,
  273. ...attrs,
  274. ...extraProps,
  275. },
  276. slots,
  277. );
  278. },
  279. DatePicker: (props, { attrs, slots }) => {
  280. const { name, id, type } = props;
  281. const extraProps: Recordable<any> = {};
  282. if (type && type.includes('range')) {
  283. if (name && !Array.isArray(name)) {
  284. extraProps.name = [name, `${name}_end`];
  285. }
  286. if (id && !Array.isArray(id)) {
  287. extraProps.id = [id, `${id}_end`];
  288. }
  289. }
  290. return h(
  291. ElDatePicker,
  292. {
  293. ...props,
  294. ...attrs,
  295. ...extraProps,
  296. },
  297. slots,
  298. );
  299. },
  300. TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
  301. Upload: ElUpload,
  302. };
  303. // 将组件注册到全局共享状态中
  304. globalShareState.setComponents(components);
  305. // 定义全局共享状态中的消息提示
  306. globalShareState.defineMessage({
  307. // 复制成功消息提示
  308. copyPreferencesSuccess: (title, content) => {
  309. ElNotification({
  310. title,
  311. message: content,
  312. position: 'bottom-right',
  313. duration: 0,
  314. type: 'success',
  315. });
  316. },
  317. });
  318. }
  319. export { initComponentAdapter };