useVxeTable.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import {computed, nextTick, reactive, Ref, ref, watch, onMounted} from 'vue'
  2. import type {VxePagerProps, VxeTableProps} from "vxe-table";
  3. import {VxePager, VxeTable, VxeColumn} from "vxe-table";
  4. import {TablePublicMethods, VxeTableEventProps} from "vxe-table/types/table";
  5. import XEUtils from "xe-utils";
  6. import {stringIsBlank, stringNotBlank} from "@/utils/blank-utils";
  7. import {TableExportMethods} from "vxe-table/types/export";
  8. import {BizException, ExceptionEnum} from "@/utils/BizException";
  9. import {eachAndReturnList} from "@/utils/cyRefList";
  10. import {ElButton, ElPopover} from "element-plus";
  11. import setDialogToJs from "@/components/js-dialog-comp/useDialogToJs";
  12. import CyDialog from "@/components/cy/dialog/src/CyDialog.vue";
  13. import {IsCyDialog} from "@/components/cy/dialog/src/useCyDialog";
  14. import CyFlex from "@/components/cy/flex/src/CyFlex.vue";
  15. declare type PageQuery = {
  16. currentPage?: number,
  17. pageSize?: number,
  18. }
  19. declare type SimplifiedConfiguration<D> = {
  20. rowHeight?: number,
  21. keyField?: string,
  22. currentKey?: string | number,
  23. remoteSearch?: (data?: PageQuery & any) => Promise<D[]>,
  24. tableProps?: VxeTableEventProps<D> & VxeTableProps<D>,
  25. showPage?: boolean,
  26. result?: string,
  27. total?: string,
  28. pagesProps?: VxePagerProps,
  29. showCheckbox?: boolean,
  30. mountedQuery?: boolean,
  31. dialogProps?: IsCyDialog,
  32. dialogHeader?: () => any
  33. }
  34. function tsxVModel(modelValue: object, name: string, modelName?: string) {
  35. const updateName = modelName || name
  36. return {
  37. // @ts-ignore
  38. [updateName]: modelValue[name],
  39. ['onUpdate:' + updateName]: (el: any) => {
  40. // @ts-ignore
  41. modelValue[name] = el
  42. }
  43. }
  44. }
  45. function useVxeTable<D = any>(simplifiedConfiguration: SimplifiedConfiguration<D>) {
  46. const tableRef: Ref<TablePublicMethods<D> & TableExportMethods<D> | undefined> = ref()
  47. const props: VxeTableProps<D> & VxeTableEventProps<D> & SimplifiedConfiguration<D> = reactive({
  48. height: '100%',
  49. rowConfig: {
  50. isHover: true,
  51. isCurrent: true,
  52. height: simplifiedConfiguration?.rowHeight || 48,
  53. useKey: true,
  54. keyField: simplifiedConfiguration?.keyField || '',
  55. },
  56. scrollY: {
  57. enabled: true,
  58. gt: 0
  59. },
  60. columnConfig: {
  61. resizable: true
  62. },
  63. scrollX: {
  64. enabled: false,
  65. },
  66. exportConfig: {},
  67. showOverflow: true,
  68. currentKey: '',
  69. menuConfig: {
  70. enabled: true
  71. },
  72. loading: false,
  73. loadingConfig: {
  74. text: '加载中...',
  75. icon: '',
  76. },
  77. checkboxConfig: {
  78. reserve: true,
  79. highlight: true,
  80. range: true,
  81. },
  82. data: [],
  83. })
  84. if (simplifiedConfiguration.showPage) {
  85. delete props.rowConfig?.height
  86. props.scrollY!.enabled = false
  87. props!.showOverflow = false
  88. props!.checkboxConfig!.reserve = true
  89. if (stringIsBlank(props.rowConfig?.keyField)) {
  90. BizException(ExceptionEnum.MESSAGE_ERROR, '请先设置行id')
  91. }
  92. }
  93. const pageVO = reactive({
  94. currentPage: 1,
  95. pageSize: 30,
  96. total: 0
  97. })
  98. const pagerProps: VxePagerProps = reactive({
  99. loading: false,
  100. })
  101. watch(() => props.currentKey, () => {
  102. if (stringNotBlank(defaultProps.value.rowConfig?.keyField)) {
  103. const findData = XEUtils.find(tableRef.value?.getData(), (item) => {
  104. // @ts-ignore
  105. return item[defaultProps.value.rowConfig?.keyField] === props.currentKey;
  106. })
  107. if (findData) {
  108. tableRef.value?.scrollToRow(findData)
  109. tableRef.value?.setCurrentRow(findData)
  110. }
  111. }
  112. }, {flush: 'post'})
  113. const defaultProps = computed(() => {
  114. return {
  115. ...simplifiedConfiguration?.tableProps,
  116. ...props
  117. } as VxeTableProps<D>
  118. })
  119. const CyVxeTable = (props: VxeTableProps<D>, {slots}: any) => {
  120. mutation.setTableDefaultSlots = () => {
  121. return slots.default ? slots.default() : null
  122. }
  123. const TempVxeTable = () => {
  124. return <VxeTable
  125. ref={tableRef}
  126. {...defaultProps.value}
  127. {...props}
  128. >
  129. {{
  130. default: () => {
  131. function renderIcon(checked: boolean, indeterminate: boolean) {
  132. if (indeterminate) {
  133. return <i class="vxe-icon-square-minus-fill"></i>
  134. } else if (checked) {
  135. return <i class="vxe-icon-square-checked-fill"></i>
  136. } else {
  137. return <i class="vxe-icon-checkbox-unchecked"></i>
  138. }
  139. }
  140. function checkboxHeader(checked: boolean, indeterminate: boolean) {
  141. return <ElPopover placement="right">
  142. {{
  143. default: () => {
  144. return [
  145. `当前选中${pageMemory.size}条`,
  146. <div style="text-align: right;margin-top: 5px;">
  147. <ElButton
  148. type="primary"
  149. onClick={(el) => {
  150. if (pageMemory.size > 0)
  151. handelLookCheckBox()
  152. }}
  153. >{{default: () => '详情'}}
  154. </ElButton>
  155. <ElButton
  156. type="danger"
  157. onClick={(el) => {
  158. mutation.clearCheckboxRow()
  159. }}
  160. >{{default: () => '清空'}}
  161. </ElButton>
  162. </div>
  163. ]
  164. },
  165. reference: () => {
  166. return <span
  167. style={"font-size: 1.1em;cursor: pointer;"}
  168. onClick={(el) => {
  169. el.preventDefault()
  170. el.stopPropagation()
  171. handelCheckboxAll()
  172. }}>{renderIcon(checked, indeterminate)}</span>
  173. }
  174. }}
  175. </ElPopover>
  176. }
  177. return [
  178. simplifiedConfiguration.showCheckbox ?
  179. <VxeColumn type="checkbox" width={"max-content"}>
  180. {{
  181. // @ts-ignore
  182. header({checked, indeterminate}) {
  183. return checkboxHeader(checked, indeterminate)
  184. },
  185. // @ts-ignore
  186. checkbox({row, checked, indeterminate}) {
  187. return <span style={"font-size: 1.1em;cursor: pointer;"}
  188. onClick={(el) => {
  189. el.preventDefault()
  190. el.stopPropagation()
  191. handelCheckboxChange({row, checked: !checked})
  192. }}>
  193. {renderIcon(checked, indeterminate)}
  194. </span>
  195. }
  196. }}
  197. </VxeColumn> : null,
  198. ...slots.default ? slots.default() : null,
  199. ];
  200. }
  201. }}
  202. </VxeTable>
  203. }
  204. if (simplifiedConfiguration.showPage) {
  205. return <div class="cy_display_flex_y">
  206. <div class="cy_flex_1-y">
  207. <TempVxeTable/>
  208. </div>
  209. <div style="height:max-content">
  210. <VxePager
  211. onPageChange={(data) => {
  212. pageChange()
  213. }}
  214. size={"small"}
  215. {...tsxVModel(pageVO, 'currentPage')}
  216. {...tsxVModel(pageVO, 'pageSize')}
  217. total={pageVO.total}
  218. {...simplifiedConfiguration.pagesProps}
  219. {...pagerProps}
  220. />
  221. </div>
  222. </div>
  223. } else {
  224. return <TempVxeTable/>
  225. }
  226. }
  227. function exportExcel(filename: string = '', sheetName: string = 'sheet1') {
  228. if (!XEUtils.isString(filename)) {
  229. filename = ''
  230. }
  231. tableRef.value?.openExport({
  232. filename,
  233. sheetName,
  234. types: ['xlsx', 'csv', 'html', 'xml', 'txt'],
  235. type: 'xlsx',
  236. useStyle: true,
  237. original: true,
  238. })
  239. }
  240. let queryParamsCache = {}
  241. function pageChange() {
  242. if (XEUtils.isEmpty(queryParamsCache)) {
  243. return
  244. }
  245. const data = {
  246. ...queryParamsCache,
  247. ...pageVO
  248. }
  249. props.loading = true
  250. pageQuery(data, false)
  251. }
  252. function pageQuery(data: any, clearCurrentPage: boolean = true) {
  253. pagerProps.loading = true
  254. // @ts-ignore
  255. simplifiedConfiguration.remoteSearch(data).then(res => {
  256. // @ts-ignore
  257. const data: D[] = XEUtils.get(res, simplifiedConfiguration!.result || 'result') as D[]
  258. // @ts-ignore
  259. const total: number = XEUtils.get(res, simplifiedConfiguration!.total || 'total') as number
  260. props.data = data
  261. pageVO.total = total
  262. }).catch((e) => {
  263. props.data = []
  264. pageVO.total = 0
  265. console.error(e)
  266. }).finally(() => {
  267. if (clearCurrentPage) {
  268. pageVO.currentPage = 1
  269. pageMemory.clear()
  270. }
  271. props.loading = false
  272. pagerProps.loading = false
  273. handelPageCheckBox()
  274. })
  275. }
  276. const pageMemory = reactive(new Map<any, D>());
  277. function getCheckboxRecords() {
  278. // @ts-ignore
  279. return eachAndReturnList(pageMemory, (item) => {
  280. return item
  281. })
  282. }
  283. function handelCheckbox() {
  284. const data = tableRef.value!.getCheckboxRecords()
  285. if (data.length > 0) {
  286. XEUtils.arrayEach(tableRef.value!.getCheckboxRecords(), (item) => {
  287. // @ts-ignore
  288. pageMemory.set(item[simplifiedConfiguration.keyField], item)
  289. })
  290. } else {
  291. XEUtils.arrayEach(tableRef.value!.getData(), (item) => {
  292. // @ts-ignore
  293. pageMemory.delete(item[simplifiedConfiguration.keyField])
  294. })
  295. }
  296. }
  297. function handelPageCheckBox() {
  298. nextTick().then(() => {
  299. // @ts-ignore
  300. XEUtils.arrayEach(pageMemory, (item) => {
  301. tableRef.value?.setCheckboxRow(item, true)
  302. })
  303. })
  304. }
  305. function handelCheckboxChange({row, checked}: { row: any, checked: boolean }) {
  306. mutation.setCheckboxRow(row, checked)
  307. }
  308. async function handelCheckboxAll() {
  309. await tableRef.value?.toggleAllCheckboxRow()
  310. nextTick().then(() => {
  311. handelCheckbox()
  312. })
  313. }
  314. function querySearch() {
  315. props.loading = true
  316. // @ts-ignore
  317. if (simplifiedConfiguration.showPage) {
  318. const pageData = {
  319. ...pageVO,
  320. total: 0,
  321. currentPage: 1
  322. }
  323. queryParamsCache = pageData
  324. pageQuery(pageData)
  325. } else {
  326. // @ts-ignore
  327. simplifiedConfiguration.remoteSearch({...pageVO}).then(res => {
  328. props.data = res
  329. }).catch(() => {
  330. props.data = []
  331. }).finally(() => {
  332. props.loading = false
  333. })
  334. }
  335. }
  336. function handelLookCheckBox() {
  337. const dialog = (
  338. <CyDialog title="选中详情"
  339. ignore-error
  340. body-width="70%"
  341. {...simplifiedConfiguration.dialogProps}>
  342. {{
  343. default: () => {
  344. const data = getCheckboxRecords()
  345. return <CyFlex>
  346. {{
  347. header: () => {
  348. try {
  349. if (simplifiedConfiguration.dialogHeader) {
  350. const Scc = simplifiedConfiguration?.dialogHeader();
  351. if (typeof Scc !== 'undefined') {
  352. return <Scc/>
  353. } else {
  354. console.error('dialogHeader需要返回一个组件')
  355. }
  356. }
  357. } catch (e) {
  358. console.error('dialogHeader需要返回一个组件', e)
  359. return null
  360. }
  361. },
  362. default: () => {
  363. return <VxeTable data={data}
  364. rowConfig={{
  365. keyField: simplifiedConfiguration.keyField,
  366. isCurrent: true,
  367. isHover: true,
  368. useKey: true,
  369. }}
  370. scrollY={{enabled: true, gt: 100}}
  371. showOverflow={data.length > 100}
  372. height={"100%"}>
  373. {{
  374. default: () => {
  375. return [
  376. <VxeColumn title={"删除"} width={80} fixed="left">
  377. {{
  378. default({row}: any) {
  379. return <ElButton
  380. type="danger"
  381. onClick={(el) => {
  382. handelCheckboxChange({row, checked: false})
  383. }}
  384. >{{default: () => '删除'}}
  385. </ElButton>
  386. }
  387. }}
  388. </VxeColumn>,
  389. mutation.setTableDefaultSlots()
  390. ]
  391. }
  392. }}
  393. </VxeTable>
  394. }
  395. }}
  396. </CyFlex>
  397. }
  398. }}
  399. </CyDialog>
  400. )
  401. setDialogToJs(dialog, null).then(() => {
  402. })
  403. }
  404. const mutation = {
  405. updateByIndex: function updateByIndex(data: D | any, index: number) {
  406. const tableData = tableRef.value?.getData()
  407. // @ts-ignore
  408. tableData[index] = data
  409. // @ts-ignore
  410. tableRef.value?.loadData(tableData)
  411. },
  412. setCheckboxRow: async function (row: D, checked: boolean) {
  413. await tableRef.value?.setCheckboxRow(row, checked)
  414. // @ts-ignore
  415. const key = XEUtils.get(row, simplifiedConfiguration!.keyField, null) as string | null
  416. if (key === null) return
  417. if (checked) {
  418. pageMemory.set(key, row)
  419. } else {
  420. pageMemory.delete(key)
  421. }
  422. },
  423. clearCheckboxRow() {
  424. tableRef.value?.clearCheckboxRow()
  425. pageMemory.clear()
  426. },
  427. setTableDefaultSlots(): any {
  428. }
  429. }
  430. onMounted(() => {
  431. if (simplifiedConfiguration.mountedQuery) {
  432. nextTick().then(() => {
  433. querySearch()
  434. })
  435. }
  436. })
  437. return {
  438. tableRef,
  439. CyVxeTable: CyVxeTable,
  440. tableProps: props,
  441. exportExcel,
  442. mutation,
  443. querySearch,
  444. getCheckboxRecords
  445. }
  446. }
  447. export default useVxeTable