You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Odoo向导内Tree View按钮异常:初始不可点击,过滤后恢复正常

Odoo嵌入向导Tree View按钮渲染异常

问题现象

在向导内嵌入Tree View后,首次打开向导时,自定义按钮显示在列表底部且无法点击;执行筛选或分组操作后再取消,按钮会自动移至列表表头区域,且可正常触发点击事件。

界面状态说明

  • 首次打开向导:按钮位于列表底部,点击无响应
  • 应用筛选操作:执行列表筛选功能
  • 取消筛选操作:撤销筛选条件
  • 按钮恢复正常:按钮移至表头,点击可正常触发逻辑

相关配置与代码

Tree View配置

<record id="compatible_products_wizard_tree_view" model="ir.ui.view">
        <field name="name">compatible.products.wizard.tree</field>
        <field name="model">compatible.products.wizard</field>
        <field name="arch" type="xml">
            <tree string="Selected Products" create="0" edit="0" export_xlsx="false" js_class="buttton_in_tree">
                <field name="mrp_id"/>
                <field name="product_id"/>
                <field name="lst_price"/>
                <field name="standard_price"/>
                <field name="qty_available"/>
            </tree>
        </field>
</record>

按钮模板

<?xml version="1.0" encoding="UTF-8"?>

<templates>
    <t t-extend="ListView.buttons" t-name="de_alfa_web_connector.buttons">
        <t t-jquery=".o_list_buttons" t-operation="replace">
            <div class="o_list_buttons" role="toolbar" aria-label="Main actions">
                <button type="button" class="btn btn-info open_wizard_action" style="display:none;">
                    Create Moves
                </button>
            </div>
        </t>
    </t>
    <t t-extend="ListView.selection">
        <t t-jquery=".o_list_select_domain" t-operation="after">
            <button t-if="nbSelected > 0" type="button" class="btn btn-info open_wizard_action">
                Create Moves
            </button>
        </t>
    </t>
</templates>

JS代码

odoo.define('de_alfa_web_connector.tree_button', function (require) {
    "use strict";
    var core = require('web.core');
    var ListController = require('web.ListController');
    var ListRenderer = require('web.ListRenderer');
    var ListView = require('web.ListView');
    var viewRegistry = require('web.view_registry');
    var TreeButton = ListController.extend({
        buttons_template: 'de_alfa_web_connector.buttons',
        events: _.extend({}, ListController.prototype.events, {
            'click .open_wizard_action': '_OpenWizard',
        }),

        _OpenWizard: function (ev) {
            var treeView = this.renderer;
            var checkedRows = treeView.$("tbody input[type='checkbox']:checked").closest('tr');
            var extractedData = [];
            checkedRows.each(function () {
                var rowData = {};
                $(this).find('td').each(function (columnIndex) {
                    var inputElement = $(this).find('input, select');
                    if (inputElement.length > 0) {
                        rowData[columnIndex] = inputElement.val().trim();
                    } else {
                        rowData[columnIndex] = $(this).text().trim();
                    }
                });
                extractedData.push(rowData);
            });

            var self = this;
            var lineVals = [];
            var promises = [];
            var Active_id;
            console.log('Extracted', extractedData);
            extractedData.forEach(function (data) {
                Active_id = data[1];
                Active_id = Active_id.replace(/,/g, '');
                Active_id = parseInt(Active_id);
                var firstSplit = data[2].split('[');
                var lastSplit = firstSplit[1].split(']');
                var OldId = lastSplit[0];
                var p_name = data[2];

                var productPromise = self._rpc({
                    model: 'product.product',
                    method: 'search_read',
                    args: [[['old_id', '=', OldId]], ['id', 'name', 'uom_id', 'product_tmpl_id']],
                }).then(function (result) {
                    return result[0] || false;
                });
                promises.push(productPromise);

                var linePromise = Promise.all([productPromise]).then(function (results) {
                    var product = results[0];
                    if (product) {
                        var line = [0, 0, {
                            'product_id': product.id,
                            'name': p_name,
                            'product_uom_qty': 1,
                            'product_uom': product.uom_id[0],
                            'raw_material_production_id': Active_id,
                            'location_id': 15,
                            'location_dest_id': 8,
                            'picking_type_id': 41,
                        }];
                        lineVals.push(line);
                    }
                });
                promises.push(linePromise);
            });

            Promise.all(promises).then(function () {
                if (lineVals.length > 0) {
                    var moveLineVals = lineVals.map(function (lineVal) {
                        return lineVal[2];
                    });

                    var movePromise = self._rpc({
                        model: 'stock.move',
                        method: 'create',
                        args: [moveLineVals],
                    });

                    movePromise.then(function (result) {
                        console.log('Lines created successfully:', result);
                        // Update move_raw_ids field of mrp.production with the created stock.move IDs
                        var productionID = Active_id;
//                        var productionPromise = self._rpc({
//                            model: 'mrp.production',
//                            method: 'write',
//                            args: [[productionID], { move_raw_ids: result }],
//                        });
                        var productionPromise = self._rpc({
                                model: 'mrp.production',
                                method: 'read',
                                args: [[productionID], ['move_raw_ids']],
                            }).then(function (production) {
                                    var existingMoveRawIds = production[0].move_raw_ids || [];
                                    var updatedMoveRawIds = existingMoveRawIds.concat(result);
                                    return self._rpc({
                                            model: 'mrp.production',
                                            method: 'write',
                                                args: [[productionID], { move_raw_ids: updatedMoveRawIds }],
                                                });
                                });

                        productionPromise.then(function () {
                            console.log('move_raw_ids field updated successfully');
                            self.trigger_up('history_back');
                        }).guardedCatch(function (error) {
                            console.error('Failed to update move_raw_ids field:', error);
                        });
                    }).guardedCatch(function (error) {
                        console.error('Failed to create lines:', error);
                    });
                }
            });
        },
    });

    var CompatibleProductsListView = ListView.extend({
        config: _.extend({}, ListView.prototype.config, {
            Controller: TreeButton,
        }),
    });

    viewRegistry.add('buttton_in_tree', CompatibleProductsListView);
});

