首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

ExtJS 6/7 實現 Form 與 Grid 聯動:稅種下拉選擇觸發含稅金額實時重算完整方案

     編輯:程式設計綜合問答

ExtJS 經典場景:窗口包含 FormPanel(輸入金額、選擇稅率)與 GridPanel(明細行),需實現「稅種下拉變更 → 當前行含稅金額自動重算 → 總計更新」。本文提供基於 ViewModel、Binding、ComboBox change 事件 的完整 MVVM 實現方案。

核心架構:ViewModel 雙向綁定 + 計算字段


// ViewModel 定義
Ext.define('MyApp.view.invoice.InvoiceViewModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.invoice',
    
    formulas: {
        // 總含稅金額計算屬性
        totalTaxAmount: {
            get: function(get) {
                var store = get('detailStore');
                var total = 0;
                store.each(function(rec) {
                    total += rec.get('taxAmount') || 0;
                });
                return total;
            }
        }
    },
    
    stores: {
        detailStore: {
            model: 'MyApp.model.InvoiceDetail',
            autoLoad: false,
            data: [] // 初始空數據
        }
    }
});

Model 定義:含稅金額計算邏輯封裝


Ext.define('MyApp.model.InvoiceDetail', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'itemName', type: 'string' },
        { name: 'quantity', type: 'number', defaultValue: 1 },
        { name: 'unitPrice', type: 'number', defaultValue: 0 },
        { name: 'taxRate', type: 'number', defaultValue: 0.13 }, // 稅率 13%
        { name: 'taxType', type: 'string' }, // 稅種代碼
        // 計算字段:不持久化,自動重算
        { 
            name: 'amount', 
            type: 'number', 
            calculate: function(data) {
                return (data.quantity || 0) * (data.unitPrice || 0);
            }
        },
        { 
            name: 'taxAmount', 
            type: 'number', 
            calculate: function(data) {
                var amt = (data.quantity || 0) * (data.unitPrice || 0);
                return amt * (data.taxRate || 0);
            }
        },
        { 
            name: 'totalAmount', 
            type: 'number', 
            calculate: function(data) {
                var amt = (data.quantity || 0) * (data.unitPrice || 0);
                return amt * (1 + (data.taxRate || 0));
            }
        }
    ]
});

View 定義:Form + Grid 聯動實現


