Bläddra i källkod

运营中心企业分析明细界面

zhuth 7 år sedan
förälder
incheckning
8355c584bd

+ 11 - 9
frontend/operation-web/app/model/statistical/CompanyInfo.js

@@ -1,14 +1,16 @@
 Ext.define('saas.model.statistical.CompanyInfo', {
     extend: 'saas.model.Base',
     fields: [
-        { name: 'name', type: 'string' },
-        { name: 'business_code', type: 'string' },
-        { name: 'address', type: 'string' },
-        { name: 'tel', type: 'string' },
-        { name: 'fax', type: 'string' }, // 传真
-        { name: 'realname', type: 'string' },
-        { name: 'mobile', type: 'string' },
-        { name: 'uu', type: 'int' },
-        { name: 'create_time', type: 'date' },
+        { name: 'cd_id', type: 'int' },
+        { name: 'cd_companyid', type: 'int' },
+        { name: 'cd_accountnum', type: 'int' },
+        { name: 'cd_productnum', type: 'int' },
+        { name: 'cd_customernum', type: 'int' },
+        { name: 'cd_vendornum', type: 'int' },
+        { name: 'cd_salenum', type: 'int' },
+        { name: 'cd_purchasenum', type: 'int' },
+        { name: 'cd_prodionum', type: 'int' },
+        { name: 'cd_paynum', type: 'int' },
+        { name: 'cd_receivenum', type: 'int' },
     ]
 });

+ 344 - 0
frontend/operation-web/app/util/FormUtil.js

@@ -0,0 +1,344 @@
+Ext.define('saas.util.FormUtil', {
+
+    statics: {
+
+        setItems: function(form) {
+            let me = this,
+            defaultItems = form.defaultItems;
+
+            let items = [];
+
+            items = me.applyItemsGroup(defaultItems || []);
+            items = me.initItems(items);
+
+            form.configItems = items;
+
+            items = me.applyDefaultItems(form, items);
+
+            form.removeAll();
+            form.addItems(items);
+
+            form.fireEvent('afterSetItems', form, items);
+
+            me.loadData(form);
+        },
+
+        applyItemsGroup: function(items) {
+            let groups = [],
+            groupCount = 0,
+            newItems = [];
+
+            Ext.Array.each(items, function(it, index) {
+                let item = Object.assign({}, it),
+                groupName = item.group;
+                if(!!groupName) {
+                    let idx = groups.findIndex(function(g) {
+                        return g.title == groupName;
+                    }),group;
+
+                    if(idx == -1) {
+                        group = {
+                            title: groupName,
+                            count: 1
+                        };
+                        groups.push(group);
+                    }else {
+                        group = groups[idx];
+                        group.count++;
+                    }
+                }
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                let gs = groups.concat([{
+                    title: '_nogroup'
+                }]);
+                a.group = a.group || '_nogroup';
+                let v1 = gs.findIndex(function(g) {
+                    return g.title == a.group;
+                })
+                let v2 = gs.findIndex(function(g) {
+                    return g.title == b.group;
+                })
+                return v1 - v2;
+            });
+
+            Ext.Array.each(groups, function(g) {
+                let idx = newItems.findIndex(function(i) {
+                    return i.group == g.title;
+                });
+                g.index = idx;
+            });
+
+            Ext.Array.each(groups, function(group, index) {
+                let formIndex = group.index;
+                delete group.index;
+                Ext.Array.insert(newItems, formIndex + index, [{
+                    xtype: 'separatefield',
+                    name: 'group' + (++groupCount),
+                    html: group.title,
+                    fieldLabel: group.title || '分组' + groupCount
+                }]);
+            });
+
+            return newItems;
+        },
+
+        initItems: function(items) {
+            let itemCount = detailCount = 1, newItems = [];
+            Ext.Array.each(items, function(it, i) {
+                let item = Object.assign({}, it);
+                if(item.xtype == 'detailGridField') {
+                    let columns = item.columns,
+                    colCount = 1;
+                    Ext.Array.each(columns, function(col, j) {
+                        if((col.hidden || col.width == 0 || !col.dataIndex) && (!col.hasOwnProperty('initHidden') || col.initHidden)) {
+                            Ext.applyIf(col, {
+                                index: -1,
+                                initHidden: true
+                            });
+                        }else {
+                            Ext.applyIf(col, {
+                                text: '',
+                                hidden: false,
+                                index: colCount++,
+                                allowBlank: true,
+                                width: 100,
+                                initHidden: false
+                            });
+                        }
+                    });
+                    if(!columns[columns.length - 1].flex) {
+                        columns.push({
+                            dataIndex: '',
+                            initHidden: true,
+                            flex: 1,
+                            allowBlank: true
+                        });
+                    }
+                    Ext.applyIf(item, {
+                        allowBlank: false,
+                        columnWidth: 1,
+                        gname: 'detail' + detailCount,
+                        fieldLabel: '从表' + (detailCount++),
+                    });
+                }else if(item.xtype == 'hidden') {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        hidden: true,
+                        initHidden: true,
+                    });
+                }else if(item.xtype == 'separatefield') {
+                    Ext.applyIf(item, {
+                        fieldLabel: item.html,
+                        columnWidth: 1,
+                    });
+                }else {
+                    Ext.applyIf(item, {
+                        fieldLabel: '',
+                        columnWidth: 0.25,
+                    });
+                }
+
+                if(item.hidden) {
+                    if(item.initHidden || !item.hasOwnProperty('initHidden')) {
+                        Ext.applyIf(item, {
+                            index: -1,
+                            initHidden: true
+                        });
+                    }else {
+                        Ext.applyIf(item, {
+                            index: itemCount++,
+                            initHidden: false
+                        }); 
+                    }
+                }else {
+                    Ext.applyIf(item, {
+                        index: itemCount++,
+                        initHidden: false
+                    });
+                }
+
+                Ext.applyIf(item, {
+                    name: 'item' + i,
+                    hidden: false,
+                    allowBlank: true,
+                    group: undefined,
+                });
+
+                newItems.push(item);
+            });
+
+            Ext.Array.sort(newItems, function(a, b) {
+                return a.index - b.index;
+            });
+
+            return newItems;
+        },
+
+        /**
+         * 处理formitems的一些默认配置
+         */
+        applyDefaultItems: function(form, items) {
+            let me = this,
+            formModel = form.getViewModel();
+
+            Ext.Array.each(items, function(item) {
+
+                // 设置必填
+                if(item.allowBlank==false){
+                    // TODO 需要判断类型
+                    item.beforeLabelTextTpl = "<font color=\"red\" style=\"position:relative; top:2px;right:2px; font-weight: bolder;\">*</font>";
+                }
+
+                if(item.xtype == 'textfield') {
+                    Ext.applyIf(item, {
+                        maxLength: 50
+                    });
+                }else if(item.xtype == 'datefield') {
+                    Ext.applyIf(item, {
+                        editable: false,
+                        format: 'Y-m-d'
+                    });
+                }else if(item.xtype == 'numberfield') {
+                    Ext.applyIf(item, {
+                        hideTrigger: true, // 隐藏trigger
+                        mouseWheelEnabled: false // 取消滚轮事件
+                    });
+                    // 设置默认值为0
+                    formModel.set(item.name, 0);
+                }else if(item.xtype == 'condbfindtrigger') {
+                    item.isConField = true;
+                }else if(item.xtype == 'detailGridField') {
+                    let index = form.detailCount;
+                    let columns = item.columns,
+                    cnames = columns.filter(function(c) {
+                        return c.dataIndex && !c.ignore;
+                    }).map(function(c) {
+                        return c.dataIndex
+                    }),
+                    defaultValueColumns = {};
+
+                    Ext.Array.each(columns, function(c) {
+
+                        if(c.dataIndex && c.defaultValue) {
+                            defaultValueColumns[c.dataIndex] = c.defaultValue;
+                        }
+
+                        // 不可锁定
+                        Ext.applyIf(c, {
+                            lockable: false,
+                            width: 120
+                        });
+
+                        //必填
+                        Ext.applyIf(c, {
+                            allowBlank: true
+                        });
+                        if(!c.allowBlank){
+                            c.cls = 'x-grid-necessary';
+                        }
+
+                        if(c.xtype == 'textfield') {
+                            Ext.applyIf(c, {
+                                maxLength: 50
+                            });
+                        }else if(c.xtype == 'datecolumn') {
+                            Ext.applyIf(c, {
+                                format: 'Y-m-d'
+                            });
+                        }else if(c.xtype == 'numbercolumn') {
+                            Ext.applyIf(c, {
+                                align: 'end'
+                            });
+                        }
+                        
+                        let editor = c.editor;
+                        if(editor) {
+                            Ext.applyIf(editor, {
+                                selectOnFocus: true
+                            });
+                            if(editor.xtype == 'numberfield') {
+                                Ext.applyIf(editor, {
+                                    hideTrigger: true, // 隐藏trigger
+                                    mouseWheelEnabled: false // 取消滚轮事件
+                                });
+                            }else if(editor.xtype == 'datefield') {
+                                Ext.apply(editor, {
+                                    format: 'Y-m-d'
+                                });
+                                Ext.applyIf(editor, {
+                                    editable: false
+                                });
+                            }
+                        }
+                    });
+
+                    cnames.push(item.detnoColumn);
+
+                    formModel.set('detail' + index + '.detailBindFields', cnames);
+                    item.bind = {
+                        store: '{detail' + index + '.detailStore}'
+                    };     
+                    formModel.set('detail' + index + '.detailStore', Ext.create('Ext.data.Store', {
+                        model:item.storeModel,
+                        data: [],
+                        listeners: {
+                            datachanged: function(s, eOpts) {
+                                let g = form.query('detailGridField')[index];
+                                g.fireEvent('datachanged', g, s, eOpts);
+                            },
+                            // 为新增行设置默认值
+                            add: function(store, records, index, eOpts) {
+                                Ext.Array.each(records, function(r) {
+                                    for(k in defaultValueColumns) {
+                                        r.set(k, defaultValueColumns[k]);
+                                    }
+                                    r.commit();
+                                });
+                            }
+                        }
+                    }));
+
+                    form.detailCount++;
+                }
+            });
+
+            return items;
+        },
+
+        loadData: function(form) {
+            let me = this;
+            form.setLoading(true);
+            if(form.initId && form.initId!=0) {
+                let url = form._readUrl + '/' + form.initId;
+                saas.util.BaseUtil.request({url })
+                .then(function(res) {
+                    form.setLoading(false);
+                    if(res.success) {
+                        let d = res.data;
+                        let o = {
+                            main: d.main
+                        };
+                        if(d.hasOwnProperty('items')) {
+                            o.detail0 = d.items;
+                        }else {
+                            let idx = 1;
+                            while(d.hasOwnProperty('items' + idx)) {
+                                o['detail' + (idx - 1)] = d['items' + idx];
+                                idx++;
+                            }
+                        }
+                        form.initFormData(o);
+                        form.fireEvent('load', form, o);
+                    }
+                })
+                .catch(function(e) {
+                    form.setLoading(false);
+                    saas.util.BaseUtil.showErrorToast('读取单据数据错误: ' + e.message);
+                });
+            }
+        }
+    }
+});

