xm-data-table.vue 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. <script>
  2. import Vue from 'vue'
  3. import cloneDeep from 'lodash.clonedeep'
  4. import { Input, Button, Icon, Popover, Badge, Dropdown, Menu } from 'ant-design-vue'
  5. import storeMixin from '@/common/store-mixin'
  6. import axios from '@/common/services/axios-instance'
  7. import sdDataTableMixin from '@/common/components/sd-table/sd-data-table-mixin'
  8. import SdTable from '@/common/components/sd-table.vue'
  9. import asyncComponent from '@/common/services/async-component'
  10. import findAncestorComponent from '@/common/services/find-ancestor-component'
  11. import booleanUtil from '@/common/services/boolean-util'
  12. // 增加高级搜索按钮
  13. const auditAdvancedQuerybtn = asyncComponent(() =>
  14. import(
  15. /* webpackChunkName: "src-table-export" */ '../../../components/audit-advanced-querybtn.vue'
  16. )
  17. )
  18. const sdDateFormat = Vue.filter('sdDateFormat')
  19. const sdDataTableExport = asyncComponent(() =>
  20. import(
  21. /* webpackChunkName: "src-table-export" */ '@/common/components/sd-table/sd-data-table-export.vue'
  22. )
  23. )
  24. function componentInRouterView(component) {
  25. return component?.$parent.$vnode.data.routerView
  26. }
  27. /**
  28. * <p>数据表格,自动处理翻页、搜索、列排序等功能</p>
  29. * <p>如果需要增删改等功能,请使用增强版表格:<a href="#sddatatableex">SdDataTableEx</a></p>
  30. * @displayName SdDataTable 数据表格
  31. */
  32. export default {
  33. name: 'XmDataTable',
  34. mixins: [sdDataTableMixin, storeMixin],
  35. inheritAttrs: false,
  36. props: {
  37. projectlist: {
  38. type: Boolean,
  39. default: false,
  40. },
  41. /**
  42. * 获取表格数据 API的url
  43. */
  44. dataUrl: {
  45. type: String,
  46. required: true,
  47. },
  48. /**
  49. * 指定数据行的key,选中文档后点击按钮后会作为参数传递
  50. */
  51. rowKey: {
  52. type: [String, Function],
  53. default: 'id',
  54. },
  55. /**
  56. * 发送请求前,整理request对象
  57. */
  58. processReq: {
  59. type: Function,
  60. default: undefined,
  61. },
  62. /**
  63. * 数据返回后,整理response对象
  64. */
  65. processRes: {
  66. type: Function,
  67. default: undefined,
  68. },
  69. /**
  70. * 内部使用,用于导出Excel
  71. * @ignore
  72. */
  73. disableExportTable: {
  74. type: Boolean,
  75. default: false,
  76. },
  77. /**
  78. * 内部使用,用于设置默认选中todo,换成selectedRows
  79. * @ignore
  80. */
  81. selectedDatas: {
  82. type: Array,
  83. default: () => [],
  84. },
  85. /**
  86. * 选择列表单选或者复选 add by zzb 20220224
  87. */
  88. checkType: {
  89. type: String,
  90. default: 'checkbox',
  91. },
  92. /***
  93. * 是否显示高级搜索按钮
  94. */
  95. showAdvanceQuery: {
  96. type: Boolean,
  97. default: false,
  98. },
  99. /**
  100. * 是否显示操作按钮
  101. */
  102. editnode: {
  103. type: Boolean,
  104. default: true,
  105. },
  106. /**
  107. * 默认分页数
  108. */
  109. defultpaginationPagesize: {
  110. type: Number,
  111. default: 0,
  112. },
  113. /** 自定义行 */
  114. customRow: {
  115. type: Function,
  116. requier: false,
  117. },
  118. },
  119. data() {
  120. return {
  121. data: [],
  122. localPagination: {
  123. current: 1,
  124. pageSize: 100,
  125. showSizeChanger: true,
  126. showQuickJumper: true,
  127. pageSizeOptions: ['100', '200'],
  128. showTotal: (total) => (
  129. <span>
  130. <Button type='link' onClick={() => this.refresh()}>
  131. 刷新
  132. </Button>
  133. {`共${total}条`}
  134. </span>
  135. ),
  136. },
  137. filters: null,
  138. sorter: null,
  139. localLoading: false,
  140. selectedRowKeys: [],
  141. selectedRows: [],
  142. searchText: '',
  143. btnIsLoading: {},
  144. showExportTable: false,
  145. userPerms2: {}, // build画的表的权限
  146. firstflag: true,
  147. }
  148. },
  149. computed: {
  150. computedPagination() {
  151. const result = {
  152. ...this.localPagination,
  153. ...this.pagination,
  154. }
  155. // 如果传入了pageSize,那么就不能让用户切换pageSize了,否则两个值会冲突
  156. if (this.pagination?.pageSize) {
  157. result.showSizeChanger = false
  158. }
  159. return result
  160. },
  161. formatedColumns() {
  162. const columnsFromPropAndLocal = [...this.columns]
  163. // 增加操作列
  164. if (this.inlineActions.length > 0) {
  165. columnsFromPropAndLocal.push({
  166. title: '操作',
  167. dataIndex: 'sd-opt',
  168. sdRender: 'opt',
  169. width: Math.min(this.inlineActions.length, this.maxInlineActions) * 75 + 'px',
  170. })
  171. }
  172. let hiddenDefaultSortOrderCount = 0
  173. const columns = columnsFromPropAndLocal
  174. .filter((col) => {
  175. // 过滤掉隐藏列
  176. const show = col.sdHidden !== true
  177. if (!show && col.defaultSortOrder) {
  178. // 默认排序的隐藏列,计数
  179. hiddenDefaultSortOrderCount++
  180. }
  181. return show
  182. })
  183. .map((col) => {
  184. col = { ...col }
  185. if (hiddenDefaultSortOrderCount > 0) {
  186. // 有默认排序的隐藏列,那么就不可能出现默认排序和手动排序刚好相符的情况
  187. // defaultSortOrder属性就不传给内部的a-table了
  188. delete col.defaultSortOrder
  189. }
  190. if (col.children) {
  191. col.children = col.children.map((childCol) => {
  192. childCol.ellipsis = true
  193. if (childCol.sdClickable) {
  194. // 如果此列可点击,增加额外的事件绑定
  195. childCol.customCell = (record, rowIndex) => {
  196. return {
  197. class: this.$style.clickableCell,
  198. attrs: {
  199. title: record[childCol.dataIndex],
  200. },
  201. on: {
  202. click: () => {
  203. /**
  204. * 点击数据某行时触发
  205. * @property {Object} record 点击的记录
  206. * @property {Object} options 其他信息{rowIndex,column}
  207. */
  208. this.$emit('rowClick', record, {
  209. rowIndex,
  210. column: childCol,
  211. })
  212. },
  213. },
  214. }
  215. }
  216. }
  217. return childCol
  218. })
  219. }
  220. col.ellipsis = true
  221. if (col.sdClickable) {
  222. // 如果此列可点击,增加额外的事件绑定
  223. col.customCell = (record, rowIndex) => {
  224. return {
  225. class: this.$style.clickableCell,
  226. attrs: {
  227. title: record[col.dataIndex],
  228. },
  229. on: {
  230. click: () => {
  231. /**
  232. * 点击数据某行时触发
  233. * @property {Object} record 点击的记录
  234. * @property {Object} options 其他信息{rowIndex,column}
  235. */
  236. this.$emit('rowClick', record, {
  237. rowIndex,
  238. column: col,
  239. })
  240. },
  241. },
  242. }
  243. }
  244. }
  245. if (col.scopedSlots) return col
  246. // 自定义的列格式,转换为customRender
  247. switch (col.sdRender) {
  248. case 'sdDateTimeFormat':
  249. // 日期型
  250. col.customRender = (text) => {
  251. return sdDateFormat(text, 'YYYY-MM-DD HH:mm')
  252. }
  253. break
  254. case 'sdDateSecondTimeFormat':
  255. // 日期型
  256. col.customRender = (text) => {
  257. return sdDateFormat(text, 'YYYY-MM-DD HH:mm:ss')
  258. }
  259. break
  260. case 'sdDateFormat':
  261. // 日期型
  262. col.customRender = (text) => {
  263. return sdDateFormat(text, 'YYYY-MM-DD')
  264. }
  265. break
  266. case 'sdTimeFormat':
  267. // 日期型
  268. col.customRender = (text) => {
  269. return sdDateFormat(text, 'HH:mm:ss')
  270. }
  271. break
  272. case 'statusBadge':
  273. // 红绿灯
  274. col.customRender = (val) => {
  275. return booleanUtil.isTruthy(val) ? (
  276. <Badge status='success' text='启用' />
  277. ) : (
  278. <Badge status='error' text='停用' />
  279. )
  280. }
  281. break
  282. case 'iconCheck':
  283. // √ 符号
  284. col.customRender = (val) => {
  285. return booleanUtil.isTruthy(val) ? <Icon type='check' /> : null
  286. }
  287. break
  288. case 'opt':
  289. // 操作列
  290. col.ellipsis = false
  291. col.customCell = (record, rowIndex) => {
  292. return {
  293. class: this.$style.tdOpt,
  294. }
  295. }
  296. col.sdClassName = this.$style.tdOpt
  297. col.customRender = (text, record, rowIndex, column) => {
  298. const doClick = (action) => {
  299. // 按钮id + 记录id 作为action id,否则点一个记录所有记录的按钮都进入loading了
  300. this.doAction({ ...action, id: action.id + record[this.rowKey] }, record, {
  301. rowIndex,
  302. })
  303. }
  304. // 处理按钮动态隐藏:先过滤掉有显示条件且不满足条件的按钮,再调用hidden
  305. const inlineActions = this.inlineActions
  306. .filter(
  307. (action) =>
  308. !(action.displayExpressions?.length || action.permission) ||
  309. record.listButtons?.includes(action.id)
  310. )
  311. .filter((action) => !action.hidden?.(record, rowIndex))
  312. // 没有按钮,直接返回
  313. if (inlineActions.length === 0) {
  314. return
  315. }
  316. let inlineActionsToShow = []
  317. const inlineActionsRest = []
  318. if (inlineActions.length <= this.maxInlineActions) {
  319. // 总按钮数 <= 最大显示数,全部显示出来
  320. inlineActionsToShow = inlineActions
  321. } else {
  322. // 总按钮数 > 最大显示数
  323. inlineActions.forEach((action, index) => {
  324. if (index < this.maxInlineActions - 1) {
  325. // 靠前的按钮直接显示
  326. inlineActionsToShow.push(action)
  327. } else {
  328. // 剩下的放进更多菜单
  329. inlineActionsRest.push(action)
  330. }
  331. })
  332. }
  333. const results = []
  334. inlineActionsToShow.forEach((action) => {
  335. results.push(
  336. <a
  337. vOn:click={(evt) => doClick(action)}
  338. disabled={this.btnIsLoading[action.id + record[this.rowKey]]}
  339. key={action.id}
  340. title={action.title}
  341. class={this.$style.optBtn}
  342. >
  343. {action.label}
  344. </a>
  345. )
  346. })
  347. if (inlineActionsRest.length) {
  348. results.push(
  349. <Popover class={this.$style.optBtn} overlayClassName={this.$style.popover}>
  350. <Button.Group slot='content'>
  351. {inlineActionsRest.map((action) => {
  352. return (
  353. <Button
  354. vOn:click={(evt) => doClick(action)}
  355. loading={this.btnIsLoading[action.id + record[this.rowKey]]}
  356. title={action.title}
  357. key={action.id}
  358. >
  359. {action.label}
  360. </Button>
  361. )
  362. })}
  363. </Button.Group>
  364. <Icon type='ellipsis' />
  365. </Popover>
  366. )
  367. }
  368. return results
  369. }
  370. break
  371. }
  372. return col
  373. })
  374. return columns
  375. },
  376. actionsHasPermission() {
  377. const filterFn = (act) => {
  378. // 行内按钮不走formId的权限控制
  379. if (act.type === 'inline') return true
  380. // 不允许不指定任何权限
  381. if (typeof act.permission === 'undefined') {
  382. // eslint-disable-next-line no-console
  383. console.warn(
  384. `按钮 "${act.label}" 没有指定权限,所以不会显示给任何用户。如果不需要权限控制,permission请传null`
  385. )
  386. return false
  387. }
  388. if (act.children) {
  389. act.children = act.children.filter(filterFn)
  390. }
  391. // null表示不控制权限
  392. if (act.permission === null) return true
  393. // 最后从权限表里面读
  394. return this.allUserPerms[act.permission]
  395. }
  396. return this.actions.filter(filterFn)
  397. },
  398. batchActions() {
  399. return this.actionsHasPermission.filter((action) => action.type === 'batch')
  400. },
  401. primaryAction() {
  402. return this.actionsHasPermission.find((action) => action.type === 'primary')
  403. },
  404. inlineActions() {
  405. return this.actionsHasPermission.filter((action) => action.type === 'inline')
  406. },
  407. normalActions() {
  408. return this.actionsHasPermission.filter((action) => !action.type)
  409. },
  410. allExpressions() {
  411. // 拼接查询条件。有用户输入的searchText和代码传递的filterExpressions两部分
  412. let searchExpressions = []
  413. if (this.searchText) {
  414. searchExpressions = [
  415. {
  416. dataType: 'exps',
  417. op: 'or',
  418. expressionsValue: this.searchFields.map((field) => ({
  419. dataType: 'str',
  420. name: field,
  421. op: 'like',
  422. stringValue: `%${this.searchText}%`,
  423. })),
  424. },
  425. ]
  426. }
  427. return [...searchExpressions, ...this.filterExpressions]
  428. },
  429. formIds() {
  430. const formIds = this.actions
  431. .filter((item) => item.permission)
  432. .map((item) => item.permission.split('-')[0])
  433. return [...new Set(formIds)]
  434. },
  435. allUserPerms() {
  436. return { ...this.userPerms, ...this.userPerms2 }
  437. },
  438. },
  439. watch: {
  440. // 各种prop变化时,刷新表格
  441. filterExpressions() {
  442. this.refresh(true)
  443. },
  444. dataUrl() {
  445. this.refresh(true)
  446. },
  447. 'selectedRowKeys.length': {
  448. handler() {
  449. this.updateSelectedRows()
  450. },
  451. immediate: true,
  452. },
  453. // 选择的值变化时,向父节点发送消息
  454. selectedRows() {
  455. this.$emit('selectedRowsChanged', this.selectedRows)
  456. },
  457. selectedDatas: {
  458. handler(values) {
  459. this.selectedRowKeys = this.selectedDatas.map((record) => {
  460. const rowKey = this.rowKey
  461. return typeof rowKey === 'function' ? rowKey(record) : record[rowKey]
  462. })
  463. this.selectedRows = values
  464. },
  465. immediate: true,
  466. },
  467. 'pagination.pageSize': function(p) {
  468. this.refresh()
  469. },
  470. formIds: {
  471. handler(formIds) {
  472. if (!formIds.length) return
  473. Promise.all(
  474. formIds.map((formId) =>
  475. axios.get('api/framework/v1/form-perm/my-authed-perms?formId=' + formId)
  476. )
  477. ).then((res) => {
  478. const perms = res.map((item) => item.data)
  479. this.userPerms2 = Object.assign({}, ...perms)
  480. })
  481. },
  482. immediate: true,
  483. },
  484. },
  485. mounted() {
  486. // 给父级的Tabs和Card加个class,有特殊处理的样式
  487. const tableEx = findAncestorComponent(this, 'SdDataTableEx', 1)
  488. const current = tableEx || this
  489. // 找5层,表示找到直接/隔一层包裹table的ATabs
  490. const aTabs = findAncestorComponent(current, 'ATabs', 5)
  491. let aCard
  492. if (aTabs) {
  493. aCard = findAncestorComponent(aTabs, 'ACard', 1)
  494. // 只有ATabs外层的ACard直接嵌套在路由里,才加特殊class
  495. if (componentInRouterView(aCard) || findAncestorComponent(aCard, 'ACol', 1)) {
  496. aTabs.$el.classList.add('sd-has-table')
  497. aCard.$el.classList.add('sd-has-table')
  498. }
  499. } else {
  500. // 找1层,表示找到直接/隔一层包裹table的ACard
  501. aCard = findAncestorComponent(current, 'ACard', 2)
  502. if (componentInRouterView(aCard) || findAncestorComponent(aCard, 'ACol', 1)) {
  503. aCard.$el.classList.add('sd-has-table')
  504. }
  505. }
  506. },
  507. created() {
  508. this.loadData()
  509. },
  510. methods: {
  511. /**
  512. * 加载数据方法
  513. * @param {Object} pagination 分页选项器
  514. * @param {Object} filters 过滤条件
  515. * @param {Object} sorter 排序条件
  516. */
  517. onChange(pagination, filters, sorter) {
  518. this.localPagination = { ...pagination }
  519. this.filters = { ...filters }
  520. this.sorter = { ...sorter }
  521. this.$emit('onChange', pagination, filters, sorter) // add by zzb
  522. this.loadData()
  523. },
  524. /**
  525. * 获取当前选中的记录(仅包含rowKey)
  526. * @public
  527. * @returns {Array}
  528. */
  529. getSelectedRowKeys() {
  530. return [...this.selectedRowKeys]
  531. },
  532. /**
  533. * 获取当前选中的记录
  534. * @returns {Object[]}
  535. * @public
  536. */
  537. getSelectedRows() {
  538. return cloneDeep(this.selectedRows)
  539. },
  540. /**
  541. * 刷新表格
  542. * @public
  543. * @param {boolean} toFirstPage 是否返回第一页
  544. */
  545. refresh(toFirstPage) {
  546. if (toFirstPage) this.localPagination.current = 1
  547. return this.loadData().then(() => {
  548. // 数据刷新后,更新选中的记录内容
  549. this.updateSelectedRows()
  550. })
  551. },
  552. clearSelection() {
  553. this.selectedRowKeys = []
  554. },
  555. loadData() {
  556. const pagination = this.computedPagination
  557. this.localLoading = true
  558. if (this.firstflag) {
  559. if (this.defultpaginationPagesize !== 0) {
  560. pagination.pageSize = this.defultpaginationPagesize
  561. }
  562. this.firstflag = false
  563. }
  564. const param = {
  565. columns: this.columns
  566. .map((col) => col.dataIndex)
  567. .filter((name) => name !== 'sd-opt')
  568. .join(','),
  569. // 后台-1表示显示所有记录
  570. maxResults: pagination.pageSize === Number.MAX_VALUE ? -1 : pagination.pageSize,
  571. startPosition: pagination.pageSize * (pagination.current - 1),
  572. }
  573. let sorters = []
  574. // 用户点击的排序列先放进去
  575. if (this.sorter?.order) {
  576. sorters.push(this.sorter)
  577. }
  578. // 获取默认排序列,可以是多个
  579. const defaultSorters = this.columns
  580. .filter((col) => col.defaultSortOrder)
  581. .map((col) => ({
  582. field: col.dataIndex,
  583. order: col.defaultSortOrder,
  584. }))
  585. if (
  586. // 用户没有点击排序
  587. sorters.length === 0 ||
  588. // 或者 用户点击的排序值和默认排序完全一样
  589. (sorters[0]?.field === defaultSorters[0]?.field &&
  590. sorters[0]?.order === defaultSorters[0]?.order)
  591. ) {
  592. // 直接用默认排序值即可
  593. sorters = defaultSorters
  594. }
  595. if (sorters.length > 0) {
  596. param.orderBy = sorters
  597. .map((sorter) => `${sorter.field} ${sorter.order.replace(/end$/, '')}`)
  598. .join(',')
  599. }
  600. param.expressions = this.allExpressions
  601. // 拼接行内按钮显示条件、权限点
  602. param.buttonExpressions = this.inlineActions
  603. .filter((action) => action.displayExpressions?.length > 0 || action.permission)
  604. .map((action) => ({
  605. id: action.id,
  606. expressions: action.displayExpressions,
  607. permission: action.permission,
  608. }))
  609. // 拼好的请求参数,交给processReq处理一下
  610. let req = {
  611. url: this.dataUrl,
  612. method: 'post',
  613. data: param,
  614. }
  615. if (this.processReq) req = this.processReq(req)
  616. return axios(req)
  617. .then((res) => {
  618. let data = res.data
  619. if (this.processRes) data = this.processRes(data)
  620. // Read total count from server
  621. pagination.total = data.total ?? data.totalSize
  622. // 处理删除文档后,当前页大于总页数的情况
  623. const current = Math.min(
  624. pagination.current,
  625. // 当前页至少为1,不能为0
  626. Math.max(Math.ceil(pagination.total / pagination.pageSize), 1)
  627. )
  628. if (current < pagination.current) {
  629. // 重新请求适当页的数据
  630. pagination.current = current
  631. this.loadData()
  632. } else {
  633. this.data = data.datas ?? data.data
  634. }
  635. this.localPagination = { ...pagination }
  636. /**
  637. * 表格数据加载完成时触发
  638. * @property {Object} pagination 分页信息{current,pageSize,total}
  639. */
  640. this.$emit('dataLoaded', {
  641. current: pagination.current,
  642. pageSize: pagination.pageSize,
  643. total: pagination.total,
  644. data: this.data,
  645. respData: data,
  646. })
  647. })
  648. .finally(() => {
  649. this.localLoading = false
  650. if (this.projectlist) {
  651. if (
  652. document.getElementsByClassName('ant-table-empty').length > 0 &&
  653. document.getElementsByClassName('ant-table-placeholder').length > 0
  654. ) {
  655. document
  656. .getElementsByClassName('ant-table-empty')[0]
  657. .getElementsByClassName('ant-table-tbody')[0]
  658. .appendChild(document.getElementsByClassName('ant-table-placeholder')[0])
  659. }
  660. this.$emit('fnonloadsum')
  661. }
  662. })
  663. },
  664. onSelectChange(selectedRowKeys) {
  665. this.selectedRowKeys = selectedRowKeys
  666. },
  667. onSearchChange(text) {
  668. if (this.searchText === text) return
  669. this.searchText = text.trim()
  670. const pagination = this.computedPagination
  671. pagination.current = 1
  672. this.localPagination = { ...pagination }
  673. this.loadData()
  674. },
  675. doAction(action, ...args) {
  676. // 调用callback,并处理loading状态
  677. this.btnIsLoading = { ...this.btnIsLoading, [action.id]: true }
  678. Promise.resolve(action.callback(...args)).finally(() => {
  679. if (action.label.includes('导出')) {
  680. setTimeout(() => {
  681. this.btnIsLoading = { ...this.btnIsLoading, [action.id]: false }
  682. }, 1000)
  683. } else {
  684. this.btnIsLoading = { ...this.btnIsLoading, [action.id]: false }
  685. }
  686. })
  687. },
  688. /**
  689. * 导出Excel
  690. * @public
  691. * @since 0.15
  692. * @returns {Promise}
  693. */
  694. exportExcel() {
  695. if (this.showExportTable) {
  696. return Promise.reject(new Error('已经在导出中……'))
  697. }
  698. this.showExportTable = true
  699. return new Promise((resolve, reject) => {
  700. this.exportResolve = resolve
  701. }).then(() => {
  702. this.showExportTable = false
  703. })
  704. },
  705. /**
  706. * 重新计算各列的宽度,容器宽度发生变化时,可以调用
  707. * @public
  708. * @since 0.15
  709. */
  710. handleResize() {
  711. return this.$refs.table.handleResize()
  712. },
  713. exportFinish() {
  714. this.exportResolve()
  715. },
  716. updateSelectedRows() {
  717. // selectedRows 与 selectedRowKeys 同步
  718. const records = [...this.data, ...this.selectedRows]
  719. this.selectedRows = this.selectedRowKeys.map((key) =>
  720. records.find((record) => {
  721. const rowKey = this.rowKey
  722. const recordKey = typeof rowKey === 'function' ? rowKey(record) : record[rowKey]
  723. return recordKey === key
  724. })
  725. )
  726. this.$emit('update:selectedDatas', this.selectedRows)
  727. },
  728. // 增加高级搜搜按钮调用方法
  729. searchbtnClick() {
  730. this.$emit('searchbtnClick')
  731. },
  732. },
  733. /**
  734. * 数据表格右上角(搜索框、按钮区)前面部分的内容
  735. * @slot headerPrefix
  736. */
  737. render() {
  738. const getBtn = (action, onClick, { disabled = false, type } = {}) => {
  739. const btn = (
  740. <Button
  741. class={action.class}
  742. type={type}
  743. disabled={disabled}
  744. vOn:click={onClick}
  745. loading={this.btnIsLoading[action.id]}
  746. key={action.id}
  747. title={action.title}
  748. >
  749. {action.label}
  750. </Button>
  751. )
  752. if (action.children?.length) {
  753. return (
  754. <div class='ant-btn-group ant-dropdown-button'>
  755. {[
  756. btn,
  757. <Dropdown
  758. placement='bottomRight'
  759. trigger={['click']}
  760. scopedSlots={{
  761. overlay: () => (
  762. <Menu>
  763. {action.children.map((item) => {
  764. const isLoading = this.btnIsLoading[item.id]
  765. const disabled = item.type === 'batch' && this.selectedRowKeys.length === 0
  766. return (
  767. <Menu.Item
  768. title={item.title}
  769. disabled={isLoading || disabled}
  770. vOn:click={() => {
  771. if (item.type === 'batch') {
  772. this.doAction(
  773. item,
  774. this.selectedRowKeys,
  775. cloneDeep(this.selectedRows)
  776. )
  777. } else {
  778. this.doAction(item)
  779. }
  780. }}
  781. >
  782. {item.label}
  783. </Menu.Item>
  784. )
  785. })}
  786. </Menu>
  787. ),
  788. }}
  789. >
  790. <Button type={type} class='ant-dropdown-trigger'>
  791. <Icon type='down' />
  792. </Button>
  793. </Dropdown>,
  794. ]}
  795. </div>
  796. )
  797. } else {
  798. return btn
  799. }
  800. }
  801. const attrs = {
  802. rowKey: this.rowKey,
  803. loading: this.localLoading,
  804. dataSource: this.data,
  805. pagination: this.computedPagination,
  806. columns: this.formatedColumns,
  807. showHeader: this.showHeader,
  808. scroll: this.scroll,
  809. showAllColumns: this.disableExportTable,
  810. }
  811. if (this.showSelection) {
  812. attrs.rowSelection = {
  813. onChange: this.onSelectChange,
  814. selectedRowKeys: this.selectedRowKeys,
  815. type: this.checkType,
  816. }
  817. if (typeof this.showSelection === 'function') {
  818. attrs.rowSelection.getCheckboxProps = (record) => {
  819. return {
  820. props: {
  821. disabled: !this.showSelection(record),
  822. },
  823. }
  824. }
  825. }
  826. }
  827. if (this.customRow !== undefined) {
  828. attrs.customRow = this.customRow
  829. }
  830. const { headerPrefix, ...scopedSlots } = this.$scopedSlots
  831. return (
  832. <div
  833. class={{
  834. [this.$style.wrapper]: true,
  835. [this.$style.hidePagination]: this.hidePagination,
  836. projectlist: this.projectlist,
  837. }}
  838. >
  839. <div class={this.$style.header}>
  840. {headerPrefix?.()}
  841. {this.searchFields.length > 0 ? (
  842. <Input.Search
  843. vOn:search={this.onSearchChange}
  844. allow-clear
  845. placeholder='请输入搜索条件'
  846. />
  847. ) : null}
  848. {this.showAdvanceQuery ? (
  849. <auditAdvancedQuerybtn
  850. vOn:searchbtnClick={() => {
  851. this.searchbtnClick()
  852. }}
  853. />
  854. ) : null}
  855. <span v-show={this.editnode}>
  856. {this.primaryAction
  857. ? getBtn(this.primaryAction, () => this.doAction(this.primaryAction), {
  858. type: 'primary',
  859. })
  860. : null}
  861. {this.normalActions.map((action) => {
  862. return getBtn(action, () => this.doAction(action))
  863. })}
  864. {this.batchActions.map((action) => {
  865. return getBtn(
  866. action,
  867. (evt) => {
  868. this.doAction(action, this.selectedRowKeys, cloneDeep(this.selectedRows))
  869. },
  870. { disabled: this.selectedRowKeys.length === 0 }
  871. )
  872. })}
  873. </span>
  874. </div>
  875. <SdTable
  876. {...{
  877. attrs,
  878. ref: 'table',
  879. scopedSlots: { ...scopedSlots },
  880. }}
  881. onChange={this.onChange}
  882. onRefresh={this.refresh}
  883. ></SdTable>
  884. {this.disableExportTable || !this.showExportTable ? null : (
  885. <sdDataTableExport
  886. {...{
  887. attrs: {
  888. ...this.$props,
  889. // 把用户输入的搜索条件也作为filter
  890. filterExpressions: this.allExpressions,
  891. sorter: this.sorter,
  892. },
  893. scopedSlots,
  894. on: {
  895. finish: this.exportFinish,
  896. },
  897. }}
  898. />
  899. )}
  900. </div>
  901. )
  902. },
  903. }
  904. </script>
  905. <style module lang="scss">
  906. @use '@/common/design' as *;
  907. // 包裹表格的tab页特殊处理
  908. :global(.sd-has-table.ant-tabs) {
  909. .wrapper {
  910. margin-top: -64px;
  911. }
  912. :global(.ant-tabs-nav) {
  913. position: relative;
  914. z-index: 2;
  915. }
  916. .header {
  917. height: 42px;
  918. }
  919. }
  920. // 包裹表格的Card特殊处理
  921. :global(.sd-has-table.ant-card) {
  922. min-height: 100%;
  923. }
  924. .header {
  925. position: relative;
  926. z-index: 1;
  927. display: flex;
  928. align-items: center;
  929. float: right;
  930. margin: 3px 0;
  931. :global .ant-input-search {
  932. width: 200px;
  933. margin-left: 10px;
  934. }
  935. :global .ant-btn {
  936. margin: 5px 0 5px 10px;
  937. }
  938. :global .ant-btn-group {
  939. margin-left: 10px;
  940. }
  941. }
  942. .wrapper {
  943. :global(.ant-table-tbody) {
  944. .clickable-cell {
  945. color: $primary-color;
  946. cursor: pointer;
  947. &:hover {
  948. color: $primary-5;
  949. }
  950. &:active {
  951. color: $primary-7;
  952. }
  953. }
  954. }
  955. .td-opt {
  956. padding: 0;
  957. .opt-btn + .opt-btn {
  958. padding-left: 10px;
  959. }
  960. }
  961. }
  962. .hide-pagination {
  963. :global(.ant-table-pagination) {
  964. display: none;
  965. }
  966. }
  967. .popover {
  968. :global .ant-popover-inner-content {
  969. padding: 0;
  970. margin-bottom: -10px;
  971. }
  972. :global .ant-popover-arrow {
  973. display: none;
  974. }
  975. :global .ant-btn-group {
  976. white-space: nowrap;
  977. }
  978. }
  979. </style>