From f7d3620de073598d2d8595cc1cd2082dcae1cd3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Tue, 24 Jun 2025 10:53:12 +0800 Subject: [PATCH] 1 --- oo_list_manager/__init__.py | 4 + oo_list_manager/__manifest__.py | 38 +++++ oo_list_manager/controllers/__init__.py | 3 + oo_list_manager/controllers/controllers.py | 22 +++ oo_list_manager/models/__init__.py | 3 + oo_list_manager/models/ir_ui_view.py | 20 +++ oo_list_manager/security/ir.model.access.csv | 2 + .../auto_hide_list/auto_hide_list_renderer.js | 30 ++++ .../auto_hide_list/auto_hide_list_view.js | 12 ++ .../static/src/views/list/list_controller.js | 156 ++++++++++++++++++ .../src/views/list/list_controller.scss | 47 ++++++ .../static/src/views/list/list_controller.xml | 69 ++++++++ .../static/src/views/list/list_renderer.js | 147 +++++++++++++++++ .../static/src/views/list/list_renderer.xml | 57 +++++++ 14 files changed, 610 insertions(+) create mode 100644 oo_list_manager/__init__.py create mode 100644 oo_list_manager/__manifest__.py create mode 100644 oo_list_manager/controllers/__init__.py create mode 100644 oo_list_manager/controllers/controllers.py create mode 100644 oo_list_manager/models/__init__.py create mode 100644 oo_list_manager/models/ir_ui_view.py create mode 100644 oo_list_manager/security/ir.model.access.csv create mode 100644 oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_renderer.js create mode 100644 oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_view.js create mode 100644 oo_list_manager/static/src/views/list/list_controller.js create mode 100644 oo_list_manager/static/src/views/list/list_controller.scss create mode 100644 oo_list_manager/static/src/views/list/list_controller.xml create mode 100644 oo_list_manager/static/src/views/list/list_renderer.js create mode 100644 oo_list_manager/static/src/views/list/list_renderer.xml diff --git a/oo_list_manager/__init__.py b/oo_list_manager/__init__.py new file mode 100644 index 000000000..aa4d0fd63 --- /dev/null +++ b/oo_list_manager/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models diff --git a/oo_list_manager/__manifest__.py b/oo_list_manager/__manifest__.py new file mode 100644 index 000000000..d725014ee --- /dev/null +++ b/oo_list_manager/__manifest__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +{ + 'name': "oo_list_manager", + + 'summary': "Short (1 phrase/line) summary of the module's purpose", + + 'description': """ +Long description of module's purpose + """, + + 'author': "My Company", + 'website': "https://www.yourcompany.com", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml + # for the full list + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['base', 'web', 'tree_view_advanced_filter'], + + # always loaded + 'data': [ + # 'security/ir.model.access.csv', + ], + # only loaded in demonstration mode + "application": True, + "auto_install": False, + 'license': 'LGPL-3', + 'assets': { + 'web.assets_backend': [ + 'oo_list_manager/static/src/views/list/*', + 'oo_list_manager/static/src/views/auto_hide_list/*', + ] + }, +} + diff --git a/oo_list_manager/controllers/__init__.py b/oo_list_manager/controllers/__init__.py new file mode 100644 index 000000000..b0f26a9a6 --- /dev/null +++ b/oo_list_manager/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers diff --git a/oo_list_manager/controllers/controllers.py b/oo_list_manager/controllers/controllers.py new file mode 100644 index 000000000..34e3f1bab --- /dev/null +++ b/oo_list_manager/controllers/controllers.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# from odoo import http + + +# class OoListManager(http.Controller): +# @http.route('/oo_list_manager/oo_list_manager', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/oo_list_manager/oo_list_manager/objects', auth='public') +# def list(self, **kw): +# return http.request.render('oo_list_manager.listing', { +# 'root': '/oo_list_manager/oo_list_manager', +# 'objects': http.request.env['oo_list_manager.oo_list_manager'].search([]), +# }) + +# @http.route('/oo_list_manager/oo_list_manager/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('oo_list_manager.object', { +# 'object': obj +# }) + diff --git a/oo_list_manager/models/__init__.py b/oo_list_manager/models/__init__.py new file mode 100644 index 000000000..491b85465 --- /dev/null +++ b/oo_list_manager/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import ir_ui_view diff --git a/oo_list_manager/models/ir_ui_view.py b/oo_list_manager/models/ir_ui_view.py new file mode 100644 index 000000000..81202e493 --- /dev/null +++ b/oo_list_manager/models/ir_ui_view.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class IrUiView(models.Model): + _inherit = 'ir.ui.view' + + manager_data = fields.Json(string='data') + + def get_manager_data(self): + self.ensure_one() + return self.manager_data or {} + + def save_manager_data(self, manager_data): + self.ensure_one() + self.sudo().write({ + 'manager_data': manager_data + }) + return True diff --git a/oo_list_manager/security/ir.model.access.csv b/oo_list_manager/security/ir.model.access.csv new file mode 100644 index 000000000..aa63a2bda --- /dev/null +++ b/oo_list_manager/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_oo_list_manager_oo_list_manager,oo_list_manager.oo_list_manager,model_oo_list_manager_oo_list_manager,base.group_user,1,1,1,1 diff --git a/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_renderer.js b/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_renderer.js new file mode 100644 index 000000000..c069a3a25 --- /dev/null +++ b/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_renderer.js @@ -0,0 +1,30 @@ +/** @odoo-module **/ + +import {ListRenderer} from "@web/views/list/list_renderer"; + +export class AutoHideListRenderer extends ListRenderer { + + getActiveColumns(list) { + const columns = super.getActiveColumns(list); + let values; + if (this.props.list.selection && this.props.list.selection.length) { + values = this.props.list.selection.map((r) => r.data); + } else if (this.props.list.isGrouped) { + values = this.props.list.groups.map((g) => g.aggregates); + } else { + values = this.props.list.records.map((r) => r.data); + } + return columns.filter(column => { + const fieldInfo = list.fields[column.name]; + if (["float", "integer", "monetary"].includes(fieldInfo.type)) { + const fieldValues = values.map((v) => v[fieldInfo.name] || 0); + const aggregateValue = fieldValues.reduce((acc, val) => acc + val); + if (!aggregateValue) { + return false; + } + } + return true; + }) + } + +} \ No newline at end of file diff --git a/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_view.js b/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_view.js new file mode 100644 index 000000000..cc44a2638 --- /dev/null +++ b/oo_list_manager/static/src/views/auto_hide_list/auto_hide_list_view.js @@ -0,0 +1,12 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; +import { listView } from "@web/views/list/list_view"; +import { AutoHideListRenderer } from "./auto_hide_list_renderer"; + +export const autoHideListView = { + ...listView, + Renderer: AutoHideListRenderer +} + +registry.category("views").add("autoHideListView", autoHideListView); \ No newline at end of file diff --git a/oo_list_manager/static/src/views/list/list_controller.js b/oo_list_manager/static/src/views/list/list_controller.js new file mode 100644 index 000000000..53f1b4429 --- /dev/null +++ b/oo_list_manager/static/src/views/list/list_controller.js @@ -0,0 +1,156 @@ +/** @odoo-module **/ + +import {ListController} from "@web/views/list/list_controller"; +import {patch} from "@web/core/utils/patch"; +import {onWillStart, useState, useRef, useExternalListener} from "@odoo/owl"; +import {Dropdown} from "@web/core/dropdown/dropdown"; +import {CheckBox} from "@web/core/checkbox/checkbox"; +import {useSortable} from "@web/core/utils/sortable_owl"; +import {useService} from "@web/core/utils/hooks"; +import {usePosition} from "@web/core/position_hook"; + + +patch(ListController.prototype, { + setup() { + super.setup(); + this.isChange = false; + this.orm = useService('orm'); + this.ooColumnsRef = useRef('ooColumnsRef'); + this.ooManagerRef = useRef('ooManagerRef'); + this.ooListRef = useRef('ooListRef'); + + this.ooManager = useState({ + state: false, + key: '', + filter: false, + serial_num: false, + columns: this.archInfo.columns.filter((column) => { + if (column.type === "field" && column.widget !== 'handle') { + return column + } + }) + }); + + onWillStart(async () => { + const manager_data = await this.orm.call('ir.ui.view', 'get_manager_data', [this.env.config.viewId]); + if (!Object.keys(manager_data).length) { + this.ooManager.columns.forEach(val => { + val.visibility = !!!val.column_invisible; + }) + } else { + Object.assign(this.ooManager, manager_data); + this.archInfo.advanced_filter = this.ooManager.filter; + } + }) + + usePosition('ooListRef', () => this.ooManagerRef.el, { + position: "bottom-start", + }) + + useSortable({ + ref: this.ooColumnsRef, + elements: ".oo_column", + handle: ".oo_sort_cell", + cursor: "move", + onDrop: (params) => this.sortColumnDrop(params) + }) + + useExternalListener(window, "click", this.onGlobalClick.bind(this)); + }, + + onGlobalClick(ev) { + if (this.ooListRef.el && !this.ooListRef.el.contains(ev.target)) { + this.ooManager.state = false; + this.ooManager.key = ''; + if (this.isChange) { + return this.saveManagerData(); + } + } + }, + + onChangeSerialNum() { + this.isChange = true; + this.ooManager.serial_num = !this.ooManager.serial_num; + }, + + onChangeFilter() { + this.isChange = true; + this.ooManager.filter = !this.ooManager.filter; + this.archInfo.advanced_filter = this.ooManager.filter; + }, + + onChangeColumns() { + const columns = [...this.ooManager.columns]; + this.isChange = true; + this.ooManager.columns = columns; + }, + + /** + * 控制列显示/隐藏 + * @param ev + */ + onChangeVisibility(ev) { + const id = ev.currentTarget.dataset.id; + const columns = [...this.ooManager.columns]; + const columnIdx = columns.findIndex(item => item.id === id); + if (columnIdx >= 0) { + columns[columnIdx].visibility = !columns[columnIdx].visibility; + } + this.isChange = true; + this.ooManager.columns = columns; + }, + + /** + * 排序 + * @param params + */ + sortColumnDrop(params) { + const {element, previous} = params; + + const fromId = element.dataset.id; + const toId = previous ? previous.dataset.id : null; + const columns = [...this.ooManager.columns]; + const fromIndex = columns.findIndex((d) => d.id === fromId); + let toIndex = 0; + if (toId !== null) { + const targetIndex = columns.findIndex((d) => d.id === toId); + toIndex = fromIndex > targetIndex ? targetIndex + 1 : targetIndex; + } + + // 取出被拖拽的元素 + const [moved] = columns.splice(fromIndex, 1); + // 插入到新位置 + columns.splice(toIndex, 0, moved); + columns.forEach((column, i) => { + column.seq = i + 1; + }); + this.isChange = true; + this.ooManager.columns = columns; + }, + + /** + * 保存 + * @returns {*} + */ + saveManagerData() { + this.isChange = false; + return this.orm.call( + 'ir.ui.view', + 'save_manager_data', + [ + this.env.config.viewId, + { + columns: this.ooManager.columns, + filter: this.ooManager.filter, + serial_num: this.ooManager.serial_num, + } + ] + ); + }, +}) + +ListController.components = { + ...ListController.components, + Dropdown, + CheckBox +} \ No newline at end of file diff --git a/oo_list_manager/static/src/views/list/list_controller.scss b/oo_list_manager/static/src/views/list/list_controller.scss new file mode 100644 index 000000000..0437639a1 --- /dev/null +++ b/oo_list_manager/static/src/views/list/list_controller.scss @@ -0,0 +1,47 @@ +.oo_list_manager { + padding: 10px 10px 0 10px; + overflow: hidden; + display: flex !important; + flex-direction: column; + z-index: 999999999999999; + left: 0; + + .oo_columns { + overflow: auto; + + .oo_column { + background-color: white; + + .o_width_input { + width: 50px; + appearance: textfield; + margin: 0 5px 0 5px; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + appearance: none; + margin: 0; + } + } + + .o_lock_box { + margin: 0 5px 0 5px; + padding: 0 5px 0 5px; + + i { + display: block; + font-size: 1.4rem; + cursor: pointer; + + &:hover { + color: #27ae60; + } + } + + .active { + color: #27ae60; + } + } + } + } +} \ No newline at end of file diff --git a/oo_list_manager/static/src/views/list/list_controller.xml b/oo_list_manager/static/src/views/list/list_controller.xml new file mode 100644 index 000000000..88723fe38 --- /dev/null +++ b/oo_list_manager/static/src/views/list/list_controller.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/oo_list_manager/static/src/views/list/list_renderer.js b/oo_list_manager/static/src/views/list/list_renderer.js new file mode 100644 index 000000000..01f08b46b --- /dev/null +++ b/oo_list_manager/static/src/views/list/list_renderer.js @@ -0,0 +1,147 @@ +/** @odoo-module **/ + +import {ListRenderer} from "@web/views/list/list_renderer"; +import {patch} from "@web/core/utils/patch"; +import {loadJS, loadCSS} from "@web/core/assets"; +import {ListRendererCust} from "@tree_view_advanced_filter/tree_view_advanced_filter/tree" + +patch(ListRenderer.prototype, { + setup() { + super.setup(); + if(this.isX2Many) { + this.props.ooManagerSerialNum = true; + } + }, + + getActiveColumns(list) { + if (this.props.ooManagerColumns) { + const columns = this.allColumns.filter((col) => { + if (list.isGrouped && col.widget === "handle") { + return false; // no handle column if the list is grouped + } + const mColumn = this.props.ooManagerColumns.find(item => item.id === col.id) + if (mColumn) { + return mColumn.visibility; + } + if (col.optional && !this.optionalActiveFields[col.name]) { + return false; + } + if (this.evalColumnInvisible(col.column_invisible)) { + return false; + } + return true; + }); + + columns.sort((x, y) => { + const seqX = this.props.ooManagerColumns.find(item => item.id === x.id)?.seq || 0; + const seqY = this.props.ooManagerColumns.find(item => item.id === y.id)?.seq || 0; + return seqX - seqY; + }); + + return columns + + } else { + return super.getActiveColumns(list); + } + }, + + freezeColumnWidths() { + super.freezeColumnWidths(); + const table = this.tableRef.el; + const headers = [...table.querySelectorAll("thead th:not(.o_list_actions_header)")]; + + headers.forEach((th, index) => { + th.style.minWidth = `${Math.floor(this.columnWidths[index])}px`; + }); + }, + + /** + * 宽度 + * @param allowedWidth + * @returns {*} + */ + computeColumnWidthsFromContent(allowedWidth) { + const columnWidths = super.computeColumnWidthsFromContent(allowedWidth); + if (!this.props.ooManagerColumns) return columnWidths; + const table = this.tableRef.el; + + // Toggle a className used to remove style that could interfere with the ideal width + // computation algorithm (e.g. prevent text fields from being wrapped during the + // computation, to prevent them from being completely crushed) + table.classList.add("o_list_computing_widths"); + + const headers = [...table.querySelectorAll("thead th")]; + + headers.forEach((th, index) => { + const id = th.getAttribute('data-id'); + const mColumn = this.props.ooManagerColumns.find(item => item.id === id) + if (mColumn && mColumn.width) { + columnWidths[index] = mColumn.width; + } + }); + + // We are no longer computing widths, so restore the normal style + table.classList.remove("o_list_computing_widths"); + return columnWidths; + }, + + /** + * 样式 + * @param column + * @returns {string} + */ + getColumnStyle(column) { + if (!this.props.ooManagerColumns) return ''; + const mColumn = this.props.ooManagerColumns.find(item => item.id === column.id); + if (mColumn && mColumn.color) { + return 'color: ' + mColumn.color; + } else { + return ''; + } + }, + + _renderSearchPanel: async function () { + if (this.isX2Many) { + return ListRendererCust._renderSearchPanel.call(this); + } + if (!this.props.archInfo.advanced_filter) { + $('.o_filter_box').remove(); + } else { + await loadJS("/tree_view_advanced_filter/static/src/libs/daterangepicker.min.js"); + await loadCSS("/tree_view_advanced_filter/static/src/libs/daterangepicker.css"); + await loadJS("/tree_view_advanced_filter/static/src/libs/moment/moment.min.js"); + var List = this.props.list; + const bm_params = List.model.__bm_load_params__; + const rootRef = this.rootRef.el + const tableRef = this.tableRef.el; + tableRef.removeAttribute('style') + const columns = this.getActiveColumns(this.props.list); + + var $tr1 = $('', {class: 'o_filter_box'}).append(columns.map(columns => this._renderHeaderCellSearch(columns))); + // 添加行号占位td + $tr1.prepend($('').addClass('web_list_num')); + if (this.hasSelectors) { + $tr1.prepend($('').addClass('o_list_record_selector')); + } + + if (this.isStudioEditable || this.displayOptionalFields) { + $tr1.append($('').addClass('o_list_record_selector')); + } + + if ($(tableRef).find('.search_td').length) { + $(tableRef).find('.search_td').parent().replaceWith($tr1) + } else { + $tr1.insertAfter($(tableRef).find('tr')[0]); + } + + rootRef.querySelectorAll('thead tr td.search_td input').forEach((a) => a.addEventListener('keyup', this._onfilter.bind(this))); + rootRef.querySelectorAll('.search_button').forEach((a) => a.addEventListener('click', this._onSearch.bind(this))); + rootRef.querySelectorAll('.remove_button').forEach((a) => a.addEventListener('click', this._onRemove.bind(this))); + rootRef.querySelectorAll('thead tr td').forEach((a) => a.addEventListener('keypress', this._onKeyPress.bind(this))); + } + } +}) + +ListRenderer.props.push('ooManagerColumns?') +ListRenderer.props.push('ooManagerFilter?') +ListRenderer.props.push('ooManagerSerialNum?') diff --git a/oo_list_manager/static/src/views/list/list_renderer.xml b/oo_list_manager/static/src/views/list/list_renderer.xml new file mode 100644 index 000000000..4f4c4eac3 --- /dev/null +++ b/oo_list_manager/static/src/views/list/list_renderer.xml @@ -0,0 +1,57 @@ + + + + + + column.id + getColumnStyle(column) + + + + + # + + + + + + + + + + + + getColumnStyle(column) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file