Ext.define('MyApp.view.invoice.InvoiceWindow', {
    extend: 'Ext.window.Window',
    title: '發票錄入',
    width: 800,
    height: 500,
    layout: 'border',
    viewModel: { type: 'invoice' },
    
    items: [{
        // 北部:主表單
        region: 'north',
        xtype: 'form',
        height: 120,
        bodyPadding: 10,
        reference: 'headerForm',
        items: [{
            xtype: 'fieldcontainer',
            layout: 'hbox',
            items: [
                { xtype: 'textfield', fieldLabel: '發票號', bind: '{invoiceNo}', flex: 1 },
                { xtype: 'datefield', fieldLabel: '開票日期', bind: '{invoiceDate}', flex: 1 }
            ]
        }]
    }, {
        // 中部:明細 Grid
        region: 'center',
        xtype: 'grid',
        reference: 'detailGrid',
        bind: { store: '{detailStore}' },
        plugins: {
            ptype: 'cellediting',
            clicksToEdit: 1
        },
        columns: [
            { text: '商品', dataIndex: 'itemName', flex: 2, editor: 'textfield' },
            { text: '數量', dataIndex: 'quantity', width: 80, editor: { xtype: 'numberfield', minValue: 0 } },
            { text: '單價', dataIndex: 'unitPrice', width: 100, editor: { xtype: 'numberfield', minValue: 0 } },
            { 
                text: '稅種', 
                dataIndex: 'taxType', 
                width: 120,
                editor: {
                    xtype: 'combobox',
                    store: {
                        fields: ['code', 'name', 'rate'],
                        data: [
                            { code: 'VAT13', name: '增值稅 13%', rate: 0.13 },
                            { code: 'VAT9', name: '增值稅 9%', rate: 0.09 },
                            { code: 'VAT6', name: '增值稅 6%', rate: 0.06 },
                            { code: 'FREE', name: '免稅', rate: 0 }
                        ]
                    },
                    displayField: 'name',
                    valueField: 'code',
                    queryMode: 'local',
                    listeners: {
                        // 關鍵:選擇稅種時同步更新 taxRate 字段,觸發 calculate 重算
                        select: function(combo, record) {
                            var grid = combo.up('grid');
                            var editing = grid.getPlugin('cellediting');
                            var context = editing.getActiveContext();
                            if (context) {
                                context.record.set('taxRate', record.get('rate'));
                                // taxAmount、totalAmount 會自動重算 (calculate 字段)
                            }
                        }
                    }
                },
                renderer: function(val) {
                    var store = this.getEditor().getStore();
                    var rec = store.findRecord('code', val);
                    return rec ? rec.get('name') : val;
                }
            },
            { text: '稅率', dataIndex: 'taxRate', width: 80, renderer: function(v) { return (v*100).toFixed(2) + '%'; } },
            { text: '金額', dataIndex: 'amount', width: 100, renderer: Ext.util.Format.numberRenderer('0,0.00') },
            { text: '稅額', dataIndex: 'taxAmount', width: 100, renderer: Ext.util.Format.numberRenderer('0,0.00') },
            { text: '含稅價', dataIndex: 'totalAmount', width: 100, renderer: Ext.util.Format.numberRenderer('0,0.00') },
            { 
                xtype: 'actioncolumn', 
                width: 50, 
                items: [{
                    iconCls: 'x-fa fa-trash',
                    handler: 'onDeleteRow'
                }]
            }
        ],
        tbar: [
            { text: '新增行', iconCls: 'x-fa fa-plus', handler: 'onAddRow' }
        ],
        bbar: [
            '->',
            { 
                xtype: 'displayfield', 
                bind: '{totalTaxAmount}', 
                renderer: function(v) { return '總含稅金額: ' + Ext.util.Format.number(v, '0,0.00'); }
            }
        ]
    }],
    
    // Controller 方法 (或 ViewController)
    controller: {
        onAddRow: function() {
            var store = this.getViewModel().getStore('detailStore');
            store.add({ itemName: '', quantity: 1, unitPrice: 0, taxType: 'VAT13', taxRate: 0.13 });
        },
        
        onDeleteRow: function(grid, rowIndex) {
            var store = grid.getStore();
            store.removeAt(rowIndex);
        }
    }
});

關鍵技術點解析

技術點作用關鍵代碼
Model calculate 字段聲明式計算邏輯,依賴字段變化自動重算,無需手動監聽calculate: function(data) { return data.quantity * data.unitPrice * data.taxRate; }
ComboBox select 事件用戶選擇稅種時,將稅率寫入 record,觸發 calculatecontext.record.set('taxRate', record.get('rate'))
CellEditing 插件啟用單元格編輯模式,編輯後自動同步到 storeptype: 'cellediting', clicksToEdit: 1
ViewModel formulas總計字段響應式更新,綁定到 bbar displayfieldformulas: { totalTaxAmount: { get: function(get) { ... } } }
Store 綁定Grid 與 Form 共享同一 store,數據單一來源bind: { store: '{detailStore}' }

進階:稅率變更時批量更新已有行

若需「修改表頭稅率 → 所有明細行稅率同步更新」:


// ViewModel 添加表頭稅率字段
data: {
    headerTaxRate: 0.13
},
formulas: {
    // ...
}

// 表頭 Form 綁定
{ xtype: 'combobox', fieldLabel: '默認稅率', bind: '{headerTaxRate}', 
  store: taxRateStore, displayField: 'name', valueField: 'rate',
  listeners: {
      change: function(combo, newRate) {
          var store = this.getViewModel().getStore('detailStore');
          store.each(function(rec) {
              // 僅更新未手動修改稅率的行 (可擴展 dirty 追蹤)
              if (!rec.dirtyTaxRate) rec.set('taxRate', newRate);
          });
      }
  }
}

總結

ExtJS 實現 Form-Grid 聯動的核心是Model.calculate 聲明式計算字段配合ComboBox 選擇事件同步關鍵字段。利用 MVVM 雙向綁定,稅率變更 → record.set('taxRate', ...) → calculate 自動重算 → Grid 單元格更新 → ViewModel formulas 總計更新,形成完整響應式鏈路,代碼零手動刷新、零監聽雜亂。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved