grid.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. import _ from 'lodash'
  2. import {grid} from '../Defaults'
  3. import {baseConfig} from "./base";
  4. import {lookupFn, lookupScope} from "../lib/lib";
  5. import LAY_EXCEL from "lay-excel"
  6. import {serverInvokeUrlTransform} from "../lib/config";
  7. import {calcObject, calcObjectFlat} from "../lib/systemLib";
  8. import {
  9. disabled,
  10. fieldLabel,
  11. gravity, height, metaId,
  12. PropertyDescriptionTable,
  13. tooltip,
  14. value, width,
  15. YvBase
  16. } from "../PropertyDescriptionTable";
  17. import {PropertyDescription} from "../PropertyDescription";
  18. import {gridInvokeBuild} from "./stores";
  19. import { msg } from 'src/message';
  20. const defaultGrid = grid
  21. export default function () {
  22. Ext.define('Yvan.Grid', {
  23. extend: 'Ext.grid.Panel',
  24. xtype: 'yvgrid',
  25. constructor(config) {
  26. const me = this
  27. const {dataSource} = config
  28. if (!window["IS_DESIGN_MODE"]) {
  29. this.columnConfigCacheKey = this.makeColumnConfigCacheKey(config)
  30. if (Array.isArray(config.columns) && config.columns.length > 0) {
  31. const cacheData = this.getColumnConfigCache()
  32. if (Array.isArray(cacheData) && cacheData.length > 0) {
  33. const newColumns = []
  34. for (let j = 0; j < cacheData.length; j++) {
  35. const itData = cacheData[j]
  36. for (let i = 0; i < config.columns.length; i++) {
  37. const column = config.columns[i]
  38. if (itData.dataIndex === column.dataIndex) {
  39. if (itData.width) {
  40. column.width = itData.width
  41. }
  42. column.hidden = itData.hidden
  43. column.locked = itData.locked
  44. newColumns.push(column)
  45. break
  46. }
  47. }
  48. }
  49. config.columns = newColumns
  50. }
  51. }
  52. }
  53. const newConfig = _.defaultsDeep({
  54. // 强制性属性
  55. }, baseConfig(config, 'row-item'), config, grid)
  56. // 在面板上的组件
  57. const scope = newConfig.$initParent.yvanScope || newConfig.$initParent.lookupReferenceHolder().yvanScope;
  58. const buttons = []
  59. const {getRowClass} = newConfig
  60. if (typeof getRowClass === 'string' && (
  61. _.startsWith(getRowClass, "scope.") ||
  62. _.startsWith(getRowClass, "system."))
  63. ) {
  64. const fn = lookupFn(scope, getRowClass)
  65. _.set(newConfig, 'viewConfig.getRowClass', fn)
  66. }
  67. if (!newConfig.hideExport) {
  68. buttons.push({
  69. xtype: 'button',
  70. tooltip: '导出Excel',
  71. iconCls: 'x-fa fa-download',
  72. listeners: {
  73. click: this.exportExcel
  74. },
  75. })
  76. }
  77. if (!newConfig.hideAutoSize) {
  78. buttons.push({
  79. xtype: 'button',
  80. iconCls: 'x-fa fa-text-width',
  81. tooltip: '自适应宽度',
  82. listeners: {
  83. click: this.autoSizeColumns
  84. }
  85. })
  86. }
  87. if (!newConfig.hideClearFilter) {
  88. buttons.push({
  89. xtype: 'button',
  90. tooltip: '清空筛选',
  91. iconCls: 'x-fa fa-filter',
  92. handler: this.clearFilter
  93. })
  94. }
  95. if (!newConfig.hideSaveGridUIConfig) {
  96. buttons.push({
  97. xtype: 'button',
  98. tooltip: '保存表格自定义配置',
  99. iconCls: 'x-fa fa-cog',
  100. handler: this.saveGridUIConfig
  101. })
  102. }
  103. if (!newConfig.hideClearGridUIConfig) {
  104. buttons.push({
  105. xtype: 'button',
  106. tooltip: '还原表格自定义配置',
  107. iconCls: 'x-fa fa-repeat',
  108. handler: this.clearGridUIConfig
  109. })
  110. }
  111. if (!newConfig.hideFootbar) {
  112. if (newConfig.pagination) {
  113. newConfig.bbar = new Ext.PagingToolbar({
  114. // pageSize: newConfig.pageSize, 这个值是无效的
  115. displayInfo: true,
  116. store: this.store,
  117. emptyMsg: '没有记录',
  118. items: [
  119. {
  120. xtype: 'combobox',
  121. tooltip: '分页',
  122. queryMode: 'local',
  123. editable: false,
  124. allowBlank: true,
  125. labelAlign: 'right',
  126. width: 90,
  127. // labelWidth: 30,
  128. listConfig: {
  129. minWidth: null
  130. },
  131. value: 50,
  132. valueField: undefined,
  133. displayField: undefined,
  134. hideClear: true,
  135. store: newConfig.pageSizeOption,
  136. listeners: {
  137. change: (sender, nv, ov) => {
  138. this.store.pageSize = nv;
  139. this.store.loadPage(1);
  140. }
  141. }
  142. },
  143. ...buttons
  144. ]
  145. })
  146. } else {
  147. newConfig.bbar = {
  148. xtype: 'toolbar', overflowHandler: 'menu',
  149. items: [
  150. ...buttons
  151. ]
  152. }
  153. }
  154. }
  155. _.each(newConfig.columns, c => {
  156. const {renderer} = c
  157. if (typeof renderer === 'string' && (
  158. _.startsWith(renderer, "scope.") ||
  159. _.startsWith(renderer, "system."))
  160. ) {
  161. if (newConfig.$initParent) {
  162. if (scope) {
  163. const rendererFn = lookupFn(scope, renderer)
  164. c.renderer = rendererFn
  165. }
  166. }
  167. }
  168. })
  169. this.superclass.constructor.call(this, newConfig)
  170. },
  171. setData(value) {
  172. const me = this
  173. me._setDataReal(value)
  174. },
  175. appendEditRow(record, editRowCol) {
  176. const records = this.add(record)
  177. const recNew = records[0]
  178. this.setSelection(records)
  179. if (typeof editRowCol === 'number') {
  180. const ce = this.findPlugin('cellediting')
  181. this.editingPlugin = ce
  182. ce.startEdit(recNew, editRowCol)
  183. }
  184. },
  185. _setDataReal(value) {
  186. const me = this
  187. me.setStore(new Ext.data.Store({
  188. fields: getFileds(this),
  189. data: value
  190. }))
  191. },
  192. /**
  193. * 轻量级刷新
  194. */
  195. refreshData() {
  196. const store = this.getStore()
  197. if (store) {
  198. store.reload()
  199. }
  200. },
  201. /**
  202. * 为表格强制设置焦点
  203. * @param seq 顺序号
  204. */
  205. focusRow(seq) {
  206. this.setSelection(this.store.getAt(seq))
  207. this.getView().focusRow(seq)
  208. },
  209. /**
  210. * 重新载入数据(重新计算参数)
  211. */
  212. reload(reloadParams = {}) {
  213. const me = this
  214. const {config} = me
  215. if (config.dataSourceCallbackFn) {
  216. // 函数请求刷新
  217. const scope = lookupScope(this)
  218. _.defer(() => {
  219. me.setLoading(true)
  220. })
  221. config.dataSourceCallbackFn.call(scope, me, {
  222. successCallback(value) {
  223. me._setDataReal(value)
  224. _.defer(() => {
  225. me.setLoading(false)
  226. })
  227. me.fireEvent('dataLoadComplete', me, true, value);
  228. },
  229. failCallback(error) {
  230. _.defer(() => {
  231. me.setLoading(false)
  232. })
  233. me.fireEvent('dataLoadComplete', me, false, error);
  234. }
  235. })
  236. return
  237. }
  238. // if (this.store) {
  239. // this.store.reload({aaaa: 1, bbbb: 2})
  240. // }
  241. const {dataSource} = config
  242. if (_.isPlainObject(dataSource) && !window["IS_DESIGN_MODE"]) {
  243. const scope = lookupScope(me)
  244. gridInvokeBuild(scope, me, config, dataSource, reloadParams)
  245. }
  246. },
  247. exportExcelClick(excelExportParams) {
  248. const me = this
  249. const {config} = me
  250. excelExportParams.isExcelExport = true
  251. const scope = lookupScope(me)
  252. const {dataSource} = config
  253. let excelFileName = config.excelFileName || scope.vjson.title || _.uniqueId("excel-")
  254. gridInvokeBuild(scope, me, config, dataSource, excelExportParams, true, (responseData) => {
  255. let page = parseInt(responseData.pagination?.current) || 1
  256. const size = parseInt(responseData.pagination?.size) || me.exportExcelPageSize
  257. const total = parseInt(responseData.pagination?.total) || responseData.data?.length || 0
  258. me.exportExcelCurrentPage = page
  259. me.exportExcelPageSize = size
  260. me.exportExcelTotal = total
  261. if (excelFileName.endsWith(".xlsx")) {
  262. excelFileName = excelFileName.split(".xlsx")[0]
  263. }
  264. excelFileName += "(第" + me.exportExcelCurrentPage + "页,共" + Math.ceil(total / size) + "页、" + total + "条)"
  265. excelFileName += ".xlsx"
  266. const excelData = me.makeExcelData(responseData.data)
  267. LAY_EXCEL.exportExcel(excelData, excelFileName, 'xlsx')
  268. if (page < total / size) {
  269. page++
  270. }
  271. })
  272. },
  273. makeExcelData(jsonData) {
  274. if (!Array.isArray(jsonData) || jsonData.length === 0) {
  275. return
  276. }
  277. const me = this
  278. const data = [];
  279. // 获取表格的列定义
  280. const headerTextArr = []
  281. const headers = []
  282. for (let i = 0; i < me.headerCt.getGridColumns().length; i++) {
  283. const header = me.headerCt.getGridColumns()[i]
  284. if (!header.isHidden()) {
  285. const textStr = _.trim(header.text)
  286. const dataIndexStr = _.trim(header.dataIndex)
  287. if (dataIndexStr) {
  288. if (textStr === '') {
  289. headerTextArr.push(dataIndexStr)
  290. } else {
  291. headerTextArr.push(textStr)
  292. }
  293. headers.push(header)
  294. }
  295. }
  296. }
  297. if (headers.length === 0) {
  298. return
  299. }
  300. data.push(headerTextArr)
  301. for (let i = 0; i < jsonData.length; i++) {
  302. const dataRow = jsonData[i]
  303. const row = []
  304. for (let j = 0; j < headers.length; j++) {
  305. const key = headers[j].dataIndex
  306. let value = dataRow[key]
  307. if (typeof headers[j].renderer === 'function') {
  308. value = headers[j].renderer(value)
  309. }
  310. row.push(value || "")
  311. }
  312. data.push(row)
  313. }
  314. return data
  315. },
  316. initComponent() {
  317. const me = this
  318. const {config} = me
  319. const scope = lookupScope(this)
  320. if (!window["IS_DESIGN_MODE"]) {
  321. // 转换 dataSource 属性
  322. convertDataSource(me, scope, config)
  323. }
  324. this.on({
  325. afterrender(sender) {
  326. const me = this
  327. const {config} = this
  328. const {dataSource} = config
  329. if (config.autoLoad) {
  330. if (config.dataSourceCallbackFn) {
  331. me.reload()
  332. } else if (_.isPlainObject(dataSource)) {
  333. me.reload()
  334. }
  335. }
  336. if (config.contextMenu === true && _.isArray(config.tbar)) {
  337. const vm = this.lookupViewModel()
  338. this.contextMenu = this.add(new Ext.menu.Menu({
  339. viewModel: vm,
  340. items: _.map(config.tbar, item => {
  341. const menuItem = {
  342. ...item
  343. }
  344. if (menuItem.xtype === 'button') {
  345. delete menuItem.xtype
  346. }
  347. return menuItem
  348. })
  349. }))
  350. } else if (_.isPlainObject(config.contextMenu)) {
  351. this.contextMenu = this.add(config.contextMenu)
  352. }
  353. const $dom = $(sender.el.dom)
  354. $dom.on('keydown', (e) => {
  355. me.fireEvent('keydown', me, e,)
  356. }).on('keyup', (e) => {
  357. me.fireEvent('keyup', me, e,)
  358. })
  359. },
  360. itemcontextmenu(view, rec, node, index, e) {
  361. if (this.contextMenu) {
  362. e.stopEvent();
  363. this.contextMenu.show().setLocalXY(e.getXY());
  364. return false;
  365. }
  366. },
  367. // columnmove(sender, column, fromIndex, toIndex, eOpts) {
  368. // this.setColumnConfigCache()
  369. // },
  370. // columnhide(sender, column, eOpts) {
  371. // this.setColumnConfigCache()
  372. // },
  373. // columnshow(sender, column, eOpts) {
  374. // this.setColumnConfigCache()
  375. // },
  376. // columnresize(sender, column, width, eOpts) {
  377. // this.setColumnConfigCache()
  378. // },
  379. destory() {
  380. },
  381. })
  382. if (this.store?.proxy) {
  383. // 为 stores.proxy.buildRequest 做准备
  384. this.store.proxy.$owner = this
  385. }
  386. this.superclass.initComponent.call(this)
  387. },
  388. // 生成列自定义的缓存key
  389. makeColumnConfigCacheKey(config) {
  390. const me = this
  391. const scope = config.$initParent.yvanScope || config.$initParent.lookupReferenceHolder().yvanScope;
  392. let key = "gridColumnCache-" + scope.scopeKey + "-"
  393. if (config.reference) {
  394. key += config.reference
  395. } else {
  396. let subKey = ""
  397. for (let i = 0; i < config.columns.length; i++) {
  398. const column = config.columns[i]
  399. if (column.dataIndex) {
  400. subKey += column.dataIndex
  401. }
  402. }
  403. key += subKey
  404. }
  405. return key
  406. },
  407. getColumnConfigCache() {
  408. const key = this.columnConfigCacheKey
  409. const dataStr = localStorage.getItem(key)
  410. if (dataStr) {
  411. return JSON.parse(dataStr)
  412. }
  413. return ""
  414. },
  415. setColumnConfigCache() {
  416. const key = this.columnConfigCacheKey
  417. const cacheData = []
  418. const columns = this.headerCt.getGridColumns()
  419. let index = 0
  420. for (let i = 0; i < columns.length; i++) {
  421. const column = columns[i]
  422. if (column.dataIndex) {
  423. cacheData.push({
  424. dataIndex: column.dataIndex,
  425. width: column.width,
  426. hidden: column.hidden,
  427. locked: column.locked,
  428. index
  429. })
  430. index++
  431. }
  432. }
  433. localStorage.setItem(key, JSON.stringify(cacheData))
  434. },
  435. autoSizeColumns(sender) {
  436. const grid = sender.up('grid')
  437. // const columns = grid.columns;
  438. // for (let i = 0; i < columns.length; i++) {
  439. // const column = columns[i];
  440. // grid.getView().autoSizeColumn(column);
  441. // column.setWidth(column.getWidth() + 5);
  442. // }
  443. for (let i = 1; i < grid.headerCt.getColumnCount(); i++) {
  444. grid.headerCt.getGridColumns()[i].autoSize(i);
  445. grid.headerCt.getGridColumns()[i].setWidth(grid.headerCt.getGridColumns()[i].getWidth() + 15);
  446. }
  447. },
  448. clearFilter(sender) {
  449. sender.up('grid').filters.clearFilters();
  450. },
  451. saveGridUIConfig(sender) {
  452. const grid = sender.up('grid')
  453. grid.setColumnConfigCache()
  454. msg('保存设置成功!')
  455. },
  456. clearGridUIConfig(sender) {
  457. const grid = sender.up('grid')
  458. const key = grid.columnConfigCacheKey
  459. localStorage.setItem(key, "")
  460. msg('清空设置成功,重新打开后生效!')
  461. },
  462. setLoading(value) {
  463. if (value) {
  464. this.mask('读取中')
  465. } else {
  466. this.unmask()
  467. }
  468. },
  469. exportExcel(sender) {
  470. const rect = sender.btnEl.dom.getBoundingClientRect()
  471. const scope = lookupScope(this)
  472. const grid = sender.up('grid')
  473. const treeMenu = new Ext.menu.Menu(
  474. {
  475. xtype: 'menu',
  476. floated: false,
  477. width: 300,
  478. docked: 'left',
  479. items: [{
  480. xtype: "textfield",
  481. fieldLabel: '当前导出页',
  482. maskRe: /[0-9]/,
  483. value: grid.exportExcelCurrentPage,
  484. listeners: {
  485. render: (sender) => {
  486. grid.exportExcelCurrentPageCmp = sender
  487. },
  488. change: (sender, value) => {
  489. let v = parseInt(value)
  490. if (isNaN(v) || v === 0) {
  491. window['system'].msg("页码不能为0")
  492. v = 1
  493. sender.setValue(v)
  494. }
  495. const size = parseInt(grid.exportExcelPageSize)
  496. const total = parseInt(grid.exportExcelTotal)
  497. if (v > total / size) {
  498. v = parseInt(total / size + "")
  499. }
  500. grid.exportExcelCurrentPage = v + ""
  501. }
  502. }
  503. }, {
  504. xtype: "textfield",
  505. fieldLabel: '导出页大小',
  506. maskRe: /[0-9]/,
  507. value: grid.exportExcelPageSize,
  508. listeners: {
  509. render: (sender) => {
  510. grid.exportExcelPageSizeCmp = sender
  511. },
  512. change: (sender, value) => {
  513. let v = parseInt(value)
  514. if (isNaN(v) || v === 0) {
  515. window['system'].msg("导出页大小不能为0")
  516. v = defaultGrid.exportExcelPageSize
  517. sender.setValue(v);
  518. }
  519. let page = parseInt(grid.exportExcelCurrentPage)
  520. const total = parseInt(grid.exportExcelTotal)
  521. if (page > total / v) {
  522. page = parseInt(total / v + "") + 1
  523. grid.exportExcelCurrentPageCmp.setValue(page)
  524. }
  525. grid.exportExcelPageSize = v + ""
  526. }
  527. }
  528. }, {
  529. xtype: "textfield",
  530. fieldLabel: '总条数',
  531. value: grid.exportExcelTotal,
  532. readOnly: true
  533. }, {
  534. text: '导出',
  535. iconCls: 'x-fa fa-download',
  536. listeners: {
  537. click: (sender, value) => {
  538. grid.exportExcelClick({
  539. exportExcelPageSize: grid.exportExcelPageSize,
  540. exportExcelCurrentPage: grid.exportExcelCurrentPage
  541. })
  542. }
  543. }
  544. }]
  545. }
  546. );
  547. treeMenu.showAt(rect.left, rect.top - 120);
  548. // for (let i = 1; i < grid.headerCt.getColumnCount(); i++) {
  549. // grid.headerCt.getGridColumns()[i].autoSize(i);
  550. // grid.headerCt.getGridColumns()[i].setWidth(grid.headerCt.getGridColumns()[i].getWidth() + 15);
  551. // }
  552. }
  553. // reload() {
  554. // dataSourceReload(this)
  555. // },
  556. })
  557. PropertyDescriptionTable.set(
  558. 'yvgrid',
  559. new PropertyDescription(YvBase, {
  560. props: [
  561. fieldLabel, value, disabled,
  562. gravity, tooltip, metaId, width, height
  563. ],
  564. })
  565. )
  566. }
  567. /**
  568. * 获取 columns 中所有的 dataIndex
  569. */
  570. function getFileds(newConfig) {
  571. const fields = []
  572. _.forEach(newConfig.columns, c => {
  573. if (c.dataIndex) {
  574. fields.push(c.dataIndex)
  575. }
  576. })
  577. return fields
  578. }
  579. function convertDataSource(sender, scope, newConfig) {
  580. if (typeof newConfig.store !== 'undefined') {
  581. // 有 store 属性的情况下,不做任何事
  582. return
  583. }
  584. if (typeof newConfig.dataSource === 'undefined') {
  585. // 没有定义 dataSource 的情况下,不做任何事
  586. return
  587. }
  588. if (_.isArray(newConfig.data)) {
  589. // 有 data 属性赋值的情况下
  590. newConfig.store = {
  591. fields: getFileds(newConfig),
  592. data: newConfig.data
  593. }
  594. delete newConfig.data
  595. return
  596. }
  597. let {dataSource} = newConfig
  598. if (typeof dataSource === 'string') {
  599. // dataSource 是字符串的情况下,找到成员函数
  600. dataSource = lookupFn(scope, dataSource)
  601. }
  602. if (typeof dataSource === 'function') {
  603. // dataSource 是函数的情况下,在 afterrender 之后进行回调
  604. newConfig.store = new Ext.data.Store({
  605. fields: getFileds(newConfig),
  606. // data: [],
  607. autoLoad: true,
  608. proxy: {
  609. type: 'memory',
  610. data: [],
  611. // reader: {
  612. // type: 'json',
  613. // rootProperty: 'users'
  614. // }
  615. }
  616. })
  617. newConfig.dataSourceCallbackFn = dataSource
  618. return
  619. }
  620. // throw new TypeError('无法识别的调用方法')
  621. }