+ 443 - 0
frontend/operation-web/app/view/core/form/FormPanel.js

@@ -0,0 +1,443 @@
+Ext.define('saas.view.core.form.FormPanel', {
+    extend: 'Ext.form.Panel',
+    xtype: 'core-form-formpanel',
+
+    controller: 'core-form-formpanel',
+    viewModel: 'core-form-formpanel',
+
+    cls: 'x-core-form',
+
+    detailCount: 0,
+
+    //基础属性
+    initId: 0,
+    layout: 'column',
+    autoScroll: true,
+    bodyPadding: '8 12 8 12',
+
+    fieldDefaults: {
+        margin: '0 0 10 0',
+        labelAlign: 'right',
+        labelWidth: 90,
+        columnWidth: 0.25,
+    },
+
+    items: [],
+
+    initComponent: function () {
+        let me = this;
+        me.callParent(arguments);
+        me.initFormItems();
+
+    },
+
+    initFormItems: function() {
+        let me = this;
+        saas.util.FormUtil.setItems(me);
+    },
+
+    addItems: function (items) {
+        let me = this;
+        me.setBindFields(items);
+        let formItems = me.add(items);
+        me.formItems = formItems;
+        return formItems;
+    },
+
+    /**
+     * 获取form数据
+     */
+    getFormData: function () {
+        let me = this,
+            viewModel = me.getViewModel(),
+            allData = viewModel.getData(),
+            bindFields = allData.base.bindFields,
+            detailCount = me.detailCount,
+            formData = {},
+            detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            formData[field] = v;
+        });
+
+        let data = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridTrueData = g.getTrueData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridTrueData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            data['detail' + i] = gridTrueData;
+        }
+
+        return data;
+    },
+
+    setFormData: function (formData) {
+        let me = this,
+        main = formData.main,
+        detailCount = me.detailCount,
+        viewModel = me.getViewModel(),
+        viewData = viewModel.getData();
+
+        me.setMainData(main);
+
+        let detailGrids = me.query('detailGridField');
+
+        for (let i = 0; i < detailGrids.length; i++) {
+            let detailData = formData['detail' + i] || [];
+            me.setDetailData(detailData, i);
+        }
+    },
+
+    setMainData: function(mainData) {
+        let me = this,
+        viewModel = me.getViewModel();
+
+        viewModel.setData(mainData);
+        viewModel.notify();
+
+        me.isValid();
+    },
+
+    setDetailData: function(detailData, index) {
+        index = index || 0;
+
+        let me = this,
+        viewModel = me.getViewModel(),
+        viewData = viewModel.getData(),
+        detailGrids = me.query('detailGridField'),
+        detail = viewData['detail' + index],
+        grid = detailGrids[index],
+        detnoColumn = grid.detnoColumn,
+        store = detail.detailStore;
+
+        store.removeAll();
+        if (detailData.length > 0) {
+
+            for(let j = 0; j < detailData.length; j++) {
+                let d = detailData[j];
+                let o = {};
+                o[detnoColumn] = j + 1;
+                let r = store.add(o)[0];
+                for(let k in d) {
+                    r.set(k, d[k]);
+                }
+            }
+        }
+
+        me.isValid();
+    },
+
+    initFormData: function(data) {
+        let me = this;
+        me.setFormData(data);
+        me.clearDirty();
+    },
+
+    /**
+     * 设置需要绑定的字段
+     */
+    setBindFields: function (items) {
+        let me = this,
+            viewModel = me.getViewModel(),
+            bindFields = [];
+
+        Ext.Array.each(items, function (item) {
+            let xtype = item.xtype,
+            bind = item.bind,
+            name = item.name,
+            ignore = item.ignore,
+            defaultValue = item.defaultValue,
+            isConField = item.isConField;
+
+            item.listeners = item.listeners || {};
+            item.listeners.validChange = function() {
+                me.isValid();
+            }
+            item.listeners.validitychange = function() {
+                me.isValid();
+            }
+
+
+            if (xtype == 'detailGridField') {
+                item.defaultColumns = item.columns;
+                return;
+            }
+
+            if(item.readOnly){
+                item.defaultReadOnly = item.readOnly;
+            }
+
+            // 如果是组合放大镜
+            if(isConField) {
+                let names = item.dbfinds.map(function(d) {
+                    return d.to;
+                });
+
+                // 设置默认值
+                if (defaultValue != undefined) {
+                    
+
+                    for(let x = 0; x < names.length; x++) {
+                        viewModel.set(names[x], defaultValue[names[x]]);
+                    }
+                }
+
+                // 设置model绑定
+                if (!ignore) {
+                    for(let x = 0; x < names.length; x++) {
+                        if(!Ext.Array.contains(bindFields, names[x])) {
+                            bindFields.push(names[x]);
+                        }
+                    }
+                }
+            }else {
+                if (bind) {
+                    if (!Ext.isString(bind)) {
+                        Ext.apply(bind, {
+                            value: '{' + name + '}'
+                        });
+                    } else {
+                        item.bind = '{' + name + '}';
+                    }
+                } else {
+                    item.bind = '{' + name + '}';
+                }
+                // 设置默认值
+                if (defaultValue != undefined) {
+                    viewModel.set(name, defaultValue);
+                }
+    
+                // 设置model绑定
+                if (!ignore) {
+                    if(!Ext.Array.contains(bindFields, name)) {
+                        bindFields.push(name);
+                    }
+                }
+            }
+        });
+        viewModel.set('base.bindFields', bindFields);
+    },
+
+    clearDirty: function() {
+        let me = this;
+        let detailGrids = me.query('detailGridField');
+        let fields = me.getForm().getFields().items;
+        
+        Ext.Array.each(fields, function(f) {
+            f.resetOriginalValue ? f.resetOriginalValue() : '';
+        });
+        Ext.Array.each(detailGrids, function(g) {
+            g.clearDirty();
+        });
+    },
+
+    setEditable: function(able) {
+        let me = this,
+        viewModel = me.getViewModel(),
+        items = me.getForm().getFields().items;
+
+        let detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(detailGrids, function(g) {
+
+            g.setGridDisabled(able);
+        });
+
+        Ext.Array.each(items, function(item) {
+            if(typeof item.setReadOnly  == 'function') {
+               item.setReadOnly (item.defaultReadOnly || !able);
+            }
+        });
+    },
+
+    //overriders
+    isValid: function() {
+        let me = this;
+        let viewModel = me.getViewModel();
+        let formItems = me.formItems || [];
+        let valid = !Ext.Array.findBy(formItems, function(f) {
+            return !f.isValid();
+        });
+        let detailGrids = me.query('detailGridField');
+
+        for(let i = 0; i < detailGrids.length; i++) {
+            let g = detailGrids[i];
+            if(!g.isValid()) {
+                valid = false;
+                break;
+            }
+        }
+        viewModel.set('base.valid', valid);
+        return valid;
+    },
+
+    isDirty: function () {
+        let me = this,
+        formItems = me.formItems || [],
+        detailGrids = me.query('detailGridField'),
+        dirty = false;
+
+        for(let i = 0; i < detailGrids.length; i++) {
+            let grid = detailGrids[i];
+            if(grid.isDirty()) {
+                dirty = true;
+                break;
+            }
+        }
+
+        if(!dirty) {
+            dirty = !!Ext.Array.findBy(formItems, function(f) {
+                return f.isDirty();
+            });
+        }
+
+        return dirty;
+    },
+
+    getSaveData: function() {
+        let me = this,
+        viewModel = me.getViewModel(),
+        allData = viewModel.getData(),
+        bindFields = allData.base.bindFields,
+        detailCount = me.detailCount,
+        formData = {},
+        detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            formData[field] = v;
+        });
+
+        let dirtyData = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridDirtyData = g.getSaveData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridDirtyData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            dirtyData['detail' + i] = gridDirtyData;
+        }
+
+        return dirtyData;
+    },
+
+    getDirtyData: function() {
+        let me = this,
+        formF = me.getForm(),
+        viewModel = me.getViewModel(),
+        allData = viewModel.getData(),
+        bindFields = allData.base.bindFields,
+        detailCount = me.detailCount,
+        formData = {},
+        detailGrids = me.query('detailGridField');
+
+        Ext.Array.each(bindFields, function(field) {
+            let v = allData[field];
+            let f = formF.findField(field);
+            if(Ext.isDate(v)) {
+                v = Ext.Date.format(v, 'Y-m-d H:i:s');
+            }
+            if(f && f.isDirty()) {
+                formData[field] = f.originalValue;
+            }
+        });
+
+        let dirtyData = {
+            main: formData,
+        };
+
+        for (let i = 0; i < detailCount; i++) {
+            let g = detailGrids[i];
+            let gridDirtyData = g.getDirtyData();
+            let modelDetail = allData['detail' + i];
+            let detailBindFields = modelDetail.detailBindFields;
+
+            Ext.Array.each(gridDirtyData, function(d) {
+                for(k in d) {
+                    if(!Ext.Array.contains(detailBindFields, k)) {
+                        delete d[k];
+                    }
+                }
+            });
+            
+
+            dirtyData['detail' + i] = gridDirtyData;
+        }
+
+        return dirtyData;
+    },
+
+    beforeSave: function() {
+        return true;
+    },
+
+    beforeDelete: function() {
+        return true;
+    },
+
+    beforeAudit: function() {
+        return true;
+    },
+
+    beforeUnAudit: function() {
+        return true;
+    },
+
+    promiseCloseTab: function() {
+        let me = this,
+        controller = me.getController();
+
+        if(me.isDirty()) {
+            return saas.util.BaseUtil.showConfirm('提示', me.dirtyCloseText, {
+                buttons: Ext.Msg.YESNO
+            }).then(function(yes) {
+                if(yes === 'yes') {
+                    return true;
+                }else if(yes === 'no') {
+                    return false
+                }
+            })
+        }else {
+            return new Promise(function (resolve, reject) {
+                return resolve(true);
+            });
+        }
+    },
+
+    refreshViewConfig: function() {
+        this.getController().refresh();
+    }
+});

