Sfoglia il codice sorgente

ext-mobile 添加表格支持、完成表格datasource和示例页面

yuliang 3 anni fa
parent
commit
85160d3334

+ 5 - 1
src/Defaults.ts

@@ -108,7 +108,7 @@ export const form = {
 }
 
 export const column = {
-    filter: {type: 'string'},
+    menuDisabled: true
 }
 
 export const grid = {
@@ -202,6 +202,10 @@ export const fieldSet = {
     },
 }
 
+export const storeAjax = {
+    timeout: 60000
+}
+
 export const panel = {}
 
 export const splitter = {}

+ 106 - 0
src/PropertyDescription.ts

@@ -0,0 +1,106 @@
+import _ from 'lodash'
+
+export type GroupType = 'css' | 'data' | 'bind' | 'common' | 'event'
+
+export interface PropertyValue {
+  /**
+   * 属性名
+   */
+  name: string
+
+  /**
+   * 默认值
+   */
+  default: any
+
+  /**
+   * 隶属分组
+   */
+  group: GroupType
+
+  /**
+   * 描述
+   */
+  desc: string
+
+  /**
+   * 取值范围
+   */
+  type:
+    | 'boolean'
+    | 'number'
+    | 'string'
+    | 'object'
+    | 'dataSource'
+    | 'valid'
+    | 'format'
+    | Array<string>
+    | 'widget'
+
+  eventParamter?: string[]
+
+  eventResult?: string
+
+  eventDoc?:(vjson:any)=>string
+
+  expr?: boolean,
+}
+
+export interface EventValue {
+  /**
+   * 属性名
+   */
+  name: string
+
+  /**
+   * 描述
+   */
+  desc: string
+}
+
+export interface PropertyDescriptionInterface {
+  props: PropertyValue[]
+  events?: EventValue[]
+}
+
+export class PropertyDescription {
+  propertyes: PropertyDescriptionInterface = {
+    props: [],
+    events: []
+  }
+
+  constructor(...args: PropertyDescriptionInterface[]) {
+    _.each(args, arg => {
+      this.merge(arg)
+    })
+  }
+
+  merge(pd: PropertyDescriptionInterface) {
+    this.propertyes.props = <any>(
+      _.uniqBy([...this.propertyes.props, ...pd.props], 'name')
+    )
+    if (pd.events) {
+      if (this.propertyes.events) {
+        this.propertyes.events = <any>(
+          _.uniqBy([...this.propertyes.events, ...pd.events], 'name')
+        )
+      } else {
+        this.propertyes.events = <any>_.uniqBy([...pd.events], 'name')
+      }
+    }
+  }
+
+  /**
+   * 根据分组名 获取属性定义
+   */
+  getPropsByGroup(name: GroupType): PropertyValue[] {
+    return _.filter(this.propertyes.props, i => i.group === name)
+  }
+
+  /**
+   * 获取全部事件
+   */
+  getEvents(): EventValue[] {
+    return this.propertyes.events!
+  }
+}

+ 507 - 0
src/PropertyDescriptionTable.ts

