xm-data-table-ex.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. <script>
  2. import { Switch, Modal } from 'ant-design-vue'
  3. import qs from 'qs'
  4. import axios from '@/common/services/axios-instance'
  5. import crossWindowWatcher from '@/common/services/cross-window-watcher'
  6. import sdDataTableMixin from '@/common/components/sd-table/sd-data-table-mixin'
  7. import SdDataTable from './xm-data-table'
  8. import SdDetailModal from '@/common/components/sd-detail-modal'
  9. import form from '@/common/components/sd-icon-library/icons/outline/form'
  10. import { message } from '@/common/one-ui'
  11. import booleanUtil from '@/common/services/boolean-util'
  12. /**
  13. * <p>为了项目上能快速实现一个增删查改的模块,我们提供了组件 SdDataTableEx</p>
  14. * <p>该组件需要配合固定格式的接口实现</p>
  15. * <ul>
  16. * <li>获取列表 <pre>api/framework/v1/page/businessList</pre></li>
  17. * <li>获取详情 <pre>api/framework/v1/page/wp/${pageId}?id={beanId}</pre></li>
  18. * <li>保存数据 <pre>api/framework/v1/page/handleData</pre></li>
  19. * <li>删除数据 <pre>api/framework/v1/page/${formId}?ids={beanId1},{beanId2}</pre></li>
  20. * <li>权限控制 <pre>api/framework/v1/form-perm/my-authed-perms</pre></li>
  21. * </ul>
  22. * @displayName SdDataTableEx 高级数据表格
  23. * @since 8.0.12
  24. */
  25. export default {
  26. name: 'XmDataTableEx',
  27. mixins: [sdDataTableMixin],
  28. inheritAttrs: false,
  29. props: {
  30. projectlist: {
  31. type: Boolean,
  32. default: false,
  33. },
  34. /**
  35. * framework/v1/page/businessList 接口中的 formId
  36. */
  37. formId: {
  38. type: String,
  39. required: true,
  40. },
  41. /**
  42. * framework/v1/page/wp 接口中的 pageId
  43. */
  44. pageId: {
  45. type: String,
  46. default: function() {
  47. return this.formId
  48. },
  49. },
  50. /**
  51. * framework/v1/page/wp 接口中的 extParams
  52. * @since 8.1.0.29
  53. */
  54. extParams: {
  55. type: Object,
  56. default: undefined,
  57. },
  58. /**
  59. * 获取表格数据 API的url
  60. */
  61. dataUrl: {
  62. type: String,
  63. default: 'api/xcoa-mobile/v1/iam-page/businessList',
  64. },
  65. /**
  66. * 传给详情 modal 的 props,可以用于设置样式等
  67. */
  68. modalProps: {
  69. type: Object,
  70. default: undefined,
  71. },
  72. /***
  73. * 请求过滤方法
  74. */
  75. processReq: {
  76. type: Function,
  77. default: undefined,
  78. },
  79. /**
  80. * 选择列表单选或者复选 add by zzb 20220224
  81. */
  82. checkType: {
  83. type: String,
  84. default: 'checkbox',
  85. },
  86. /***
  87. * 是否显示高级搜索按钮
  88. */
  89. showAdvanceQuery: {
  90. type: Boolean,
  91. default: false,
  92. },
  93. /***
  94. * 当前页面是否显示操作按钮
  95. */
  96. editnode: {
  97. type: Boolean,
  98. default: true,
  99. },
  100. /**
  101. * 自定义删除方法
  102. */
  103. customDeleteFun: {
  104. type: Function,
  105. requier: false,
  106. },
  107. /** 自定义行 */
  108. customRow: {
  109. type: Function,
  110. requier: false,
  111. },
  112. // 加载样式
  113. fnonloadsum: {
  114. type: Function,
  115. default: null,
  116. },
  117. },
  118. data() {
  119. return {
  120. recordId: undefined,
  121. localPageId: this.pageId,
  122. loadingSwitchs: [],
  123. isWindowOpen: false,
  124. }
  125. },
  126. computed: {
  127. // 把新建和删除按钮转换为标准格式
  128. computedActions() {
  129. const actions = this.actions.map((act) => {
  130. const action = { ...act }
  131. if (action.type === 'delete') {
  132. action.type = 'batch'
  133. if (this.customDeleteFun) {
  134. action.callback = this.customDeleteFun
  135. } else {
  136. action.callback = this.deleteRows
  137. action.permission = 'delete'
  138. }
  139. } else if (action.type === 'create') {
  140. action.type = 'primary'
  141. action.permission = 'create'
  142. action.callback = this.create
  143. }
  144. if (action.permission) {
  145. if (action.permission.startsWith(this.formId + '-')) {
  146. // eslint-disable-next-line no-console
  147. console.warn(
  148. `按钮 "${act.label}" 的 permission 属性请去掉 "${this.formId}-" 部分,formId 会自动添加`
  149. )
  150. } else {
  151. if (!action.permission.includes('-')) {
  152. action.permission = `${this.formId}-${action.permission}`
  153. }
  154. }
  155. }
  156. return action
  157. })
  158. return actions
  159. },
  160. formatedColumns() {
  161. const columns = this.columns.map((col) => {
  162. if (col.sdRender === 'switch') {
  163. // 行内的切换按钮
  164. col.customRender = (text, record, index, column) => {
  165. return (
  166. <Switch
  167. checked={booleanUtil.isTruthy(text)}
  168. loading={this.loadingSwitchs.indexOf(record.id) > -1}
  169. onChange={() => this.onSwitchChange(record, column)}
  170. />
  171. )
  172. }
  173. }
  174. return col
  175. })
  176. return columns
  177. },
  178. },
  179. // 加载分级授权获取部门下拉框 add by zzb
  180. mounted() {
  181. this.isWindowOpen = !this.$scopedSlots.form
  182. let tree = this.$parent.$children.find((item) => {
  183. return item.$options._componentTag === 'audit-tree'
  184. })
  185. if (!tree) {
  186. tree = this.$parent.$parent.$children.find((item) => {
  187. return (
  188. (item.$options._componentTag === 'audit-tree') |
  189. (item.$options._componentTag === 'audit-matters-catalog-tree') |
  190. (item.$options._componentTag === 'mtx-process-catalog-tree') |
  191. (item.$options._componentTag === 'mtx-version-catalog-tree') |
  192. (item.$options._componentTag === 'risk-category-tree')
  193. )
  194. })
  195. }
  196. // 如果还是找不到树组件,则直接退出
  197. if (!tree) {
  198. if (tree?.initTreeData) {
  199. tree.initTreeData()
  200. }
  201. return
  202. }
  203. const table = this.$parent.$children.find((item) => {
  204. return (
  205. (item.$options._componentTag === 'sd-data-table-ex') |
  206. (item.$options._componentTag === 'sd-oa-table')
  207. )
  208. })
  209. if (!table) {
  210. const acard = this.$parent.$children.find((item) => {
  211. return item.$options._componentTag === 'a-card'
  212. })
  213. let intable
  214. if (acard) {
  215. intable = acard.$parent.$children.find((item) => {
  216. return item.$options._componentTag === 'sd-data-table-ex'
  217. })
  218. } else {
  219. intable = this.$parent.$children.find((item) => {
  220. return item.$options._componentTag === 'sd-data-table-ex'
  221. })
  222. }
  223. // 显示下拉框才初始化
  224. if (tree.isSelectDep) {
  225. tree.initDeptList(intable.formId)
  226. } else if (tree.checkNodeEdit) {
  227. // 如果需要检查节点权限
  228. if (tree?.beforeInitTreeData) {
  229. tree.beforeInitTreeData(intable.formId)
  230. }
  231. } else {
  232. // 正常加载
  233. tree.initTreeData()
  234. }
  235. } else {
  236. // 显示下拉框才初始化
  237. if (tree.isSelectDep) {
  238. tree.initDeptList(table.formId)
  239. } else if (tree.checkNodeEdit) {
  240. // 如果需要检查节点权限
  241. if (tree?.beforeInitTreeData) {
  242. tree.beforeInitTreeData(table.formId)
  243. }
  244. } else {
  245. // 正常加载
  246. tree.initTreeData()
  247. }
  248. }
  249. },
  250. methods: {
  251. addMonitor() {
  252. window.addEventListener('message', this.eventListener, false)
  253. },
  254. removeMonitor() {
  255. window.removeEventListener('message', this.eventListener, false)
  256. },
  257. eventListener(event) {
  258. // 格式为{type:'', data: {}}
  259. if (event.data?.type === 'recordSaved') {
  260. const data = event.data.data
  261. if (data.formId === this.formId) {
  262. this.refresh()
  263. this.$emit('recordSaved', data.recordId)
  264. }
  265. }
  266. },
  267. rowClick(record) {
  268. this.showDetailModal(record.id, this.pageId)
  269. },
  270. create() {
  271. this.showDetailModal(null, this.pageId)
  272. },
  273. showDetailWindow(recordId = null, pageId = this.pageId) {
  274. crossWindowWatcher.subscribeChange({
  275. url:
  276. '/sd-webform?' +
  277. qs.stringify({
  278. id: recordId,
  279. pageId,
  280. // 新建时,传extParams,用于设置默认值
  281. extParams: !recordId && this.extParams ? JSON.stringify(this.extParams) : undefined,
  282. }),
  283. callback: (data) => {
  284. this.refresh()
  285. this.$emit('recordSaved', recordId || data?.pageFormData?.beanId)
  286. },
  287. })
  288. },
  289. /**
  290. * 获取详情对话框,可以通过它调用操作字段的API
  291. * 详情信息以Modal方式打开时有效
  292. * @public
  293. */
  294. getDetailModal() {
  295. return this.$refs.detail
  296. },
  297. /**
  298. * 隐藏详情对话框
  299. * 详情信息以Modal方式打开时有效
  300. * @public
  301. */
  302. hideDetailModal() {
  303. this.$refs.detail.hide()
  304. },
  305. /**
  306. * 显示详情对话框(Modal或新窗口方式)
  307. * @public
  308. * @param {string} recordId 要显示的记录ID,不传此参数表示新建
  309. * @param {string} pageId 用哪个page显示,默认为组件pageId prop的值
  310. */
  311. showDetailModal(recordId = null, pageId = this.pageId) {
  312. if (this.isWindowOpen) {
  313. this.showDetailWindow(recordId, pageId)
  314. } else {
  315. this.recordId = recordId
  316. this.localPageId = pageId
  317. this.$refs.detail.show()
  318. }
  319. /**
  320. * 详情对话框显示时触发8.1.0.27添加纪录id
  321. */
  322. this.$emit('detailModalShow', recordId)
  323. },
  324. deleteRows(selectedRowKeys) {
  325. return new Promise((resolve) => {
  326. // 判断是否起草环节
  327. const rows = this.$refs.dataTable.getSelectedRows()
  328. let flag = true
  329. let surl = 'api/framework/v1/page/' + this.formId
  330. if (rows[0].instId === undefined || rows[0].instId === null) {
  331. surl = 'api/xcoa-mobile/v1/iam-page/' + this.formId
  332. } else {
  333. // 流程类的 判断是否是起草环节
  334. rows.forEach((r) => {
  335. if (r.endType) {
  336. if (r.endType !== 0) {
  337. flag = false
  338. }
  339. }
  340. })
  341. }
  342. if (!flag) {
  343. Modal.error({
  344. title: '删除失败:存在不是起草状态的数据',
  345. })
  346. resolve()
  347. } else {
  348. Modal.confirm({
  349. title: '您确定删除这项内容吗?',
  350. content: '删除这条数据后,就无法恢复初始的状态。',
  351. okText: '删除',
  352. cancelText: '取消',
  353. okType: 'danger',
  354. onOk: () => {
  355. axios({
  356. // url: 'api/framework/v1/page/' + this.formId,
  357. url: surl,
  358. method: 'delete',
  359. params: {
  360. ids: selectedRowKeys.join(','),
  361. },
  362. })
  363. .then((res) => {
  364. if (res.status === 200) {
  365. this.clearSelection()
  366. this.refresh()
  367. message.success('删除成功')
  368. /**
  369. * 数据删除成功后触发
  370. * @property {Array} rowKeys 已删除的记录ids
  371. */
  372. this.$emit('recordsDeleted', selectedRowKeys)
  373. } else {
  374. if (res.errors) {
  375. message.error(res.errors)
  376. } else {
  377. message.error('删除失败,请联系管理员')
  378. }
  379. }
  380. })
  381. .catch((e) => {
  382. let messageinfo = '删除失败,请联系管理员'
  383. if (e.response?.data?.code === '-1') {
  384. messageinfo = e.response?.data?.message
  385. } else if (['403', '401'].includes(e.response?.data?.code)) {
  386. messageinfo = '您没有访问权限'
  387. } else if (e.response?.data?.code === '405') {
  388. if (e.response.data && e.response.data.errors) {
  389. messageinfo = e.response.data.errors
  390. }
  391. } else {
  392. messageinfo = '处理失败,请联系管理员'
  393. }
  394. message.error(messageinfo)
  395. })
  396. .finally(resolve)
  397. },
  398. onCancel: () => {
  399. resolve()
  400. },
  401. })
  402. }
  403. })
  404. },
  405. /**
  406. * 获取当前选中的记录(仅包含rowKey)
  407. * @public
  408. * @returns {Array}
  409. */
  410. getSelectedRowKeys() {
  411. return this.$refs.dataTable.getSelectedRowKeys()
  412. },
  413. /**
  414. * 获取当前选中的记录
  415. * @public
  416. * @returns {Object[]}
  417. */
  418. getSelectedRows() {
  419. return this.$refs.dataTable.getSelectedRows()
  420. },
  421. /**
  422. * 刷新表格
  423. * @public
  424. * @param {boolean} toFirstPage 是否返回第一页
  425. */
  426. refresh(toFirstPage) {
  427. return this.$refs.dataTable.refresh(toFirstPage)
  428. },
  429. clearSelection() {
  430. this.$refs.dataTable.clearSelection()
  431. },
  432. // 行内的切换按钮。先读取详情,再保存
  433. onSwitchChange(record, column) {
  434. this.loadingSwitchs.push(record.id)
  435. axios({
  436. url: 'api/framework/v1/page/wp/' + this.pageId,
  437. method: 'get',
  438. params: {
  439. id: record.id,
  440. },
  441. })
  442. .then((res) => {
  443. const resJson = res.data
  444. const data = {
  445. eventId: 'save',
  446. inputs: resJson.pageFormData.pageFieldInfos.map((v) => ({
  447. name: v.name,
  448. value: v.name === column.dataIndex ? booleanUtil.getOppositeValue(v.value) : v.value,
  449. })),
  450. pageFlowId: resJson.attrs.pageflowId,
  451. pagePath: resJson.attrs.pagePath,
  452. }
  453. return axios({
  454. url: 'api/framework/v1/page/handleData',
  455. method: 'post',
  456. data,
  457. })
  458. })
  459. .then((res) => {
  460. this.refresh()
  461. /**
  462. * 数据保存成功后触发
  463. * @property {String} rowKey 已保存的记录id
  464. */
  465. this.$emit('recordSaved', record.id)
  466. })
  467. .finally(() => {
  468. const index = this.loadingSwitchs.indexOf(record.id)
  469. if (index > -1) {
  470. this.loadingSwitchs.splice(index, 1)
  471. }
  472. })
  473. },
  474. /**
  475. * 导出Excel
  476. * @public
  477. * @since 0.15
  478. * @returns {Promise}
  479. */
  480. exportExcel() {
  481. return this.$refs.dataTable.exportExcel()
  482. },
  483. /**
  484. * 重新计算各列的宽度,容器宽度发生变化时,可以调用
  485. * @public
  486. * @since 0.15
  487. */
  488. handleResize() {
  489. return this.$refs.dataTable.handleResize()
  490. },
  491. },
  492. /**
  493. * 详情表单内容
  494. * @slot form
  495. * @binding {object} model 整个表单的数据,可用于 v-model 绑定
  496. * @binding {object} fields 后台返回的字段定义
  497. * @binding {string} pageId 当前显示详情页使用的pageId,可以为不同pageId画不同的表单
  498. * @binding {object} formData 当前显示详情页的整个formData(8.1.0.24新增)
  499. */
  500. render() {
  501. const { form: formSlot, ...columnSlots } = this.$scopedSlots
  502. const { columns, ...restProps } = this.$props
  503. return (
  504. <div>
  505. <SdDataTable
  506. {...{
  507. attrs: {
  508. ...restProps,
  509. projectlist: this.projectlist,
  510. editnode: this.editnode,
  511. columns: this.formatedColumns,
  512. dataUrl: this.dataUrl,
  513. actions: this.computedActions,
  514. processReq: (req) => {
  515. req.data.formId = this.formId
  516. if (this.processReq) {
  517. req = this.processReq(req)
  518. }
  519. return req
  520. },
  521. },
  522. ref: 'dataTable',
  523. on: {
  524. rowClick: this.rowClick,
  525. dataLoaded: (pagination) => {
  526. /**
  527. * 表格数据加载完成时触发
  528. * @property {Object} pagination 分页信息{current,pageSize,total}
  529. */
  530. this.$emit('tableDataLoaded', pagination)
  531. },
  532. onChange: (pagination, filters, sorter) => {
  533. this.$emit('onChange', pagination, filters, sorter)
  534. },
  535. searchbtnClick: () => {
  536. this.$emit('searchbtnClick')
  537. },
  538. selectedRowsChanged: (selectedRows) => {
  539. this.$emit('selectedRowsChanged', selectedRows)
  540. },
  541. fnonloadsum: () => {
  542. if (this.fnonloadsum !== null) {
  543. this.fnonloadsum()
  544. }
  545. },
  546. },
  547. scopedSlots: { ...columnSlots },
  548. }}
  549. ></SdDataTable>
  550. {formSlot ? (
  551. <SdDetailModal
  552. ref='detail'
  553. pageId={this.localPageId}
  554. recordId={this.recordId}
  555. extParams={this.extParams}
  556. onSaved={(data) => {
  557. this.refresh()
  558. message.success('保存成功')
  559. this.$emit('recordSaved', this.recordId || data?.pageFormData?.beanId)
  560. }}
  561. onActionBtnClick={(evt, btn) => {
  562. /**
  563. * 详情表单内的操作按钮点击时触发
  564. *
  565. * @property {event} evt 事件对象,可以用来取消默认操作等
  566. * @property {object} btn 点击的按钮
  567. * @property {object} context 上下文信息,其中包括:
  568. * form:详情对话框
  569. * recordId:当前记录ID
  570. */
  571. this.$emit('formBtnClick', evt, btn, {
  572. form: this.$refs.detail,
  573. recordId: this.recordId,
  574. })
  575. }}
  576. scopedSlots={{ default: formSlot }}
  577. modalProps={this.modalProps}
  578. />
  579. ) : (
  580. ''
  581. )}
  582. </div>
  583. )
  584. },
  585. }
  586. </script>
  587. <style module lang="scss">
  588. @use '@/common/design' as *;
  589. </style>