+ 92 - 0
frontend/operation-web/app/view/core/form/FormPanel.scss

@@ -0,0 +1,92 @@
+.x-core-form {
+    padding: 8px 12px 8px 12px;
+
+    .x-toolbar-default-docked-top{
+        padding: 12px 5px 12px 8px;
+    }
+    .x-tb {
+        color: #A2A2A2;
+        letter-spacing: 0.72px;
+        text-align: left;
+        font-size: 18px;
+        line-height: 16px;
+        font-weight: 400;
+        // font: 400 18px/16px 'PingFangSC-Regular';
+    }
+    .x-codeeditor {
+        top: -2px !important;
+        left: 44.5px !important;
+
+        .x-form-trigger-wrap {
+            border-top: none;
+            border-right: none;
+            border-bottom: 1px solid #aeb1b5;
+            border-left: none;
+
+            input {
+                padding-left: 0;
+                letter-spacing: 0.72px;
+                font-size: 18px;
+                min-height: inherit;
+                font-size: 18px;
+                line-height: 16px;
+                font-weight: 400;
+                // font: 400 18px/16px 'PingFangSC-Regular';
+            }
+        }
+        .x-form-trigger-wrap-invalid {
+            border-bottom: 1px solid #cf4c35;
+        }
+    }
+    .x-codeeditor-btn {
+        background-color: transparent !important;
+        border: none;
+        font-size: 18px;
+        box-shadow: none !important;
+
+        .fa-check-circle {
+            color: #52C41A;
+        }
+
+        .fa-edit {
+            color: #A2A2A2;
+        }
+
+        &:hover {
+            .fa-edit {
+                color: black;
+            }
+            .fa-check-circle {
+                color: #A0D911;
+            }
+        }
+    }
+    .x-audited {
+        border: 1px solid #FF002B;
+        color: #FF002B;
+    }
+}
+
+.x-field-separator {
+    &>div {
+        &>div {
+            padding-left: 20px;
+            font-weight: bold;
+            height: 100%;
+            line-height: 100%;
+            font-size: 14px;
+            vertical-align: middle;
+
+            &:before {
+                content: ' ';
+                position: absolute;
+                width: 5px;
+                height: 16px;
+                border-radius: 4px;
+                background: #33b4ee;
+                left: 4px;
+                top: 10px;
+            }
+        }
+    }
+}

