import _ from 'lodash' import {grid} from '../Defaults' import {baseConfig} from "./base"; import {lookupFn, lookupScope} from "../lib/lib"; import LAY_EXCEL from "lay-excel" import { disabled, fieldLabel, gravity, height, metaId, PropertyDescriptionTable, tooltip, value, width, YvBase } from "../PropertyDescriptionTable"; import {PropertyDescription} from "../PropertyDescription"; import {gridInvokeBuild} from "./stores"; import {msg} from 'src/message'; const defaultGrid = grid export default function () { Ext.define('Yvan.Grid', { extend: 'Ext.grid.Panel', xtype: 'yvgrid', constructor(config) { const me = this const {dataSource} = config if (!window["IS_DESIGN_MODE"]) { this.columnConfigCacheKey = this.makeColumnConfigCacheKey(config) if (Array.isArray(config.columns) && config.columns.length > 0) { const cacheData = this.getColumnConfigCache() if (Array.isArray(cacheData) && cacheData.length > 0) { const newColumns = [] for (let j = 0; j < cacheData.length; j++) { const itData = cacheData[j] for (let i = 0; i < config.columns.length; i++) { const column = config.columns[i] if (itData.dataIndex === column.dataIndex) { if (itData.width) { column.width = itData.width } column.hidden = itData.hidden column.locked = itData.locked newColumns.push(column) break } } } config.columns = newColumns } } } const newConfig = _.defaultsDeep({ // 强制性属性 }, baseConfig(config, 'row-item'), config, grid) // 在面板上的组件 const scope = newConfig.$initParent.yvanScope || newConfig.$initParent.lookupReferenceHolder().yvanScope; const buttons = [] const {getRowClass} = newConfig if (typeof getRowClass === 'string' && ( _.startsWith(getRowClass, "scope.") || _.startsWith(getRowClass, "system.")) ) { const fn = lookupFn(scope, getRowClass) _.set(newConfig, 'viewConfig.getRowClass', fn) } if (!newConfig.hideExport) { buttons.push({ xtype: 'button', tooltip: '导出Excel', iconCls: 'x-fa fa-download', listeners: { click: this.exportExcel }, }) } if (!newConfig.hideAutoSize) { buttons.push({ xtype: 'button', iconCls: 'x-fa fa-text-width', tooltip: '自适应宽度', listeners: { click: this.autoSizeColumns } }) } if (!newConfig.hideClearFilter) { buttons.push({ xtype: 'button', tooltip: '清空筛选', iconCls: 'x-fa fa-filter', handler: this.clearFilter }) } if (!newConfig.hideSaveGridUIConfig) { buttons.push({ xtype: 'button', tooltip: '保存表格自定义配置', iconCls: 'x-fa fa-cogs', handler: this.saveGridUIConfig }) } if (!newConfig.hideClearGridUIConfig) { buttons.push({ xtype: 'button', tooltip: '还原表格自定义配置', iconCls: 'x-fa fa-reply-all', handler: this.clearGridUIConfig }) } if (!newConfig.hideFootbar) { if (newConfig.pagination) { newConfig.bbar = new Ext.PagingToolbar({ // pageSize: newConfig.pageSize, 这个值是无效的 displayInfo: true, store: this.store, emptyMsg: '没有记录', items: [ { xtype: 'combobox', tooltip: '分页', queryMode: 'local', editable: false, allowBlank: true, labelAlign: 'right', width: 90, // labelWidth: 30, listConfig: { minWidth: null }, value: 50, valueField: undefined, displayField: undefined, hideClear: true, store: newConfig.pageSizeOption, listeners: { change: (sender, nv, ov) => { this.store.pageSize = nv; this.store.loadPage(1); } } }, ...buttons ] }) } else { newConfig.bbar = { xtype: 'toolbar', overflowHandler: 'menu', items: [ { xtype: 'button', tooltip: '刷新', iconCls: 'x-fa fa-refresh', handler: () => { this.reload() } }, '-', ...buttons, ] } } } _.each(newConfig.columns, c => { const {renderer} = c if (typeof renderer === 'string' && ( _.startsWith(renderer, "scope.") || _.startsWith(renderer, "system.")) ) { if (newConfig.$initParent) { if (scope) { const rendererFn = lookupFn(scope, renderer) c.renderer = rendererFn } } } }) this.superclass.constructor.call(this, newConfig) this.store.pageSize = newConfig.pageSize }, setData(value) { const me = this me._setDataReal(value) }, /** * 添加行,并进入编辑状态 * @param record 新行的属性集 * @param editRowCol 要编辑的列序号 */ appendEditRow(record, editRowCol) { const records = this.getStore().add(record) const recNew = records[0] this.setSelection(records) if (typeof editRowCol === 'number') { const ce = this.findPlugin('cellediting') this.editingPlugin = ce ce.startEdit(recNew, editRowCol) } }, /** * 移除行 * @param record 如果记录传空,就是当前选中的行 */ removeEditRow(record) { if (!record) { record = this.selection } if (!record) { msg('请选中要删除的行') return } this.getStore().remove(record) }, /** * 获取全部原始数据 */ getDataRows() { return this.getStore().data.items.map(r => r.data) }, _transform(data) { // 无论是 grid._setDataReal 还是 stores.gridInvokeBuild 都会走这个函数,设值前都可以改变表格值 _.forEach(data, row => { row._origin = _.clone(row) }) }, _setDataReal(value) { const me = this this._transform(value) me.setStore(new Ext.data.Store({ fields: getFileds(this), data: value })) }, /** * 轻量级刷新 */ refreshData() { const store = this.getStore() if (store) { store.reload() } }, /** * 为表格强制设置焦点 * @param seq 顺序号 */ focusRow(seq) { this.setSelection(this.store.getAt(seq)) this.getView().focusRow(seq) }, /** * 重新载入数据(重新计算参数) */ reload(reloadParams = {}) { const me = this const {config} = me if (config.dataSourceCallbackFn) { // 函数请求刷新 const scope = lookupScope(this) _.defer(() => { me.setLoading(true) }) config.dataSourceCallbackFn.call(scope, me, { successCallback(value) { me._setDataReal(value) _.defer(() => { me.setLoading(false) }) me.fireEvent('dataLoadComplete', me, true, value); }, failCallback(error) { _.defer(() => { me.setLoading(false) }) me.fireEvent('dataLoadComplete', me, false, error); } }) return } // if (this.store) { // this.store.reload({aaaa: 1, bbbb: 2}) // } const {dataSource} = config if (_.isPlainObject(dataSource) && !window["IS_DESIGN_MODE"]) { const scope = lookupScope(me) gridInvokeBuild(scope, me, config, dataSource, reloadParams) } }, exportCurrentExcelClick() { const me = this const {config} = me const scope = lookupScope(me) let excelFileName = config.excelFileName || scope.vjson.title || _.uniqueId("excel-") if (excelFileName.endsWith(".xlsx")) { excelFileName = excelFileName.split(".xlsx")[0] } excelFileName += ".xlsx" const rowsAll = this.getStore().getData().items?.map(r => r.data); const excelData = me.makeExcelData(rowsAll) LAY_EXCEL.exportExcel(excelData, excelFileName, 'xlsx') }, exportExcelClick(excelExportParams) { const me = this const {config} = me excelExportParams.isExcelExport = true const scope = lookupScope(me) const {dataSource} = config let excelFileName = config.excelFileName || scope.vjson.title || _.uniqueId("excel-") gridInvokeBuild(scope, me, config, dataSource, excelExportParams, true, (responseData) => { let page = parseInt(responseData.pagination?.current) || 1 const size = parseInt(responseData.pagination?.size) || me.exportExcelPageSize const total = parseInt(responseData.pagination?.total) || responseData.data?.length || 0 me.exportExcelCurrentPage = page me.exportExcelPageSize = size me.exportExcelTotal = total if (excelFileName.endsWith(".xlsx")) { excelFileName = excelFileName.split(".xlsx")[0] } excelFileName += "(第" + me.exportExcelCurrentPage + "页,共" + Math.ceil(total / size) + "页、" + total + "条)" excelFileName += ".xlsx" const excelData = me.makeExcelData(responseData.data) LAY_EXCEL.exportExcel(excelData, excelFileName, 'xlsx') if (page < total / size) { page++ } }) }, makeExcelData(jsonData) { if (!Array.isArray(jsonData) || jsonData.length === 0) { return } const me = this const data = []; // 获取表格的列定义 const headerTextArr = [] const headers = [] for (let i = 0; i < me.headerCt.getGridColumns().length; i++) { const header = me.headerCt.getGridColumns()[i] if (!header.isHidden()) { const textStr = _.trim(header.text) const dataIndexStr = _.trim(header.dataIndex) if (dataIndexStr) { if (textStr === '') { headerTextArr.push(dataIndexStr) } else { headerTextArr.push(textStr) } headers.push(header) } } } if (headers.length === 0) { return } data.push(headerTextArr) for (let i = 0; i < jsonData.length; i++) { const dataRow = jsonData[i] const row = [] for (let j = 0; j < headers.length; j++) { const key = headers[j].dataIndex let value = dataRow[key] if (typeof headers[j].renderer === 'function') { value = headers[j].renderer(value) } row.push(value || "") } data.push(row) } return data }, initComponent() { const me = this const {config} = me const scope = lookupScope(this) if (!window["IS_DESIGN_MODE"]) { // 转换 dataSource 属性 convertDataSource(me, scope, config) } this.on({ afterrender(sender) { const me = this const {config} = this const {dataSource} = config if (config.autoLoad) { if (config.dataSourceCallbackFn) { me.reload() } else if (_.isPlainObject(dataSource)) { me.reload() } } if (config.contextMenu === true && _.isArray(config.tbar)) { const vm = this.lookupViewModel() this.contextMenu = this.add(new Ext.menu.Menu({ viewModel: vm, items: _.map(config.tbar, item => { const menuItem = { ...item } if (menuItem.xtype === 'button') { delete menuItem.xtype } return menuItem }) })) } else if (_.isPlainObject(config.contextMenu)) { this.contextMenu = this.add(config.contextMenu) } const $dom = $(sender.el.dom) $dom.on('keydown', (e) => { me.fireEvent('keydown', me, e,) }).on('keyup', (e) => { me.fireEvent('keyup', me, e,) }) }, itemcontextmenu(view, rec, node, index, e) { if (this.contextMenu) { e.stopEvent(); this.contextMenu.show().setLocalXY(e.getXY()); return false; } }, // columnmove(sender, column, fromIndex, toIndex, eOpts) { // this.setColumnConfigCache() // }, // columnhide(sender, column, eOpts) { // this.setColumnConfigCache() // }, // columnshow(sender, column, eOpts) { // this.setColumnConfigCache() // }, // columnresize(sender, column, width, eOpts) { // this.setColumnConfigCache() // }, destory() { }, }) if (this.store?.proxy) { // 为 stores.proxy.buildRequest 做准备 this.store.proxy.$owner = this } this.superclass.initComponent.call(this) }, // 生成列自定义的缓存key makeColumnConfigCacheKey(config) { const me = this const scope = config.$initParent.yvanScope || config.$initParent.lookupReferenceHolder().yvanScope; let key = "gridColumnCache-" + scope.scopeKey + "-" if (config.reference) { key += config.reference } else { let subKey = "" for (let i = 0; i < config.columns.length; i++) { const column = config.columns[i] if (column.dataIndex) { subKey += column.dataIndex } } key += subKey } return key }, getColumnConfigCache() { const key = this.columnConfigCacheKey const dataStr = localStorage.getItem(key) if (dataStr) { return JSON.parse(dataStr) } return "" }, setColumnConfigCache() { const key = this.columnConfigCacheKey const cacheData = [] const columns = this.headerCt.getGridColumns() let index = 0 for (let i = 0; i < columns.length; i++) { const column = columns[i] if (column.dataIndex) { cacheData.push({ dataIndex: column.dataIndex, width: column.width, hidden: column.hidden, locked: column.locked, index }) index++ } } localStorage.setItem(key, JSON.stringify(cacheData)) }, autoSizeColumns(sender) { const grid = sender.up('grid') // const columns = grid.columns; // for (let i = 0; i < columns.length; i++) { // const column = columns[i]; // grid.getView().autoSizeColumn(column); // column.setWidth(column.getWidth() + 5); // } for (let i = 1; i < grid.headerCt.getColumnCount(); i++) { grid.headerCt.getGridColumns()[i].autoSize(i); grid.headerCt.getGridColumns()[i].setWidth(grid.headerCt.getGridColumns()[i].getWidth() + 15); } }, clearFilter(sender) { sender.up('grid').filters.clearFilters(); }, saveGridUIConfig(sender) { const grid = sender.up('grid') grid.setColumnConfigCache() msg('保存设置成功!') }, clearGridUIConfig(sender) { const grid = sender.up('grid') const key = grid.columnConfigCacheKey localStorage.setItem(key, "") msg('清空设置成功,重新打开后生效!') }, setLoading(value) { if (value) { this.mask('读取中') } else { this.unmask() } }, exportExcel(sender) { const rect = sender.btnEl.dom.getBoundingClientRect() const scope = lookupScope(this) const grid = sender.up('grid') const treeMenu = new Ext.menu.Menu( { xtype: 'menu', floated: false, width: 300, docked: 'left', items: [{ text: '导出表格当前的数据', iconCls: 'x-fa fa-download', listeners: { click: (sender, value) => { grid.exportCurrentExcelClick() } } }, { xtype: "textfield", fieldLabel: '当前导出页', maskRe: /[0-9]/, value: grid.exportExcelCurrentPage, listeners: { render: (sender) => { grid.exportExcelCurrentPageCmp = sender }, change: (sender, value) => { let v = parseInt(value) if (isNaN(v) || v === 0) { window['system'].msg("页码不能为0") v = 1 sender.setValue(v) } const size = parseInt(grid.exportExcelPageSize) const total = parseInt(grid.exportExcelTotal) if (v > total / size) { v = parseInt(total / size + "") } grid.exportExcelCurrentPage = v + "" } } }, { xtype: "textfield", fieldLabel: '导出页大小', maskRe: /[0-9]/, value: grid.exportExcelPageSize, listeners: { render: (sender) => { grid.exportExcelPageSizeCmp = sender }, change: (sender, value) => { let v = parseInt(value) if (isNaN(v) || v === 0) { window['system'].msg("导出页大小不能为0") v = defaultGrid.exportExcelPageSize sender.setValue(v); } if (v >= 10000) { window['system'].msg("导出页大小不能大于10000") v = 10000 sender.setValue(v); } let page = parseInt(grid.exportExcelCurrentPage) const total = parseInt(grid.exportExcelTotal) if (page > total / v) { page = parseInt(total / v + "") + 1 grid.exportExcelCurrentPageCmp.setValue(page) } grid.exportExcelPageSize = v + "" } } }, { xtype: "textfield", fieldLabel: '总条数', value: grid.exportExcelTotal, readOnly: true }, { text: '导出', iconCls: 'x-fa fa-download', listeners: { click: (sender, value) => { grid.exportExcelClick({ exportExcelPageSize: grid.exportExcelPageSize, exportExcelCurrentPage: grid.exportExcelCurrentPage }) } } }] } ); treeMenu.showAt(rect.left, rect.top - 120); // for (let i = 1; i < grid.headerCt.getColumnCount(); i++) { // grid.headerCt.getGridColumns()[i].autoSize(i); // grid.headerCt.getGridColumns()[i].setWidth(grid.headerCt.getGridColumns()[i].getWidth() + 15); // } } // reload() { // dataSourceReload(this) // }, }) PropertyDescriptionTable.set( 'yvgrid', new PropertyDescription(YvBase, { props: [ fieldLabel, value, disabled, gravity, tooltip, metaId, width, height ], }) ) } /** * 获取 columns 中所有的 dataIndex */ function getFileds(newConfig) { const fields = [] _.forEach(newConfig.columns, c => { if (c.dataIndex) { fields.push(c.dataIndex) } }) return fields } function convertDataSource(sender, scope, newConfig) { if (typeof newConfig.store !== 'undefined') { // 有 store 属性的情况下,不做任何事 return } if (typeof newConfig.dataSource === 'undefined') { // 没有定义 dataSource 的情况下,不做任何事 return } if (_.isArray(newConfig.data)) { // 有 data 属性赋值的情况下 newConfig.store = { fields: getFileds(newConfig), data: newConfig.data } delete newConfig.data return } let {dataSource} = newConfig if (typeof dataSource === 'string') { // dataSource 是字符串的情况下,找到成员函数 dataSource = lookupFn(scope, dataSource) } if (typeof dataSource === 'function') { // dataSource 是函数的情况下,在 afterrender 之后进行回调 newConfig.store = new Ext.data.Store({ fields: getFileds(newConfig), // data: [], autoLoad: true, proxy: { type: 'memory', data: [], // reader: { // type: 'json', // rootProperty: 'users' // } } }) newConfig.dataSourceCallbackFn = dataSource return } // throw new TypeError('无法识别的调用方法') }