@@ -0,0 +1,507 @@
+import _ from 'lodash'
+import {PropertyDescription, PropertyDescriptionInterface, PropertyValue} from './PropertyDescription'
+
+export const PropertyDescriptionTable = new Map<String, PropertyDescription>()
+
+PropertyDescriptionTable.set(
+    'tabCell',
+    new PropertyDescription({
+        props: [
+            {
+                name: 'header',
+                default: '',
+                group: 'common',
+                desc: '选项卡名称',
+                type: 'string'
+            },
+            {
+                name: 'close',
+                default: '',
+                group: 'common',
+                desc: '是否允许关闭',
+                type: 'boolean'
+            },
+        ]
+    })
+)
+
+export const width: PropertyValue = {
+    name: 'width',
+    default: '',
+    group: 'common',
+    desc: '宽',
+    type: 'number'
+}
+
+export const height: PropertyValue = {
+    name: 'height',
+    default: '',
+    group: 'common',
+    desc: '高',
+    type: 'number'
+}
+
+export const fieldLabel: PropertyValue = {
+    name: 'fieldLabel',
+    default: '',
+    group: 'common',
+    desc: '文本描述',
+    type: 'string',
+    expr: true,
+};
+
+export const text: PropertyValue = {
+    name: 'text',
+    default: '',
+    group: 'common',
+    desc: '文本描述',
+    type: 'string',
+    expr: true,
+};
+
+export const iconCls: PropertyValue = {
+    name: 'iconCls',
+    default: '',
+    group: 'common',
+    desc: '图标',
+    type: 'string',
+    expr: true,
+};
+
+export const borderless: PropertyValue = {
+    name: 'borderless',
+    default: '',
+    group: 'css',
+    desc: '是否有边框',
+    type: 'boolean',
+};
+export const labelAlign: PropertyValue = {
+    name: 'labelAlign',
+    default: '',
+    group: 'common',
+    desc: '描述对齐方式',
+    type: ['left', 'right', 'center']
+};
+export const type: PropertyValue = {
+    name: 'type',
+    default: '',
+    group: 'css',
+    desc: '容器内部线类型',
+    type: ['line', 'clean', 'wide', 'space', 'form']
+};
+export const labelWidth: PropertyValue = {
+    name: 'labelWidth',
+    default: undefined,
+    group: 'common',
+    desc: '文本宽度',
+    type: 'number'
+};
+export const gravity: PropertyValue = {
+    name: 'gravity',
+    default: 1,
+    group: 'common',
+    desc: '占位权重',
+    type: 'number'
+};
+export const hidden: PropertyValue = {
+    name: 'hidden',
+    default: false,
+    group: 'common',
+    desc: '隐藏',
+    type: 'boolean',
+    expr: true,
+};
+export const readonly: PropertyValue = {
+    name: 'readonly',
+    default: false,
+    group: 'common',
+    desc: '只读',
+    type: 'boolean',
+    expr: true,
+};
+export const disabled: PropertyValue = {
+    name: 'disabled',
+    default: false,
+    group: 'common',
+    desc: '禁用',
+    type: 'boolean',
+    expr: true,
+};
+export const loading: PropertyValue = {
+    name: 'loading',
+    default: false,
+    group: 'common',
+    desc: '载入中',
+    type: 'boolean',
+    expr: true,
+};
+export const required: PropertyValue = {
+    name: 'required',
+    default: false,
+    group: 'common',
+    desc: '必填',
+    type: 'boolean',
+    expr: true,
+};
+export const value: PropertyValue = {
+    name: 'value',
+    default: '',
+    group: 'common',
+    desc: '字段值',
+    type: 'string',
+    expr: true,
+};
+export const prompt: PropertyValue = {
+    name: 'prompt',
+    default: '请输入',
+    group: 'common',
+    desc: '水印',
+    type: 'string'
+};
+export const validType: PropertyValue = {
+    name: 'validType',
+    default: '',
+    group: 'common',
+    desc: '校验类型',
+    type: 'valid'
+};
+export const metaId: PropertyValue = {
+    name: 'metaId',
+    default: '',
+    group: 'bind',
+    desc: '元数据ID',
+    type: 'string'
+};
+export const template: PropertyValue = {
+    name: 'template',
+    default: '',
+    group: 'common',
+    desc: 'HTML内容',
+    type: 'string',
+    expr: true,
+}
+
+export const tooltip: PropertyValue = {
+    name: 'tooltip',
+    default: '',
+    group: 'common',
+    desc: '悬停提示',
+    type: 'string'
+}
+
+export const onClick: PropertyValue = {
+    name: 'onClick',
+    default: '',
+    group: 'event',
+    desc: '点击事件',
+    eventParamter: [
+        'sender: YvanUI.CtlButton',
+        'e: any'
+    ],
+    eventDoc(vjson) {
+        return (vjson['text'] ? vjson['text'] : '按钮') + '被点击后触发';
+    },
+    type: 'string'
+}
+
+PropertyDescriptionTable.set(
+    'rows',
+    new PropertyDescription({
+        props: [
+            gravity,
+            width
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'cols',
+    new PropertyDescription({
+        props: [
+            gravity,
+            height
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'window',
+    new PropertyDescription({
+        props: [
+            {
+                name: '_designMode',
+                default: 'module',
+                group: 'common',
+                desc: '设计类型',
+                type: ['none', 'dialog', 'scroll-dialog']
+            },
+            {
+                name: 'title',
+                default: '',
+                group: 'common',
+                desc: '对话框标题',
+                type: 'string'
+            },
+            {
+                name: 'modal',
+                default: '',
+                group: 'common',
+                desc: '是否模态',
+                type: 'boolean'
+            },
+            _.merge(_.cloneDeep(width), {default: 200}),
+            _.merge(_.cloneDeep(height), {default: 200}),
+            {
+                name: 'top',
+                default: 200,
+                group: 'common',
+                desc: '顶像素',
+                type: 'number'
+            },
+            {
+                name: 'left',
+                default: 200,
+                group: 'common',
+                desc: '左像素',
+                type: 'number'
+            }
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'layout',
+    new PropertyDescription({
+        props: [
+            {
+                name: 'borderless',
+                default: true,
+                group: 'css',
+                desc: '有无边框',
+                type: 'boolean'
+            },
+            {
+                name: 'type',
+                default: '',
+                group: 'css',
+                desc: '布局类型',
+                type: ['line', 'clean', 'wide', 'space', 'form']
+            },
+            {
+                name: '_designMode',
+                default: 'module',
+                group: 'common',
+                desc: '设计类型',
+                type: ['none', 'module', 'scroll-module']
+            }
+        ]
+    })
+)
+
+export const YvBase: PropertyDescriptionInterface = {
+    props: [
+        {
+            name: 'bind',
+            default: '',
+            group: 'bind',
+            desc: '实体类名',
+            type: 'string'
+        },
+        {
+            name: 'ref',
+            default: '',
+            group: 'bind',
+            desc: '控件名',
+            type: 'string'
+        },
+        {
+            name: 'css',
+            default: '',
+            group: 'css',
+            desc: '样式类名',
+            type: 'string'
+        },
+        {
+            name: 'hidden',
+            default: false,
+            group: 'common',
+            desc: '是否显示',
+            type: 'boolean'
+        },
+        {
+            name: 'padding',
+            default: undefined,
+            group: 'css',
+            desc: '内边距',
+            type: 'object'
+        },
+        {
+            name: 'margin',
+            default: undefined,
+            group: 'css',
+            desc: '外边距',
+            type: 'object'
+        },
+        {
+            name: 'ff',
+            default: 0,
+            group: 'common',
+            desc: '自动定焦时间',
+            type: 'number'
+        },
+        width, height,
+        {
+            name: 'autowidth',
+            default: false,
+            group: 'common',
+            desc: '自动计算宽度',
+            type: 'boolean'
+        },
+        {
+            name: 'autoheight',
+            default: false,
+            group: 'common',
+            desc: '自动计算高度',
+            type: 'boolean'
+        },
+    ],
+    events: [
+        {name: 'onRender', desc: '第一次控件被渲染时触发'}
+    ]
+}
+
+export const YvDataSource: PropertyDescriptionInterface = {
+    props: [
+        {
+            name: 'type',
+            default: '',
+            group: 'data',
+            desc: '数据源类型',
+            type: 'dataSource'
+        }
+    ],
+    events: [{name: 'onDataComplete', desc: '数据绑定完成后触发'}]
+}
+
+PropertyDescriptionTable.set(
+    'template',
+    new PropertyDescription(YvBase, {
+        props: [
+            template
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'fieldset',
+    new PropertyDescription(YvBase, {
+        props: [
+            {
+                name: 'label',
+                default: '',
+                group: 'common',
+                desc: '字段组标题',
+                type: 'string'
+            }
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'iframe',
+    new PropertyDescription(YvBase, {
+        props: [
+            {
+                name: 'src',
+                default: '',
+                group: 'common',
+                desc: '地址路径',
+                type: 'string'
+            }
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'uploader',
+    new PropertyDescription(YvBase, {
+        props: [
+            width, height,
+            {
+                name: 'value',
+                default: '上传',
+                group: 'common',
+                desc: '文本描述',
+                type: 'string'
+            },
+            {
+                name: 'upload',
+                default: '/upload',
+                group: 'common',
+                desc: '上传地址',
+                type: 'string'
+            },
+        ],
+        events: [
+            {name: 'onFileUpload', desc: '文件上传成功结束时触发'},
+            {name: 'onFileUploadError', desc: '在上传过程中发生服务器端错误时触发'},
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'viewer',
+    new PropertyDescription(YvBase, {
+        props: [
+            value,
+            gravity,
+            {
+                name: 'imgWidth',
+                default: '',
+                group: 'common',
+                desc: '图片宽',
+                type: 'string'
+            },
+            {
+                name: 'imgHeight',
+                default: '',
+                group: 'common',
+                desc: '图片高',
+                type: 'string'
+            }
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'image',
+    new PropertyDescription(YvBase, {
+        props: [
+            value,
+            gravity,
+            {
+                name: 'imgWidth',
+                default: '',
+                group: 'common',
+                desc: '图片宽',
+                type: 'string'
+            },
+            {
+                name: 'imgHeight',
+                default: '',
+                group: 'common',
+                desc: '图片高',
+                type: 'string'
+            }
+        ]
+    })
+)
+
+PropertyDescriptionTable.set(
+    'scrollview',
+    new PropertyDescription(YvBase, {
+        props: [
+            width, height
+        ]
+    })
+)

+ 460 - 0
src/controls/grid.js

@@ -0,0 +1,460 @@
+import _ from 'lodash'
+import {grid} from '../Defaults'
+import {baseConfig} from "./base";
+import {lookupFn, lookupScope} from "../lib/lib";
+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.Grid',
+        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({
+                // 强制性属性 bug.
+                // lock 属性会造成 Cannot read properties of undefined (reading 'els')
+                // enableLocking: false,
+                // syncRowHeight: false,
+
+            }, 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.hideAutoSize) {
+                buttons.push({
+                    xtype: 'button',
+                    iconCls: 'x-fa fa-text-width',
+                    tooltip: '自适应宽度',
+                    listeners: {
+                        click: this.autoSizeColumns
+                    }
+                })
+            }
+
+            _.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 要编辑的列序号,或 dataIndex 的名称
+         */
+        appendEditRow(record, editRowCol) {
+            const records = this.getStore().add(record)
+            const recNew = records[0]
+            this.setSelection(records)
+
+            if (typeof editRowCol === 'string' && editRowCol) {
+                editRowCol = this.columns.findIndex((c) => c.dataIndex === editRowCol)
+            }
+
+            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)
+        },
+
+        getEditRecord() {
+            const me = this
+            const editingPlugin = me.editingPlugin || me.ownerGrid.editingPlugin
+            const rowIdx = editingPlugin?.activeEditor?.context?.rowIdx
+
+            let record;
+            if (!rowIdx) {
+                record = me.getSelectionModel().getLastSelected()
+            } else {
+                record = me.store.getAt(rowIdx)
+            }
+
+            return record
+        },
+
+        getEditRow() {
+            const me = this
+            return me.getEditRecord()?.data
+        },
+
+        setEditRow(rowValues) {
+            const me = this
+            const record = me.getEditRecord()
+            if (record) {
+                _.forOwn(rowValues, (v, k) => {
+                    record.set(k, v)
+                })
+            }
+        },
+
+        _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)
+            }
+        },
+
+        initialize() {
+            const me = this
+            const {config} = me
+            const scope = lookupScope(this)
+
+            if (!window["IS_DESIGN_MODE"]) {
+                // 转换 dataSource 属性
+                convertDataSource(me, scope, config)
+            }
+
+            // this.on({
+            //
+            //
+            //
+            //     destory() {
+            //     },
+            // })
+
+
+            if (this.store?.proxy) {
+                // 为 stores.proxy.buildRequest 做准备
+                this.store.proxy.$owner = this
+            }
+
+            this.superclass.initialize.call(this)
+        },
+
+        afterRender() {
+            const me = this
+            const {config} = this
+            const {dataSource} = config
+
+            if (config.autoLoad) {
+                if (config.dataSourceCallbackFn) {
+                    me.reload()
+
+                } else if (_.isPlainObject(dataSource)) {
+                    me.reload()
+                }
+            }
+        },
+
+        // 生成列自定义的缓存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) {
+            const grid = sender.up('grid')
+            grid.filters.clearFilters()
+            grid.getStore().sorters.removeAll()
+            // grid.getStore().reload()
+        },
+
+        setLoading(value) {
+            if (value) {
+                this.mask('读取中')
+            } else {
+                this.unmask()
+            }
+        },
+    })
+
+    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('无法识别的调用方法')
+}

+ 37 - 0
src/controls/gridcolumn.js

@@ -0,0 +1,37 @@
+import _ from "lodash";
+import {baseConfig} from "./base";
+import {column} from "../Defaults";
+
+export default function () {
+
+    const ct = Ext.grid.column.Column.prototype.constructor
+    Ext.grid.column.Column.override({
+        constructor: function (config) {
+            const newConfig = _.defaults({}, config, column)
+            if (newConfig.header && !newConfig.text) {
+                newConfig.text = newConfig.header
+            }
+            ct.call(this, newConfig)
+        }
+    });
+
+    // 227573
+    // const {onTitleElClick} = Ext.grid.column.Column.prototype
+    // Ext.define('Yvan.ColumnOverride', {
+    //     override: 'Ext.grid.column.Column',
+    //
+    //     config: {
+    //         enableSortOnClick: false,
+    //     },
+    //
+    //     // /**@Overrides*/
+    //     onTitleElClick: function (e, t, sortOnClick) {
+    //         // return this.callParent([e, t, this.enableSortOnClick && sortOnClick]);
+    //         if (!$(e.target).is('.x-column-header-trigger')) {
+    //             // 不是点击菜单的情况下,让列自适应宽度
+    //             this.autoSize()
+    //         }
+    //         return onTitleElClick.call(this, e, t, false)
+    //     }
+    // });
+}

+ 143 - 0
src/controls/stores.js

@@ -0,0 +1,143 @@
+import _ from 'lodash'
+import {lookupFn, lookupScope} from "../lib/lib";
+import {apiConvert, serverInvokeUrlTransform} from "../lib/config";
+import {calcObjectFlat} from '../lib/systemLib'
+import {storeAjax} from '../Defaults'
+
+/**
+ * 构建一个 grid 支持的 dataSource
+ */
+export function gridInvokeBuild(scope, grid, config, dataSource, reloadParams = {}) {
+    const me = grid
+    const params = calcObjectFlat(scope.viewModel.data, dataSource.params)
+    let storeOption = {}
+    if (dataSource.method === 'invoke') {
+
+        const fields = []
+        _.forEach(grid.columns, col => {
+            const c = {}
+            if (col.dataIndex) {
+                c.name = col.dataIndex
+                fields.push(c)
+            }
+            if (col.dataType) {
+                c.type = col.dataType
+            }
+        })
+
+        // 默认支持 gridInvoke
+        storeOption = {
+            fields,
+            // remoteSort: config.remoteSort, 虚拟store的方式不能设置此属性
+            // remoteFilter: config.remoteFilter, 虚拟store的方式不能设置此属性
+            autoLoad: true,
+            pageSize: me.store?.pageSize || config.pageSize,
+            proxy: {
+                type: 'jsonAjax',
+                $owner: me,
+                url: serverInvokeUrlTransform(dataSource.url, {scope, grid}),
+                extraParams: _.defaultsDeep({}, reloadParams, params),
+                reader: {
+                    type: 'json',
+                    rootProperty: 'data',
+                    totalProperty: 'pagination.total',
+                    successProperty: 'success',
+                    messageProperty: 'msg',
+                    transform: function (data) {
+                        if (typeof grid._transform === 'function') {
+                            // 系统转换函数
+                            grid._transform(data.data)
+                        }
+                        if (grid.dataTransform) {
+                            if (typeof grid.dataTransform === 'string') {
+                                grid.dataTransform = lookupFn(lookupScope(grid), grid.dataTransform)
+                            }
+
+                            return grid.dataTransform.call(scope, grid, data)
+                        }
+                        return data
+                    }
+                }
+            },
+            listeners: {
+                load: function (store, records, successful, operation) {
+                    me.fireEvent('dataLoadComplete', me, successful, records);
+                }
+            }
+        }
+
+    } else if (apiConvert) {
+        // 外部扩展的 apiConvert
+        storeOption = apiConvert.gridInvokeBuild(scope, grid, config, dataSource, params, reloadParams)
+
+    } else {
+        throw new TypeError("不支持的 API 请求方式")
+    }
+    me.setStore(new Ext.data.virtual.Store(storeOption))
+
+}
+
+export default function () {
+
+    Ext.define('Yvan.JsonAjaxProxy', {
+        extend: 'Ext.data.proxy.Ajax',
+        alias: 'proxy.jsonAjax',
+        actionMethods: {
+            create: "POST",
+            read: "POST",
+            update: "POST",
+            destroy: "POST"
+        },
+        timeout: storeAjax.timeout,
+        buildRequest: function (operation) {
+            // 参考源码 ext-all-debug.js:71468 method:buildRequest
+            const me = this
+            const {$owner} = me // 在 grid.initComponent 中赋值 $owner
+            const scope = lookupScope($owner)
+
+            const gridParam = me.getParams(operation)
+            const customParam = {}
+
+            // 提取 srot 元素
+            if (gridParam.sort) {
+                const sort = JSON.parse(gridParam.sort)
+                // 字符串 [{"property":"BRANCHID","direction":"ASC"}]
+                // 转换为对象 [{colId: "BRANCHID", sort: "asc"}]
+                customParam.sortModel = []
+                _.forEach(sort, s => {
+                    customParam.sortModel.push({
+                        colId: s.property,
+                        sort: _.toLower(s.direction)
+                    })
+                })
+                delete gridParam.sort
+            }
+            delete gridParam.filter
+
+            // 被 grid.constructor 作为方法存在
+            const extraParams = _.cloneDeep(me.getExtraParams())
+            const params = _.defaultsDeep(gridParam, extraParams)
+
+            let request = new Ext.data.Request({
+                params: {},
+                action: operation.getAction(),
+                records: operation.getRecords(),
+                url: me.buildUrl(),
+                jsonData: {
+                    args: [
+                        params
+                    ],
+                    ...customParam
+                },
+                proxy: me
+            });
+
+            operation.setRequest(request);
+            return request;
+        },
+        afterRequest: function (req, res) {
+            // Extend.afterExtRequest(req, res)
+        }
+    });
+
+}

+ 6 - 0
src/init.ts

@@ -1,6 +1,9 @@
 import _ from 'lodash'
 import {lookupFn, lookupScope} from "./lib/lib"
 import initCols from './controls/cols'
+import initGrid from './controls/grid'
+import initGridColumn from './controls/gridcolumn'
+import initStores from './controls/stores'
 import * as SystemLib from './lib/systemLib'
 import {initLocale} from './lib/locale-zh_CN'
 import './lib/fix'
@@ -87,4 +90,7 @@ export function init() {
 
     initCols()
     initLocale()
+    initGridColumn()
+    initGrid()
+    initStores()
 }