+ 556 - 0
frontend/operation-web/app/view/core/form/FormPanelController.js

@@ -0,0 +1,556 @@
+Ext.define('saas.view.core.form.FormPanelController', {
+    extend: 'Ext.app.ViewController',
+    alias: 'controller.core-form-formpanel',
+
+    auditBtnClick: function() {
+        var me = this,
+        form = me.getView(),
+        statusCodeField = form._statusCodeField,
+        viewModel = me.getViewModel(),
+        status = viewModel.get(statusCodeField);
+
+        status == 'AUDITED' ? me.onUnAudit() : me.onAudit();
+    },
+
+    refresh: function() {
+        var me = this,
+        form = me.getView(),
+        xtype = form.xtype,
+        _config = {
+            initId: form.initId,
+        },
+        currentTab = saas.util.BaseUtil.getCurrentTab();
+        
+        var view = {
+            _config: _config,
+            xtype: xtype
+        };
+        Ext.apply(view, _config);
+        
+        Ext.suspendLayouts();
+        currentTab.removeAll();
+        currentTab.add(view);
+        Ext.resumeLayouts(true);
+    },
+
+    add: function(){
+        var form = this.getView();
+        var id = form.xtype + '-add';
+        saas.util.BaseUtil.openTab(form.xtype,'新增' + form._title, id);
+    },
+
+    onCopy: function() {
+        var me = this;
+        var form = this.getView();
+        var id = form.xtype + '-add';
+
+        var formData = me.initCopyData(form.getFormData());
+
+        saas.util.BaseUtil.openTab(form.xtype,'新增' + form._title, id, {initData: formData});
+    },
+
+    initCopyData: function(formData) {
+        var me = this;
+        var form = this.getView();
+        var detailCount = form.detailCount;
+        var main = formData.main;
+        var auditTexts = form.auditTexts;
+
+        // 单号、id清空
+        main[form._idField] = 0;
+        main[form._codeField] = '';
+        // 单据状态为录入状态(未审核)
+        main[form._statusCodeField] = auditTexts.unAuditCode;
+        main[form._statusField] = auditTexts.unAuditText;
+        //重设录入人,录入日期,审核人,审核日期
+        main['creatorId'] = saas.util.BaseUtil.getCurrentUser().id;
+        main['creatorName'] = saas.util.BaseUtil.getCurrentUser().realname;
+        main['createTime'] = Ext.Date.format(new Date(), 'Y-m-d H:i:s');
+        main['updaterId'] = saas.util.BaseUtil.getCurrentUser().id;
+        main['updaterName'] = saas.util.BaseUtil.getCurrentUser().realname;
+        main['updateTime'] = Ext.Date.format(new Date(), 'Y-m-d H:i:s');
+        main[form._auditmanField] = undefined;
+        main[form._auditdateField] = undefined;
+
+
+
+        for(var k in main) {
+            // 主表日期改为当前日期
+            if(saas.util.BaseUtil.isDateString(main[k])) {
+                main[k] = Ext.Date.format(new Date, 'Y-m-d H:i:s');
+            }
+        }
+
+        for(var j = 0; j < detailCount; j++) {
+            var detail = formData['detail' + j];
+            for(var x = 0; x < detail.length; x ++) {
+                var d = detail[x];
+
+                for(var k in d) {
+                    // 从表id清空
+                    delete d['id'];
+                    // 从表日期清空
+                    if(saas.util.BaseUtil.isDateString(d[k])) {
+                        d[k] = '';
+                    }
+                }
+            }
+        }
+
+        return me.myInitCopyData(formData);;
+    },
+
+    myInitCopyData: function(formData) {
+        return formData;
+    },
+    
+    delete: function(){
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+        if(id&&id.value!=0){
+
+            if(!form.beforeDelete()) {
+                return false;
+            }
+
+            saas.util.BaseUtil.deleteWarn(form._deleteMsg,function(btn){
+                if(btn == 'yes'){
+                    saas.util.BaseUtil.request({
+                        url: form._deleteUrl+'/'+id,
+                        method: 'POST',
+                    })
+                    .then(function(localJson) {
+                        if(localJson.success){
+                            var mainTab = Ext.getCmp('main-tab-panel');
+                            mainTab.getActiveTab().close();
+                            //解析参数
+                            saas.util.BaseUtil.showSuccessToast('删除成功');
+                        }
+                    })
+                    .catch(function(e) {
+                        saas.util.BaseUtil.showErrorToast('删除失败: ' + e.message);
+                    });
+                }
+            });
+        }
+    },
+
+    onSave: function() {
+        var me = this,
+        form = this.getView();
+
+        var valid = form.isValid();
+        if(!valid) {
+            saas.util.BaseUtil.showErrorToast(form.invalidText);
+            return false;
+        }
+        var dirty = form.isDirty();
+        if(!dirty) {
+            saas.util.BaseUtil.showErrorToast(form.noDirtySaveText);
+            return false;
+        }
+
+        if(!form.beforeSave()) {
+            return false;
+        }
+
+        me.save();
+    },
+
+    save:function(){
+        var me = this,
+        form = this.getView(),
+        codeField = form.getForm().findField(form._codeField),
+        detailCount = form.detailCount,
+        viewModel = me.getViewModel(),
+        codeModified = !form.initId || (codeField && codeField.isDirty());
+
+        //form里面数据
+        var formData = form.getSaveData();
+
+        var params = {
+            main:formData.main
+        };
+
+        for(var i = 0; i < detailCount; i++) {
+            params['items' + ( i + 1)] = formData['detail' + i];
+        }
+
+        // 只有一个从表时从表字段改为items
+        if(detailCount == 1) {
+            params.items = params.items1;
+            delete params.items1;
+        }
+
+        params.codeModified = codeModified;
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._saveUrl,
+            params: JSON.stringify(params),
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                var id = localJson.data.id;
+                var code = localJson.data.code;
+                form.initId = id;
+                saas.util.FormUtil.loadData(form);
+
+                var newId = form.xtype + '-' + id;
+                var newTitle = form._title + '(' + code + ')';
+
+                saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+
+                saas.util.BaseUtil.showSuccessToast('保存成功');
+                form.fireEvent('aftersave', true, form, localJson);
+            }
+        })
+        .catch(function(e) {
+            form.setLoading(false);
+            saas.util.BaseUtil.showErrorToast('保存失败: ' + e.message);
+            form.fireEvent('aftersave', false, form);
+        });
+    },
+
+    onAudit: function(){
+        var me = this,
+        form = this.getView(),
+        viewModel = me.getViewModel(),
+        id = viewModel.get(form._idField);
+
+        var dirty = form.isDirty();
+
+        if(id && dirty) {
+            saas.util.BaseUtil.showConfirm('提示', form.dirtyAuditText)
+            .then(function(yes) {
+                if(yes == 'yes') {
+                    me.onSave();
+                }
+            });
+            return;
+        }
+
+        var valid = form.isValid();
+
+        if(!valid) {
+            saas.util.BaseUtil.showErrorToast(form.invalidText);
+            return false;
+        }
+
+        if(!form.beforeAudit()) {
+            return false;
+        }
+
+        me.audit();
+    },
+
+    audit: function() {
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        detailCount = form.detailCount,
+        codeField = form.getForm().findField(form._codeField),
+        codeModified = !form.initId || (codeField && codeField.isDirty());
+
+        //form里面数据
+        var formData = form.getFormData();
+        var params = {
+            main: formData.main
+        };
+
+        for(var i = 0; i < detailCount; i++) {
+            params['items' + ( i + 1)] = formData['detail' + i];
+        }
+
+        // 只有一个从表时从表字段改为items
+        if(detailCount == 1) {
+            params.items = params.items1;
+            delete params.items1;
+        }
+
+        params.codeModified = codeModified;
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._auditUrl,
+            params: JSON.stringify(params),
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                // 未保存直接审核会返回id
+                if(localJson.data) {
+                    var id = localJson.data.id;
+                    var code = localJson.data.code;
+                    
+                    form.initId = id;
+
+                    var newId = form.xtype + '-' + id;
+                    var newTitle = form._title + '(' + code + ')';
+
+                    saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+                }
+                saas.util.FormUtil.loadData(form);
+                form.setEditable(false);
+                saas.util.BaseUtil.showSuccessToast('审核成功' + (localJson.message ? ': ' + localJson.message : ''));
+                form.fireEvent('afteraudit', true, form, localJson);
+            }
+        })
+        .catch(function(e) {
+            form.setLoading(false);
+            // console.error(e);
+            // if(res.data) {
+            //     var id = localJson.data.id;
+            //     var code = localJson.data.code;
+                
+            //     form.initId = id;
+
+            //     var newId = form.xtype + '-' + id;
+            //     var newTitle = form._title + '(' + code + ')';
+
+            //     saas.util.BaseUtil.refreshTabTitle(newId, newTitle);
+            //     saas.util.FormUtil.loadData(form);
+            // }
+            saas.util.BaseUtil.showErrorToast('审核失败: ' + e.message);
+            form.fireEvent('afteraudit', false, form);
+        });
+    },
+
+    onUnAudit: function() {
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+        if(id&&id.value!=0){
+
+            if(!form.beforeUnAudit()) {
+                return false;
+            }
+
+            me.unAudit();
+        }
+    },
+
+    unAudit: function() {
+        var me = this;
+        var form = this.getView();
+        var viewModel = me.getViewModel();
+        var id = viewModel.get(form._idField);
+        var code = viewModel.get(form._codeField);
+
+        form.setLoading(true);
+        saas.util.BaseUtil.request({
+            url: form._unAuditUrl + '/' + id,
+            method: 'POST',
+        })
+        .then(function(localJson) {
+            form.setLoading(false);
+            if(localJson.success){
+                //解析参数
+                saas.util.BaseUtil.showSuccessToast('反审核成功');
+                saas.util.FormUtil.loadData(form);
+            }
+        })
+        .catch(function(res) {
+            form.setLoading(false);
+            console.error(res);
+            saas.util.BaseUtil.showErrorToast('反审核失败: ' + res.message);
+        });
+    },
+
+    codeEditorBlur: function(e) {
+        var me = this,
+        viewModel = me.getViewModel(),
+        targetEl = event.target,
+        faEl = targetEl.getElementsByClassName('fa')[0];
+
+        if(faEl && faEl.classList.contains('fa-check-circle')) {
+            // 处理重复触发事件
+            // viewModel.set('base.codeEditable', false);
+        }else {
+            viewModel.set('base.codeEditable', false);
+        }
+    },
+    codeEditorClick: function() {
+        var me = this,
+        form = me.getView(),
+        codeField = form.getForm().findField(form._codeField),
+        viewModel = me.getViewModel(),
+        codeEditable = viewModel.get('base.codeEditable');
+
+        codeField.setValue(viewModel.get(form._codeField));
+        viewModel.set('base.codeEditable', !codeEditable);
+    },
+    showMessageLog:function(btn){
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        mlKeyvalue = viewModel.get(form._idField),
+        mlCaller = form.caller,
+        win = Ext.getCmp(form.xtype+mlKeyvalue);
+        if (!win&&mlKeyvalue!=0) {
+            var panel = form.up('core-tab-panel'),panelEl;panelEl = panel.getEl()
+            var box = panelEl.getBox();
+            var height = box.height;
+            var width = box.width;
+            var win = form.add(Ext.create('Ext.window.Window', {
+                modal: true,
+                id:me.xtype+mlKeyvalue,
+                cls:'x-window-dbfind',
+                height: height*0.8,
+                width: width*0.8,
+                title: '操作日志('+viewModel.get(form._codeField)+')',
+                scrollable: true,
+                constrain: true,
+                closable: true,
+                layout: 'fit',
+                items: [{
+                    padding:'5 10 5 10',
+                    xtype: 'core-form-mseeageLog',
+                    mlKeyvalue:mlKeyvalue,
+                    mlCaller:mlCaller
+                }],
+                listeners:{
+                    'close':function(){
+                        btn.removeCls('x-btn-focus');
+                    }
+                }
+            }));
+        };
+        win.show();
+    },
+
+    onPrint: function() {
+        var me = this,
+        form = me.getView(),
+        viewModel = me.getViewModel(),
+        caller = form.caller,
+        id = viewModel.get(form._idField),
+        code = viewModel.get(form._codeField);
+
+        me.beforePrint(caller).then(function(flag) {
+            if(!flag) {
+                return false;
+            }
+            saas.util.BaseUtil.request({
+                url: '/api/commons/jasperReport/printByDefault',
+                // url: 'http://192.168.253.58:8920/jasperReport/printByDefault',
+                method: 'POST',
+                headers: {
+                    "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
+                },
+                params: {
+                    caller: caller,
+                    id: id,
+                    code: code
+                }
+            }).then(function(res) {
+                var data = res.data,
+                printurl = data.printurl,
+                printtype = data.printtype,
+                reportName = data.reportName,
+                title = data.title,
+                userName = data.userName,
+                whereCondition = data.whereCondition,
+                companyId = saas.util.BaseUtil.getCurrentUser().companyId;
+                var url = printurl + '?' + 'reportName=' + reportName + '&' + 'companyId=' + companyId + '&whereCondition=' + whereCondition + (userName ? '&userName=' + userName : "");
+    
+                window.open(url);
+            }).catch(function(res) {
+                console.error(res.message);
+                saas.util.BaseUtil.showErrorToast('获取打印报表错误:' + res.message);
+            });
+        });
+    },
+
+    /**
+     * 判断权限
+     */
+    beforePrint: function(caller) {
+        return saas.util.BaseUtil.request({
+            url: '/api/commons/' + caller + '/print',
+            method: 'GET',
+            headers: {
+                "Content-Type": 'application/x-www-form-urlencoded;charset=UTF-8'
+            },
+            params: {
+                caller: caller,
+                id: id
+            }
+        }).then(function(res) {
+            return res.success;
+        }).catch(function(res) {
+            console.error(res.message);
+            saas.util.BaseUtil.showErrorToast('未通过权限验证:' + res.message);
+        });
+    },
+
+    onSetting: function() {
+        var me = this,
+        form = me.getView(),
+        viewName = form.viewName,
+        configItems = form.configItems,
+        items = [];
+
+        Ext.Array.sort(configItems, function(a, b) {
+            return a.index - b.index;
+        });
+
+        for(let i = 0; i < configItems.length; i++) {
+            let item = configItems[i];
+            let formItem = form.query('[name=' + item.name + ']')[0];
+            let xtypes = formItem.getXTypes().split('/');
+            item.xtypes = xtypes;
+            if(!item.initHidden) {
+                items.push(Object.assign({}, item));
+            }
+        }
+
+        me.openSettingWindow(viewName, items, 'main');
+    },
+
+    onColSetting: function(ct, col, e, t, eoPts) {
+        let me = this,
+        form = me.getView(),
+        viewName = form.viewName,
+        grid = ct.grid.ownerGrid,
+        columns = grid.defaultColumns,
+        items = [];
+
+        for(let i = 0; i < columns.length; i++) {
+            let col = columns[i];
+            if(!col.initHidden) {
+                items.push(Object.assign({}, col));
+            }
+        }
+
+        me.openSettingWindow(viewName, items, grid.gname);
+    },
+
+    openSettingWindow: function(viewName, items, settype) {
+        var panel = saas.util.BaseUtil.getCurrentTab(),
+        box = panel.getBox(),
+        refs = panel.getReferences() || {},
+        win = refs.settingwin;
+
+        title = settype == 'main' ? '界面设置' : '列设置';
+
+        if(!win) {
+            win = panel.add({
+                title: title,
+                xtype: 'settingwin',
+                viewName: viewName,
+                fieldItems: Ext.Array.clone(items),
+                settype: settype,
+            });
+        }
+        win.show();
+    }
+});