Python代码

def open_compatible_products_wizard(self):
        self.ensure_one()
        compatible_products = self.move_raw_ids_compatible_products
        sql = "delete from compatible_products_wizard"
        self.env.cr.execute(sql)
        m_id = self.id
        for item in compatible_products:
            wizard = self.env['compatible.products.wizard'].create({
                'mrp_id': m_id,
                'product_id': item.id,
            })

        return {
            'name': 'Compatible Products',
            'view_mode': 'tree',
            'view_id': self.env.ref('de_alfa_web_connector.compatible_products_wizard_tree_view').id,
            'res_model': 'compatible.products.wizard',
            'type': 'ir.actions.act_window',
            'target': 'new',
        }

问题修复方案

原因分析

首次渲染时,Tree View的表头按钮区域未正确初始化,自定义按钮被错误插入到列表底部;筛选/分组操作触发列表重新渲染,此时模板逻辑被正确执行,按钮插入到预期位置。

具体修复步骤

  1. 简化按钮模板:移除对ListView.selection的扩展,避免模板渲染冲突,仅保留表头按钮区域的扩展:
<?xml version="1.0" encoding="UTF-8"?>
<templates>
    <t t-extend="ListView.buttons" t-name="de_alfa_web_connector.buttons">
        <t t-jquery=".o_list_buttons" t-operation="append">
            <button type="button" class="btn btn-info open_wizard_action" style="display:none;">
                Create Moves
            </button>
        </t>
    </t>
</templates>
  1. 控制器中处理按钮显示逻辑:监听列表选择事件,动态控制按钮显示/隐藏,确保首次渲染时按钮位置正确:
// 在TreeButton控制器中添加以下代码
init: function () {
    this._super.apply(this, arguments);
    this.selectedRecords = [];
},

_onSelectionChange: function (ev) {
    this._super.apply(this, arguments);
    this.selectedRecords = ev.data.ids;
    this.$('.open_wizard_action').toggle(this.selectedRecords.length > 0);
},

events: _.extend({}, ListController.prototype.events, {
    'click .open_wizard_action': '_OpenWizard',
    'selection_change': '_onSelectionChange',
}),
  1. 优化数据提取逻辑:避免通过DOM提取数据,直接使用Odoo提供的选中记录ID获取数据,提升稳定性:
_OpenWizard: function (ev) {
    var self = this;
    var selectedIds = this.selectedRecords;
    if (!selectedIds.length) return;

    this._rpc({
        model: 'compatible.products.wizard',
        method: 'read',
        args: [selectedIds, ['mrp_id', 'product_id']],
    }).then(function (records) {
        // 基于读取的records处理后续逻辑,替代原DOM提取方式
        records.forEach(function(record) {
            // 处理单个记录逻辑
        });
    });
}

内容的提问来源于stack exchange,提问作者DZEUF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:07:56