瀏覽代碼

Merge branch 'master' of http://git.yvanui.com/jztd/yvan-ext

liupeng 3 年之前
父節點
當前提交
749b46cf91

+ 20 - 0
src/Defaults.ts

@@ -4,6 +4,7 @@ export const windows = {
     height: 600,
     maximizable: true,
     constrain: true,
+    // manageHeight: true,
     // draggable: true,
     // scrollable: true,
 }
@@ -25,6 +26,22 @@ export const combo = {
     enableKeyEvents: true,
 }
 
+export const comboGrid = {
+    selectOnTab: true,
+    pickerHeight: 500,
+    pickerWidth: 850,
+    editEnable: null,
+}
+
+export const comboGridPicker = {
+    xtype: 'yvgrid',
+    layout: 'fit',
+    selModel: {
+        selType: 'rowmodel',
+        mode: 'SINGLE'
+    },
+}
+
 export const numberfield = {
     labelAlign: 'right',
     labelWidth: 70,
@@ -67,6 +84,7 @@ export const grid = {
         type: 'checkboxmodel',
         checkOnly: false
     },
+    pagination: true,
     autoLoad: true,
     remoteSort: true,
     remoteFilter: true,
@@ -93,6 +111,8 @@ export const tbseparator = {}
 
 export const button = {}
 
+export const fieldSet = {}
+
 export const panel = {}
 
 export const splitter = {}

+ 1 - 1
src/controls/base.ts

@@ -13,7 +13,7 @@ type DragType = 'rows-container' | 'cols-container' | 'col-item' | 'row-item'
  *      row-item rows中的一行,比如 tree / grid / panel 等等
  */
 export function baseConfig(config, dragType?: DragType) {
-    if (config.designMode && dragType) {
+    if (config && config.designMode && dragType) {
 
         let cc = ''
         switch (dragType) {

+ 37 - 0
src/controls/fieldset.js

@@ -0,0 +1,37 @@
+import _ from 'lodash'
+import $ from 'jquery'
+import {baseConfig} from "./base";
+import {fieldSet} from "../Defaults";
+import {
+    disabled,
+    fieldLabel,
+    gravity, height, metaId,
+    PropertyDescriptionTable,
+    tooltip,
+    value, width,
+    YvBase
+} from "../PropertyDescriptionTable";
+import {PropertyDescription} from "../PropertyDescription";
+
+export default function () {
+    /**
+     *
+     */
+    const ct = Ext.form.FieldSet.prototype.constructor
+    Ext.form.FieldSet.override({
+        constructor: function (config) {
+            const newConfig = _.defaultsDeep({}, config, baseConfig(config, "rows-container"), fieldSet)
+            ct.call(this, newConfig)
+        }
+    });
+
+    PropertyDescriptionTable.set(
+        'fieldSet',
+        new PropertyDescription(YvBase, {
+            props: [
+                fieldLabel, value, disabled,
+                gravity, tooltip, metaId, width, height
+            ],
+        })
+    )
+}

+ 59 - 27
src/controls/grid.js

@@ -3,7 +3,7 @@ import {grid} from '../Defaults'
 import {baseConfig} from "./base";
 import {lookupFn, lookupScope} from "../lib/lib";
 import {serverInvokeUrlTransform} from "../lib/config";
-import {calcObject} from "../lib/systemLib";
+import {calcObject, calcObjectFlat} from "../lib/systemLib";
 import {
     disabled,
     fieldLabel,
@@ -122,6 +122,15 @@ export default function () {
         },
 
         /**
+         * 为表格强制设置焦点
+         * @param seq 顺序号
+         */
+        focusRow(seq) {
+            this.setSelection(this.store.getAt(seq))
+            this.getView().focusRow(seq)
+        },
+
+        /**
          * 重新载入数据(重新计算参数)
          */
         reload(reloadParams = {}) {
@@ -153,31 +162,30 @@ export default function () {
             if (_.isPlainObject(dataSource) && dataSource.method === 'invoke' && !window["IS_DESIGN_MODE"]) {
                 const scope = lookupScope(me)
 
-                calcObject(scope.viewModel, dataSource.params).then(params => {
-
-                    me.setStore(new Ext.data.Store({
-                        remoteSort: config.remoteSort,
-                        remoteFilter: config.remoteFilter,
-                        proxy: {
-                            type: 'jsonAjax',
-                            $owner: me,
-                            url: serverInvokeUrlTransform(dataSource.url),
-                            extraParams: _.defaultsDeep({}, reloadParams, params),
-                            reader: {
-                                type: 'json',
-                                rootProperty: 'data',
-                                totalProperty: 'pagination.total',
-                                successProperty: 'success',
-                                messageProperty: 'msg'
-                            }
-                        },
-                        listeners: {
-                            load: function (store, records, successful, operation) {
-                                me.fireEvent('dataLoadComplete', me, successful, records);
-                            }
+                const params = calcObjectFlat(scope.viewModel.data, dataSource.params)
+                me.setStore(new Ext.data.Store({
+                    remoteSort: config.remoteSort,
+                    remoteFilter: config.remoteFilter,
+                    autoLoad: true,
+                    proxy: {
+                        type: 'jsonAjax',
+                        $owner: me,
+                        url: serverInvokeUrlTransform(dataSource.url),
+                        extraParams: _.defaultsDeep({}, reloadParams, params),
+                        reader: {
+                            type: 'json',
+                            rootProperty: 'data',
+                            totalProperty: 'pagination.total',
+                            successProperty: 'success',
+                            messageProperty: 'msg'
                         }
-                    }))
-                })
+                    },
+                    listeners: {
+                        load: function (store, records, successful, operation) {
+                            me.fireEvent('dataLoadComplete', me, successful, records);
+                        }
+                    }
+                }))
             }
         },
 
@@ -193,21 +201,45 @@ export default function () {
 
             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) && dataSource.method === 'invoke') {
                             me.reload()
                         }
                     }
 
-                    if (config.contextMenu) {
+                    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) {

+ 404 - 0
src/controls/input/combogrid.js

@@ -0,0 +1,404 @@
+import _ from 'lodash'
+import {comboGrid, comboGridPicker} from '../../Defaults'
+import {
+    calcObjectFlat,
+    lookupScope,
+    tryEnable,
+    stopEvent,
+    tryVarSimple,
+    tryWriteObject
+} from "../..";
+import {msg} from "../../message";
+import {isChartEvent} from '../../utils'
+
+export default function () {
+
+    Ext.define('Yvan.ComboGrid', {
+        extend: 'Ext.form.field.Picker',
+        // extend: 'Yvan.PickerAlignCenter',
+        xtype: 'combogrid',
+
+        triggerCls: Ext.baseCSSPrefix + 'form-search-trigger',
+
+        triggers: {
+            clear: {
+                weight: 0,
+                cls: Ext.baseCSSPrefix + 'form-clear-trigger',
+                hidden: true,
+                handler: 'onClearClick',
+            },
+        },
+
+        config: {
+            ...comboGrid
+        },
+
+        editable: true,
+        _superBlur: false,
+
+        initComponent() {
+            const that = this
+
+            this.on({
+                blur(sender, e) {
+                    if (this._superBlur) {
+                        // 挂起时不恢复内容
+                        return
+                    }
+
+                    // 离开焦点时,恢复原来的值
+                    sender.setRawValue(sender.value)
+                },
+                afterrender(sender) {
+                    const $dom = $(sender.inputEl.dom)
+                    that.scope = lookupScope(sender)
+
+                    if (sender.column) {
+                        // 列模式,拦截 keydown
+                        $dom.on('keydown', (e) => {
+                            if (isChartEvent(e) && !that.tryEnable()) {
+                                // 没有达成 editEnable 条件,不用响应
+                                stopEvent(e)
+                                return
+                            }
+
+                            if (e.key === 'Enter') {
+                                stopEvent(e)
+                                that.onTriggerClick(that, that.getPickerTrigger(), e);
+                                return
+                            }
+                        })
+
+                    } else {
+                        // 常规模式,拦截 keyup
+                        $dom.on('keyup', (e) => {
+                            if (e.key === 'Escape' || e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
+                                return
+                            }
+
+                            if (isChartEvent(e) && !that.tryEnable()) {
+                                // 没有达成 editEnable 条件,不用响应
+                                stopEvent(e)
+                                return
+                            }
+
+                            if (e.key === 'Enter') {
+                                stopEvent(e)
+                                that.onTriggerClick(that, that.getPickerTrigger(), e);
+                                return
+                            }
+                            // 取消 keyup 事件,让 ext 不要吧当前输入的值记录到 value 中
+                            stopEvent(e)
+                        })
+                        $dom.on('change', e => {
+                            if (that.isExpanded) {
+                                that.filterChange(that, e.target.value)
+                            }
+                            stopEvent(e)
+                        })
+                    }
+                    $dom.on('input', e => {
+                        stopEvent(e)
+                    })
+                },
+                // 这里不能用 specialkey, 在表格编辑时可能出 bug
+                // specialkey(sender, e) {
+                //     const me = this
+                //     if (e.getKey() === e.ENTER) {
+                //         _.defer(() => {
+                //             toggle();
+                //         })
+                //         e.stopEvent()
+                //         return
+                //     }
+                // },
+            })
+
+            this.superclass.initComponent.apply(this, arguments)
+        },
+
+        createPicker() {
+            const me = this
+            const cfg = {
+                layout: 'fit',
+                border: 5,
+                style: {
+                    borderColor: 'black',
+                    borderWidth: '5px',
+                    borderStyle: 'solid'
+                },
+                shadow: 'side',
+                shadowOffset: 100,
+                baseCls: Ext.baseCSSPrefix + 'boundlist',
+                shrinkWrapDock: 2,
+                floating: true,
+                width: me.pickerWidth,
+                height: me.pickerHeight,
+                minWidth: me.pickerWidth,
+                minHeight: me.pickerHeight,
+                maxWidth: me.pickerWidth,
+                maxHeight: me.pickerHeight,
+                items: [
+                    {
+                        ...comboGridPicker,
+                        ...me.grid,
+                        autoLoad: false,
+                        listeners: {
+                            afterrender(grid) {
+                                me.grid = grid
+                                grid.focus()
+                                grid.on({
+                                    rowdblclick(sender, row) {
+                                        me.selectItem(row)
+                                    },
+                                    dataLoadComplete(sender) {
+                                        _.defer(() => {
+                                            me.setFocus()
+                                        })
+                                    },
+                                    keydown(sender, e) {
+                                        if (me.column) {
+                                            // 列模式,拦截 keydown
+                                            me.processKey(e)
+                                        }
+                                    },
+                                    keyup(sender, e) {
+                                        if (!me.column) {
+                                            // 常规模式,拦截 keyup
+                                            me.processKey(e)
+                                        }
+                                    }
+                                })
+                            },
+                        }
+                    }
+                ]
+            }
+            // @ts-ignore
+            const picker = new Ext.container.Container(cfg)
+            return picker;
+        },
+
+        setFocus() {
+            if (this.grid.store.getCount() > 0) {
+                this.grid.focusRow(0)
+            }
+        },
+
+        processKey(e) {
+            if (e.key === 'Enter') {
+                this.selectItem(this.grid.selection)
+                stopEvent(e)
+
+            } else if (e.key === 'Escape') {
+                this.collapse()
+            }
+        },
+
+        repaintPickerView() {
+            const style = this.picker.getView().getEl().dom.style;
+            style.display = style.display;
+        },
+
+        onItemClick(view, record, node, rowIndex, e) {
+            this.selectItem(record);
+        },
+
+        onPickerKeyDown(treeView, record, item, index, e) {
+            const key = e.getKey();
+            if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
+                this.selectItem(record);
+            }
+        },
+
+        filterChange: _.debounce((sender, value) => {
+            sender.onExpand()
+
+        }, 1000),
+
+        selectItem(record) {
+            const me = this;
+            const {lookup} = this
+            const scope = lookupScope(this)
+
+            me.fireEvent('select', me, record)
+
+            if (record.isModel) {
+                record = record.data
+            }
+
+            tryWriteObject(lookup, record, (path, value) => {
+                me._lookupSet(path, value)
+            })
+            // lookup
+            // fromColumn: 'toTarget'
+            // fromColumn: 'toTarget'
+            // fromColumn: 'toTarget'
+            _.forOwn(lookup, (toTarget, fromColumn) => {
+                const value = _.get(record, fromColumn)
+                me._lookupSet(toTarget, value)
+            })
+
+            const {lookupClearOnChange} = me
+            if (_.isArray(lookupClearOnChange)) {
+                _.each(lookupClearOnChange, item => {
+                    me._lookupSet(item, '')
+                })
+            }
+
+            me.collapse();
+        },
+
+        onExpand() {
+            // 展开时,根据 lookup 带入查询参数
+            const reloadParam = calcObjectFlat(this.getExpressData(), this.param)
+
+            this.grid.reload(reloadParam)
+        },
+
+        onTriggerClick(sender) {
+            const {isExpanded, readOnly, disabled, grid,} = this
+
+            if (!grid) {
+                msg('正在初始化,请稍后')
+                return
+            }
+
+            if (!this.tryEnable()) {
+                // 没有达成 editEnable 条件,不用弹出
+                return
+            }
+
+            if (isExpanded || readOnly || disabled) {
+                // 已弹出、只读、禁用状态下,不允许弹出
+                return
+            }
+
+            this._superBlur = true
+            this.superclass.onTriggerClick.apply(this, arguments)
+            this._superBlur = false
+        },
+
+        onCollapse() {
+            // 每次收回时,删除下拉组件
+            // delete this.picker
+        },
+
+        /**
+         * 本方法只会从 API 或外部调用,键盘输入的内容不会记录在内
+         */
+        setValue(value) {
+            const me = this
+
+            // 设值用户看到的表面内容
+            this.setRawValue(value)
+
+            // 设值 setValue 调用链,正常触发 change 事件等
+            return me.mixins.field.setValue.call(me, value)
+        },
+
+        _lookupSet(path, value) {
+            const me = this
+            path = tryVarSimple(path)
+
+            if (me.column) {
+
+                // 判断写入目标,是否与自己绑定的属性相等
+                if (path === 'queryValue' || me.column.dataIndex === path) {
+                    me.setValue(value)
+                }
+
+                // 列模式下,写当前编辑行
+                const parentRecord = me._lookupEditorRecord()
+                if (parentRecord) {
+                    // Ext.data.Record / Ext.data.Model
+                    parentRecord.set(path, value)
+                } else {
+                    debugger
+                }
+
+            } else {
+                // 常规模式下,写 viewModel
+                me.scope.viewModel.set(path, value)
+            }
+        },
+
+        _lookupGrid() {
+            return this.column.up('grid')
+        },
+
+        _lookupEditorRecord() {
+            // parentGrid.getSelectionModel().getLastSelected()
+            const grid = this._lookupGrid()
+            const rowIdx = grid.editingPlugin?.activeEditor?.context?.rowIdx
+            if (_.isNumber(rowIdx)) {
+                return grid.store.getAt(rowIdx)
+            }
+        },
+
+        onChange: function (newVal, oldVal) {
+            const me = this
+            const value = newVal
+
+            if (value) {
+                me.getTrigger('clear').show();
+                me.updateLayout();
+            } else {
+                me.getTrigger('clear').hide();
+                me.updateLayout();
+            }
+        },
+
+        /**
+         * 得到能用于 express 表达式计算的上下文数据
+         */
+        getExpressData() {
+            const queryValue = this.getRawValue()
+
+            if (this.column) {
+                // 列模式, 准备好判断 editEnable 表达式用的数据
+                const parentRecord = this._lookupEditorRecord()
+                if (!parentRecord) {
+                    debugger
+                }
+
+                return {
+                    ...this.scope.viewModel.data,
+                    ...parentRecord.data,
+                    queryValue,
+                }
+
+            } else {
+                // 编辑模式,准备好判断用的数据
+                return {
+                    ...this.scope.viewModel.data,
+                    queryValue,
+                }
+            }
+        },
+
+        tryEnable() {
+            return tryEnable(this.getExpressData(), this.editEnable)
+        },
+
+        /**
+         * 清空所有值
+         */
+        onClearClick(sender, e) {
+            const me = this
+            const {lookup, lookupClearOnChange} = me
+
+            me.fireEvent('clear', sender, e)
+
+            me.setValue('')
+            _.forOwn(lookup, (toTarget, fromColumn) => {
+                me._lookupSet(toTarget, '')
+            })
+
+            _.each(lookupClearOnChange, item => {
+                me._lookupSet(item, '')
+            })
+        }
+    });
+
+}

+ 187 - 0
src/controls/input/combotree.js

@@ -0,0 +1,187 @@
+export default function () {
+
+    Ext.define('Yvan.ComboTree', {
+        extend: 'Ext.form.field.Picker',
+        xtype: 'combotree',
+
+        triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
+
+        config: {
+            store: null,
+            displayField: null,
+            columns: null,
+            selectOnTab: true,
+            maxPickerHeight: 300,
+            minPickerHeight: 100,
+            minPickerWidth: 350,
+            rootVisible: true,
+        },
+
+        editable: false,
+
+        setStore(store) {
+            this.store = store
+            const me = this;
+            if (me.store) {
+                if (me.picker) {
+                    me.picker.setStore(store)
+                    if (me.isExpanded) {
+                        me.collapse()
+                        delete me.picker
+                        me.expand()
+                    }
+                }
+                this.onLoad()
+                me.mon(me.store, {
+                    scope: me,
+                    load: me.onLoad,
+                    update: me.onUpdate
+                });
+            }
+        },
+
+        createPicker() {
+            const me = this
+            const picker = new Ext.tree.Panel({
+                    border: false,
+                    baseCls: Ext.baseCSSPrefix + 'boundlist',
+                    shrinkWrapDock: 2,
+                    store: me.store,
+                    floating: true,
+                    rootVisible: me.rootVisible,
+                    displayField: me.displayField,
+                    columns: me.columns,
+                    minHeight: me.minPickerHeight,
+                    maxHeight: me.maxPickerHeight,
+                    minWidth: me.minPickerWidth,
+                    manageHeight: false,
+                    shadow: false,
+                    scrollable: true,
+                    // modal: true,
+                    // constrain: true,
+                    // constrainTo: topScope._handle.el.dom,
+                    // renderTo: topScope._handle.el.dom,
+                    // alignTarget: topScope._handle,
+                    listeners: {
+                        scope: me,
+                        itemclick: me.onItemClick,
+                        itemkeydown: me.onPickerKeyDown
+                    }
+                }),
+                view = picker.getView();
+
+            if (Ext.isIE9 && Ext.isStrict) {
+                view.on({
+                    scope: me,
+                    highlightitem: me.repaintPickerView,
+                    unhighlightitem: me.repaintPickerView,
+                    afteritemexpand: me.repaintPickerView,
+                    afteritemcollapse: me.repaintPickerView
+                });
+            }
+            return picker;
+        },
+
+        repaintPickerView() {
+            const style = this.picker.getView().getEl().dom.style;
+            style.display = style.display;
+        },
+
+        onItemClick(view, record, node, rowIndex, e) {
+            this.selectItem(record);
+        },
+
+        onPickerKeyDown(treeView, record, item, index, e) {
+            const key = e.getKey();
+            if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
+                this.selectItem(record);
+            }
+        },
+
+        selectItem(record) {
+            var me = this;
+            me.setValue(record.getId());
+            me.fireEvent('select', me, record);
+            me.collapse();
+        },
+
+        onExpand() {
+            const picker = this.picker;
+            const store = picker.store;
+            const value = this.value;
+            let node;
+
+            if (value) {
+                node = store.getNodeById(value);
+            }
+
+            if (!node) {
+                node = store.getRoot();
+            }
+
+            picker.ensureVisible(node, {
+                select: true,
+                focus: true
+            });
+        },
+
+        setValue(value) {
+            const me = this;
+            console.log('setValue', value)
+            me.value = value;
+
+            if (!me.store || me.store.loading) {
+                // Called while the Store is loading. Ensure it is processed by the onLoad method.
+                return me.mixins.field.setValue.call(me, value);
+                // return me;
+            }
+
+            let record = value ? me.store.getNodeById(value) : me.store.getRoot();
+            if (value === undefined) {
+                record = me.store.getRoot();
+                console.log('setValue', record.getId())
+                me.value = record.getId();
+                return me.mixins.field.setValue.call(me, value);
+
+            } else {
+                record = me.store.getNodeById(value);
+            }
+
+            // set the raw value to the record's display field if a record was found
+            me.setRawValue(record ? record.get(me.displayField) : '');
+
+            return me.mixins.field.setValue.call(me, value);
+        },
+
+        getSubmitValue() {
+            return this.value;
+        },
+
+        getValue() {
+            return this.value;
+        },
+
+        onLoad() {
+            const value = this.value;
+
+            if (value) {
+                this.setValue(value);
+            }
+        },
+
+        onUpdate(store, rec, type, modifiedFieldNames) {
+            const display = this.displayField;
+            const me = this
+
+            if (type === 'edit' && modifiedFieldNames &&
+                Ext.Array.contains(modifiedFieldNames, display) &&
+                this.value === rec.getId()) {
+
+                me.mixins.field.setValue.call(me, this.value);
+                this.setRawValue(rec.get(display));
+            }
+        }
+
+    });
+
+}

+ 13 - 0
src/controls/input/pickerplus.js

@@ -0,0 +1,13 @@
+export default function () {
+    Ext.define('Yvan.PickerAlignCenter', {
+        extend: 'Ext.form.field.Picker',
+
+        /**
+         * Performs the alignment on the picker using the class defaults
+         * @private
+         */
+        doAlign: function () {
+            // 不要挪动就好
+        },
+    })
+}

+ 0 - 2
src/controls/stores.js

@@ -1,7 +1,5 @@
 import _ from 'lodash'
-import $ from 'jquery'
 import {lookupScope} from "../lib/lib";
-import {tplExpress, calcObject} from 'src/lib/systemLib';
 
 export default function () {
 

+ 1 - 0
src/index.ts

@@ -26,3 +26,4 @@ export * from './lib/ajax'
 export * from './lib/Lib'
 export * from './lib/config'
 export * from './lib/systemLib'
+export * from './lib/systemLibEval'

+ 8 - 0
src/init.ts

@@ -14,6 +14,10 @@ import initCheckbox from './controls/input/checkbox'
 import initCheckboxGroup from './controls/input/checkboxgroup'
 import initRadio from './controls/input/radio'
 import initRadioGroup from './controls/input/radiogroup'
+import initFieldset from './controls/fieldset'
+import initCombotree from './controls/input/combotree'
+import initPickerPlus from './controls/input/pickerplus'
+import initCombogrid from './controls/input/combogrid'
 import initRows from './controls/rows'
 import initCols from './controls/cols'
 import initButton from './controls/button'
@@ -122,4 +126,8 @@ export function init() {
     initCheckboxGroup()
     initRadio()
     initRadioGroup()
+    initFieldset()
+    initCombotree()
+    initPickerPlus()
+    initCombogrid()
 }

+ 9 - 1
src/lib/fix.js

@@ -16,7 +16,15 @@ class FixClass {
         // 这里有个 bug
         // 但凡是调用了 this.lookupViewModel() 就会出现绑定不了的情况
         // const scope = config.$initParent.lookupReferenceHolder().yvanScope //lookupScope(me)
-        const scope = config.$initParent.lookupReferenceHolder().yvanScope //lookupScope(me)
+        let scope
+        if (config.$initParent) {
+            // 在面板上的组件
+            scope = config.$initParent.lookupReferenceHolder().yvanScope //lookupScope(me)
+        } else if (config.column) {
+            // 在列上的组件
+            scope = config.column.lookupReferenceHolder().yvanScope //lookupScope(me)
+        }
+
         if (_.isArray(fix)) {
             _.each(fix, (f) => {
                 const fn = lookupFn(scope, f)

+ 124 - 10
src/lib/systemLib.ts

@@ -2,6 +2,7 @@ import _ from 'lodash'
 import {Lib, lookupScope} from './lib'
 import {ajax} from "./config";
 import {msg, showErrorDialog} from "../message";
+import {evalFunction} from "./systemLibEval";
 
 export const SIMPLE_RE = /^(?:\{(?:(\d+)|([a-z_][\w\.]*))\})$/i
 
@@ -23,7 +24,7 @@ export function calcExpress(data, express) {
         // '{foo}' 简单表达式
         const path = express.substring(1, express.length - 1);
         const ret = _.get(data, path)
-        return ret.isModel ? ret.data : ret
+        return ret?.isModel ? ret.data : ret
     }
 
     while (true) {
@@ -213,6 +214,19 @@ export function tryWriteObject(expressObject, valueObject, writeFn) {
 }
 
 /**
+ * 尝试去掉变量两边括号
+ * {a} => a
+ * a => a
+ */
+export function tryVarSimple(value) {
+    if (SIMPLE_RE.test(value)) {
+        // If we have '{foo}' alone it is a literal 简单表达式
+        return value.substring(1, value.length - 1);
+    }
+    return value
+}
+
+/**
  * 对多个表达式进行求值. 异步回调的方式返回
  *      {
  *          a: 1,
@@ -269,6 +283,40 @@ export function getParentGrid(config) {
 }
 
 /**
+ * 解析
+ * {
+ *     condition: "{skuId}",
+ *     errorMsg: "无法编辑",
+ *     notice: 'msg'
+ * }
+ */
+export function tryEnable(data, enableSetting) {
+    if (!enableSetting) {
+        // 没有这一段配置,可以编辑
+        return true
+    }
+
+    const {condition, errorMsg, notice} = enableSetting
+
+    if (!evalFunction(data, condition)) {
+        // 没有达成 condition 条件
+
+        // 计算 msg 消息内容
+        const msgContent = calcExpress(data, errorMsg)
+        if (msgContent) {
+            if (!notice || notice === 'msg') {
+                // 进行提示
+                msg(msgContent)
+            }
+        }
+
+        return false
+    }
+
+    return true
+}
+
+/**
  * 动态的为 combo 或 columns.combo 设置下拉框的值
  * @param sender 目标对象
  * @param config 目标对象的配置(在构造函数之前也可以)
@@ -276,8 +324,20 @@ export function getParentGrid(config) {
  * @param bizKey 传入字典的参数
  */
 export function setComboStore(sender, config, getDictFn, bizKey) {
+    if (sender.xtype === 'combotree') {
+        getDictFn(bizKey, (r) => {
+            if (sender.rendered) {
+                // 异步回传
+                sender.setStore(new Ext.data.TreeStore({root: r}))
+
+            } else {
+                // 同步回传
+                config.store = new Ext.data.TreeStore({root: r})
+            }
+        })
+        return
 
-    if (sender.$className === 'Ext.form.field.ComboBox') {
+    } else if (sender.$className === 'Ext.form.field.ComboBox') {
         getDictFn(bizKey, (r) => {
             if (sender.rendered) {
                 // 异步回传
@@ -308,14 +368,7 @@ export function setComboStore(sender, config, getDictFn, bizKey) {
                     const displayField = r.field[1]
                     editor.valueField = valueField
                     editor.setDisplayField(displayField)
-                    editor.setStore(new Ext.data.Store({
-                        field: ['id', 'text'],
-                        data: [
-                            {id: "Y", text: "启用"},
-                            {id: "N", text: "禁用"},
-                            {id: "D", text: "删除"},
-                        ]
-                    }))
+                    editor.setStore(new Ext.data.Store(r))
                 }
 
             } else {
@@ -483,6 +536,33 @@ class SystemEventFu {
     }
 
     @Lib({
+        title: '删除表格当前的选中行',
+        author: '罗一帆',
+        createAt: '2021-07-08',
+        updateAt: '2021-07-08',
+        type: 'system',
+        category: '表格',
+        args: [
+            {
+                type: 'refs',
+                title: 'gridRef 表格引用名',
+                allowEmpty: true,
+                name: 'gridRefName',
+            }
+        ]
+    })
+    gridRemoveCurrentRow(gridRefName) {
+        return function (sender) {
+            const scope = lookupScope(sender)
+            const grid = scope.refs[gridRefName]
+            if (grid) {
+                const records = grid.getSelectionModel().getLastSelected()
+                grid.store.remove(records)
+            }
+        }
+    }
+
+    @Lib({
         title: '查询表单,如果 url 为空值,就取消查询',
         author: '罗一帆',
         createAt: '2021-07-06',
@@ -714,6 +794,40 @@ class SystemEventFu {
     }
 
     @Lib({
+        title: '从其他资源获取表格设置',
+        author: '罗一帆',
+        createAt: '2021-07-02',
+        updateAt: '2021-07-02',
+        type: 'system',
+        category: '表单',
+        args: [
+            {
+                type: 'viewModel',
+                title: 'propertyName 属性路径',
+                name: 'propertyName',
+            },
+            {
+                type: 'refs',
+                title: 'gridRef 表格引用名',
+                allowEmpty: true,
+                name: 'gridRefName',
+            }
+        ]
+    })
+    getGrid(url) {
+        return function (sender, config) {
+            // @ts-ignore
+            require([url], (grid) => {
+                if (sender.rendered) {
+                    sender.grid = grid.default
+                } else {
+                    config.grid = grid.default
+                }
+            })
+        }
+    }
+
+    @Lib({
         title: '清空 viewModel 某个属性,并刷新表格',
         author: '罗一帆',
         createAt: '2021-07-02',

+ 21 - 0
src/lib/systemLibEval.js

@@ -0,0 +1,21 @@
+import _ from "lodash";
+
+/**
+ * 用于计算 express 表达式
+ */
+export function evalFunction(data, express) {
+    const keys = []
+    const values = []
+    _.forOwn(data, (value, key) => {
+        keys.push(key)
+        values.push(value)
+    })
+
+    const func = Function(...keys, 'return ' + express)
+
+    try {
+        return func(...values)
+    } catch (e) {
+        return
+    }
+}

+ 33 - 0
src/utils.ts

@@ -7,4 +7,37 @@ export function invokeMethod(fn: any, sender: any, args: any) {
     }
 }
 
+/**
+ * 判断事件是否为有效的输入字符
+ * @param e
+ */
+export function isChartEvent(e) {
+    if (
+        e.key === 'Shift' ||
+        e.key === 'Control' ||
+        e.key === 'Alt' ||
+        e.key === 'Escape' ||
+        e.key === 'ArrowRight' ||
+        e.key === 'Tab' ||
+        e.key === 'ArrowLeft'
+    ) {
+        return false
+    }
+
+    if (e.keyCode >= 48 && e.keyCode <= 57) {
+        // 0-9
+        return true
+    }
+
+    if (e.keyCode >= 65 && e.keyCode <= 90) {
+        // a-z
+        return true
+    }
+
+    if (e.keyCode >= 96 && e.keyCode <= 111 && e.keyCode !== 108) {
+        // 小键盘 0-9
+        return true
+    }
 
+    return false
+}

+ 1 - 1
tsconfig.json

@@ -4,7 +4,7 @@
     "module": "esnext",
 //    "outFile": "../dist/wotu-ui.js",
     "moduleResolution": "node",
-    "strict": true,
+    "strict": false,
     "allowJs": true,
     "checkJs": true,
     "importHelpers": true,