+ 15 - 0
frontend/operation-web/app/view/core/form/FormPanelModel.js

@@ -0,0 +1,15 @@
+Ext.define('saas.view.core.form.FormPanelModel', {
+    extend: 'Ext.app.ViewModel',
+    alias: 'viewmodel.core-form-formpanel',
+
+    data: {
+        id: 0,
+        base: {
+            bindFields: [], // 绑定字段
+            valid: true, // 单据是否合法
+            editable: true, // 单据是否可编辑
+        },
+        detailBindeFields: [], // 从表绑定列
+        detailStore: null, // 从表store
+    },
+});

+ 554 - 0
frontend/operation-web/app/view/core/form/field/DetailGridField.js

@@ -0,0 +1,554 @@
+Ext.define('saas.view.core.form.field.DetailGridField', {
+    extend: 'Ext.grid.Panel',
+    xtype: 'detailGridField',
+
+    cls: 'x-detailgridfield',
+
+    sortableColumns: false,
+    enableColumnHide: false,
+    border: 1,
+    margin: '0 0 10 0', // formpanel的fieldDefaults未生效
+    minHeight: 245,
+    columnWidth : 1.0, 
+
+    requires: [
+        'Ext.grid.plugin.CellEditing',
+        'Ext.selection.CellModel'
+    ],
+
+    selModel: {
+        type: 'cellmodel'
+    },
+    
+    showIndex: true,
+    configUrl: '',
+    editable: true,
+    allowEmpty: false, // 表格为空时校验合法
+    showCount: true, // 显示合计栏
+
+    emptyRows: 5,
+
+    allowDeselect: true,
+
+    initComponent: function() {
+        var me = this;
+        me.initColumns();
+        me.setSummary();
+
+        addRows = function(id) {
+            var grid = Ext.getCmp(id);
+            if(grid.editable) {
+                grid.addDetail(0);
+            }
+        };
+
+        Ext.apply(me, {
+            plugins: [{
+                ptype: 'cellediting',
+                clicksToEdit: 1,
+                listeners: {
+                    edit: function(editor, context, eOpts) {
+                        context.column.fireEvent('edit', context.value, me, context.column, editor, context, eOpts);
+                    }
+                }
+            }, {
+                ptype: 'menuclipboard'
+            }],
+            normalViewConfig: {
+                deferEmptyText: false,
+            },
+            lockedViewConfig: {
+                scrollable: {
+                    x: false,
+                    y: true
+                },
+                deferEmptyText: false,
+                emptyText: '<div style="width: 100%; text-align: center; cursor: pointer; color: green;" class="fa fa-plus" title="新增行" onclick="addRows(\'' + me.id + '\')"></div>',
+            },
+            listeners: {
+                boxready: function(g) {
+                    var f = g.ownerCt,
+                    ih = g.initialConfig.height || 0,
+                    iminh = g.initialConfig.minHeight || 0,
+                    // 最小高度等于设定的最小高度或者设定的高度
+                    minh = ih ? (iminh ? Math.min(ih, iminh) : 0) : iminh,
+                    c = f.detailCount || 1,
+                    fb = f.getBox(),
+                    fh = fb.height,
+                    h = (fh * 0.5) / c;
+
+                    // 当自适应高度比设定的最小高度还要小时取设定的最小高度
+                    if(minh && h < minh) {
+                        g.setMinHeight(minh);
+                        h = minh;
+                    }
+                    g.setHeight(h);
+                },
+                edit: function() {
+                    this.fireEvent('validChange');
+                },
+                itemmouseenter: function(view, record, item, index, e, eOpts) {
+                    if(!view.up('detailGridField').editable) {
+                        return;
+                    }
+                    var lockedItems = view.el.dom.parentElement.parentElement.getElementsByClassName('x-grid-scrollbar-clipper-locked')[0].getElementsByClassName('x-grid-item');
+                    var currentLockedItem = lockedItems[index];
+                    var textItem = currentLockedItem.getElementsByClassName('text')[0];
+                    var iconsItem = currentLockedItem.getElementsByClassName('icons')[0];
+
+                    textItem.style.display = 'none';
+                    iconsItem.style.display = 'flex';
+                },
+                itemmouseleave: function(view, record, item, index, e, eOpts) {
+                    if(!view.up('detailGridField').editable) {
+                        return;
+                    }
+                    var lockedItems = view.el.dom.parentElement.parentElement.getElementsByClassName('x-grid-scrollbar-clipper-locked')[0].getElementsByClassName('x-grid-item');
+                    var currentLockedItem = lockedItems[index];
+                    var textItem = currentLockedItem.getElementsByClassName('text')[0];
+                    var iconsItem = currentLockedItem.getElementsByClassName('icons')[0];
+
+                    textItem.style.display = 'block';
+                    iconsItem.style.display = 'none';
+                },
+                cellclick: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
+                    var target = e.target;
+                    var detno = record.get(me.detnoColumn);
+
+                    if(target.classList.contains('fa-minus')) {
+                        me.deleteDetail(detno);
+                    }else if(target.classList.contains('fa-plus')) {
+                        me.addDetail(detno);
+                    }
+                },
+                scope: me
+            }
+        });
+        me.callParent(arguments);
+    },
+
+    initColumns: function() {
+        // 构造序号列
+        var me = this,
+        columns = me.columns,
+        detnoField = me.detnoColumn,
+        showCount = me.showCount,
+
+        indexColumn = {
+            // text : "序号", 
+            // text: '<div class="x-sa sa-setting" style="cursor: pointer;" title="列设置"></div>',
+            bind: {
+                text: "{(configurable && isAdmin) ? ('<div class=\"x-sa sa-setting\" style=\"cursor: pointer;\" title=\"列设置\"></div>') : '序号'}"
+            },
+            dataIndex : detnoField, 
+            width : 60, 
+            xtype : "numbercolumn",
+            align : 'center',
+            format:'0',
+            allowBlank: true,
+            locked:true,
+            lockable: false,
+            renderer: function(value, a, record, index) {
+                return '<div class="text">' + value + '</div>' +
+                '<div class="icons" style="height: 19px; display: none;">' + 
+                    '<div style="line-height: 19px; flex: 1; color: #34BAF6; cursor: pointer; margin-right: 2px;" class="x-row-insert fa fa-plus" title="插入行"></div>'+
+                    '<div style="line-height: 19px; flex: 1; color: #FF002B; cursor: pointer; margin-left: 2px;" class="x-row-delete fa fa-minus" title="删除行"></div>'+
+                '</div>';
+            },
+            listeners: {
+                headerclick: function() {
+                    let form = this.up('core-form-formpanel'),
+                    controller = form.getController(),
+                    text = this.text;
+
+                    if(text != '序号') {
+                        controller.onColSetting(arguments[0]);
+                    }
+                }
+            }
+        };
+
+        if(showCount) {
+            Ext.apply(indexColumn, {
+                summaryType: 'count',
+                summaryRenderer: function(value, summaryData, dataIndex) {
+                    return Ext.String.format('合计', value);
+                },
+            });
+        }
+
+        if (detnoField) {
+            Ext.apply(me, { columns: [indexColumn].concat(columns) });
+        }
+    },
+
+    setSummary: function() {
+        var me = this,
+        columns = me.columns,
+        features = me.features || [];
+
+        var hasSummary = !!columns.find(function(c) {
+            return c.summaryType
+        });
+
+        if(hasSummary) {
+            features.push({
+                ftype: 'summary',
+                dock: 'bottom'
+            });
+        }
+        Ext.applyIf(me, {
+            features: features
+        });
+    },
+
+    add10EmptyRow: function(num) {
+        var me = this,
+        detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        selectedRecord = me.selModel.lastSelected,
+        datas = [];
+
+        num = num || me.emptyRows;
+
+        //当前行后序号全部加1
+        var detno = selectedRecord ? selectedRecord.data[detnoColumn] : 0;
+        Ext.Array.each(new Array(num), function() {
+            detno += 1;
+            var data = {};
+            data[detnoColumn] = detno;
+            datas.push(data);
+        })
+        store.insert(store.indexOf(selectedRecord) + 1, datas);
+    },
+
+    addDetail: function(v, d) {
+        d = d || {};
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        records = store.getData().items,
+        selectedRecord = records.find(function(r) {
+            return r.get(detnoColumn) == v;
+        });
+
+        store.each(function(item){
+            var t = item.data[detnoColumn];
+            if(t > v) {
+                item.set(detnoColumn, t + 1);
+            }
+        });
+
+        var data = d || {};
+        data[detnoColumn] = v + 1;
+        var r = store.insert(store.indexOf(selectedRecord) + 1, data);
+
+        store.each(function(s) {
+            var itemFields = s.fields,
+            itemData = s.getData(),
+            detno = itemData[detnoColumn],
+            id = itemData.id,
+            dirtyFields = [];
+
+            if(s.isDirty()) {
+                var modified = s.modified;
+                var dirtyFields = Ext.Object.getAllKeys(modified);
+            }
+
+            // 如果有有效数据才算dirty,否则直接commit
+            if(dirtyFields.length == 2 && Ext.Array.contains(dirtyFields, 'id') && Ext.Array.contains(dirtyFields, detnoColumn)) {
+                s.commit();
+            }else if(!Ext.isNumber(id) && dirtyFields.length == 1 && dirtyFields[0] == detnoColumn) {
+                s.commit();
+            }
+        });
+
+        me.fireEvent('validChange');
+        return r;
+    },
+
+    deleteDetail: function(v) {
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+
+        var detnoColumn = me.detnoColumn,
+        store = me.getStore(),
+        records = store.getData().items,
+        selectedRecord = records.find(function(r) {
+            return r.get(detnoColumn) == v;
+        });
+
+        var id = selectedRecord.data.id;
+
+        if(id&&id!=0&&(typeof id) == 'number') {
+            saas.util.BaseUtil.showConfirm('警告', '确定删除该条明细')
+            .then(function(yes) {
+                if(yes == 'yes') {
+                    saas.util.BaseUtil.request({
+                        url: me.deleteDetailUrl + '/' + id,
+                        params: '',
+                        method: 'POST',
+                    })
+                    .then(function() {
+                        store.remove(selectedRecord);
+                        me.fireEvent('validChange');
+                        //解析参数
+                        saas.util.BaseUtil.showSuccessToast('删除成功');
+                    })
+                    .catch(function(e) {
+                        me.fireEvent('validChange');
+                        //失败
+                        saas.util.BaseUtil.showErrorToast('删除失败: ' + e.message);
+                    });
+                }else {
+                    throw new Error();
+                }
+            })
+        }else {
+            store.remove(selectedRecord);
+            me.fireEvent('validChange');
+        }
+    },
+
+    swapUp: function() {
+        var me = this;
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var store = me.getStore(),
+        record = me.selModel.lastSelected,
+        selectedIdx = store.indexOf(record);
+
+        me.swap(record, selectedIdx, -1);
+    },
+
+    swapDown: function() {
+        var me = this;
+        var me = this;
+        if(!me.editable) {
+            return;
+        }
+        var store = me.getStore(),
+        record = me.selModel.lastSelected,
+        selectedIdx = store.indexOf(record);
+        
+        me.swap(record, selectedIdx, 1);
+    },
+
+    swap: function(from, index, dir) {
+        var me = this,
+        store = me.getStore(),
+        to = store.getAt(index + dir);
+
+        if(from && to) {
+            var keys = me.getColumns().map(function(c) {
+                //剔除序号字段
+                if(c.dataIndex!=me.detnoColumn){
+                    return c.dataIndex 
+                }
+            }),
+            data = from.getData(),
+            toData = to.getData();
+
+            Ext.each(keys, function(key, index) {
+                to.set(key, null);
+                from.set(key, toData[key]);
+                to.set(key, data[key]);
+            });
+            //聚焦目标行
+            me.selModel.select(to);
+        }
+        me.fireEvent('validChange');
+    },
+
+    clearDirty: function() {
+        var me = this,
+        store = me.store,
+        count = store.getCount();
+
+        for(var x = 0; x < count; x++) {
+            store.getAt(x).commit();
+        }
+    },
+
+    setGridDisabled: function(able) {
+        var me = this,
+        columns = me.columns;
+
+        me.editable = able;
+
+        Ext.Array.each(columns, function(c) {
+            if(typeof c.getEditor != 'undefined'){
+                var e = c.getEditor();
+                if(e) {
+                    typeof e.setDisabled == 'function' && e.setDisabled(!able);
+                }
+            }
+        });
+    },
+
+    /**
+     * 判断grid数据是否合法
+     */
+    isValid: function() {
+        var me = this,
+        allowEmpty = me.allowEmpty;
+        columns = me.columns,
+        data = me.getTrueData(),
+        valid = allowEmpty;
+
+        // 判断列必填
+        a:
+        for(var i = 0; i < columns.length; i++) {
+            var c = columns[i];
+            var cname = c.dataIndex;
+            var allowBlank = c.allowBlank;
+            var isValid = c.isValid;
+            
+            b:
+            for(var j = 0; j < data.length; j++) {
+                valid = true;
+                var d = data[j];
+                var value = d[cname];
+
+                if(typeof isValid == 'function') {
+                    if(!isValid(value)) {
+                        valid = false;
+                        break a;
+                    }
+                }
+                if(!allowBlank) {
+                    if(!value) {
+                        valid = false;
+                        break a;
+                    }
+                }
+            }
+        }
+
+        return valid;
+    },
+
+    isDirty: function() {
+        var me = this,
+        store = me.getStore(),
+        dataItems = store.data.items;
+
+        for(var i = 0; i < dataItems.length; i++) {
+            var item = dataItems[i];
+            if(item.dirty) {
+                return true;
+            }
+        }
+
+        return false;
+    },
+
+    /**
+     * 获得所有数据
+     */
+    getAllData: function() {
+        var me = this,
+        store = me.getStore(),
+        storeData = store.getData().items,
+        allData = [];
+
+        Ext.Array.each(storeData, function(item){
+            var d = Object.assign({}, item.data);
+
+            if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                d.id = 0;
+            }
+            for(k in d) {
+                if(Ext.isDate(d[k])) {
+                    d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                }
+            }
+            allData.push(d);
+        });
+
+        return allData;
+    },
+
+    getSaveData: function() {
+        var me = this,
+        store = me.getStore(),
+        allData = store.getData().items,
+        dirtyData = [];
+
+        Ext.Array.each(allData, function(item){
+            var d = Object.assign({}, item.data),
+            dirty = item.dirty;
+
+            if(dirty){
+                if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                    d.id = 0;
+                }
+                for(k in d) {
+                    if(Ext.isDate(d[k])) {
+                        d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                    }
+                }
+                dirtyData.push(d);
+            }
+        });
+        return dirtyData;
+    },
+
+    /**
+     * 获得有效数据
+     */
+    getTrueData: function() {
+        var me = this,
+        store = me.getStore(),
+        allData = store.getData().items,
+        trueData = [];
+
+        Ext.Array.each(allData, function(item){
+            var d = Object.assign({}, item.data),
+            dirty = item.dirty;
+
+            if(dirty){
+                if((typeof d.id) != "number" && d.id.indexOf('-')>-1){
+                    d.id = 0;
+                }
+                for(k in d) {
+                    if(Ext.isDate(d[k])) {
+                        d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                    }
+                }
+                trueData.push(d);
+            }else {
+                if(typeof d.id == "number") {
+                    for(k in d) {
+                        if(Ext.isDate(d[k])) {
+                            d[k] = Ext.Date.format(d[k], 'Y-m-d H:i:s');
+                        }
+                    }
+                    trueData.push(d);
+                }
+            }
+        });
+        return trueData;
+    },
+
+    getDirtyData: function() {
+        var me = this,
+        store = me.getStore(),
+        modifiedData = store.getModifiedRecords(),
+        dirtyData = [];
+
+        Ext.Array.each(modifiedData, function(m, index) {
+            var modified = m.modified;
+            dirtyData.push(modified);
+        });
+
+        return dirtyData;
+    },
+});

+ 49 - 0
frontend/operation-web/app/view/core/form/field/DetailGridField.scss

@@ -0,0 +1,49 @@
+.x-detailgridfield {
+
+    .x-grid-view {
+
+        .x-grid-empty {
+            padding: 8px 10px;
+        }
+    }
+}
+
+.x-column-header{
+    text-align: center !important;
+    border-width: 1px;
+}
+.x-column-header .x-column-header-text {
+    font: "microsoft yahei";
+    text-align: center;
+    font-size:14px;
+    font-weight: 500;
+    margin-right: 0;
+}
+.x-grid-necessary .x-column-header-text:before{
+    content: '*';
+    font-size: 130%;
+    color: #FF002B;
+    width: 5px;
+    height: 5px;
+    margin-top: 4px;
+    margin-left: -10px;
+    position: absolute;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    border: none;
+}
+
+.x-grid-locked .x-grid-inner-locked {
+    
+    .x-grid-header-ct {
+        border-right: 1px solid #ABDAFF !important;
+    }
+}
+
+.x-grid-scrollbar-clipper-locked, .x-grid-scrollbar-locked {
+
+    .x-grid-view {
+        border-right: 1px solid #ABDAFF !important;
+    }
+}

+ 15 - 0
frontend/operation-web/app/view/core/form/field/SeparateField.js

@@ -0,0 +1,15 @@
+Ext.define('saas.view.core.form.field.SeparateField', {
+    extend: 'Ext.container.Container',
+    xtype: 'separatefield',
+    userCls: 'x-field-separator',
+    height: 36,
+    columnWidth: 1,
+    ignore: true,
+    // initHidden: true,
+    isValid: function() {
+        return true;
+    },
+    isDirty: function() {
+        return false;
+    }
+});

+ 3 - 0
frontend/operation-web/app/view/core/tab/Panel.js

@@ -10,6 +10,9 @@ Ext.define('saas.view.core.tab.Panel', {
         padding: '0 16 0 0'
     },
     closable: true,
+    tabConfig: {
+        maxWidth: 128
+    },
     
     listeners: {
         activate: 'onTabActivate',

+ 5 - 0
frontend/operation-web/app/view/core/tab/Panel.scss

@@ -2,4 +2,9 @@
     &>div>.x-panel-body {
         background: $panel-body-background;
     }
+}
+.main-right-tabpanel {
+    .x-tab-inner {
+        max-width: 102px;
+    }
 }

+ 9 - 1
frontend/operation-web/app/view/statistical/CompanyAnalysis.js

@@ -31,7 +31,15 @@ Ext.define('saas.view.statistical.CompanyAnalysis', {
                 }, {
                     text: '企业名称',
                     dataIndex: 'ca_company',
-                    width: 200
+                    width: 200,
+                    listeners: {
+                        click: function(tableView, td, rowIdx, colIdx, e, model, tr) {
+                            var data = model.data;
+                            saas.util.BaseUtil.openTab('companyinfo', data.ca_company , data.ca_id, {
+                                initId: data.ca_companyid
+                            });
+                        }
+                    }
                 }, {
                     text: '企业地址',
                     dataIndex: 'ca_address',

+ 110 - 0
frontend/operation-web/app/view/statistical/CompanyInfo.js

@@ -0,0 +1,110 @@
+Ext.define('saas.view.statistical.CompanyInfo', {
+    extend: 'saas.view.core.form.FormPanel',
+    xtype: 'companyinfo',
+
+    viewModel: 'companyinfo',
+
+    _readUrl: '/api/operation/data/companyAnalyzeRead',
+    codeInHeader: false,
+
+    initComponent: function () {
+        var me = this;
+        Ext.apply(this, {
+            defaultItems: [{
+                xtype: 'hidden',
+                name: 'ca_id',
+                fieldLabel: 'id'
+            }, {
+                xtype: 'textfield',
+                name: 'ca_companyid',
+                fieldLabel: '客户编号'
+            }, {
+                xtype: "textfield",
+                name: "ca_company",
+                fieldLabel: "客户名称",
+            }, {
+                xtype: "textfield",
+                name: "ca_address",
+                fieldLabel: "地址",
+            }, {
+                xtype: "textfield",
+                name: "ca_admin",
+                fieldLabel: "管理员",
+            }, {
+                xtype: "datefield",
+                name: "ca_createtime",
+                format: 'Y-m-d H:i:s',
+                fieldLabel: "注册时间",
+            }, {
+                xtype: "datefield",
+                name: "ca_newestlogtime",
+                format: 'Y-m-d H:i:s',
+                fieldLabel: "最后操作时间",
+            }, {
+                xtype: 'textfield',
+                name: 'ca_phase',
+                fieldLabel: '目前阶段',
+            }, {
+                xtype: 'textfield',
+                name: 'ca_status',
+                fieldLabel: '使用状态',
+            }, {
+                name: "detailGridField",
+                xtype: "detailGridField",
+                storeModel: 'saas.model.statistical.CompanyInfo',
+                detnoColumn: 'ord_detno',
+                deleteDetailUrl: '/api/money/othreceipts/deleteDetail',
+                showCount: false,
+                columns: [{
+                    text: "账户统计",
+                    dataIndex: "cd_accountnum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "物料资料统计",
+                    dataIndex: "cd_productnum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "客户资料统计",
+                    dataIndex: "cd_customernum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "供应商资料统计",
+                    dataIndex: "cd_vendornum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "销售订单统计",
+                    dataIndex: "cd_salenum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "采购订单统计",
+                    dataIndex: "cd_purchasenum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "出入库单据统计",
+                    dataIndex: "cd_prodionum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "付款单统计",
+                    dataIndex: "cd_paynum",
+                    width: 140,
+                    align: 'end'
+                }, {
+                    text: "收款单统计",
+                    dataIndex: "cd_receivenum",
+                    width: 140,
+                    align: 'end'
+                }]
+            }],
+        });
+        this.callParent();
+        this.setEditable(false);
+    },
+
+});

+ 14 - 0
frontend/operation-web/app/view/statistical/CompanyInfoModel.js

@@ -0,0 +1,14 @@
+Ext.define('saas.view.statistical.CompanyInfoModel', {
+    extend: 'saas.view.core.form.FormPanelModel',
+    alias: 'viewmodel.companyinfo',
+
+    data: {
+        id: 0,
+        base: {
+            bindFields: [], // 绑定字段
+            editable: false, // 单据是否可编辑
+        },
+        detailBindeFields: [], // 从表绑定列
+        detailStore: null, // 从表store
+    },
+});

+ 16 - 0
frontend/operation-web/overrides/form/field/Date.js

@@ -0,0 +1,16 @@
+Ext.define('saas.override.form.field.Date', {
+    override: 'Ext.form.field.Date',
+    formatText: '',
+
+    setValue: function (v) {
+        var me = this;
+
+        if(v && me.format) {
+            v = new Date(v);
+            v = Ext.Date.format(v, me.format);
+        }
+
+        me.callParent(arguments);
+    },
+
+});