This commit is contained in:
李鹏宇
2025-06-24 10:53:12 +08:00
parent 4709d10894
commit f7d3620de0
14 changed files with 610 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
+38
View File
@@ -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/*',
]
},
}
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers
@@ -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/<model("oo_list_manager.oo_list_manager"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('oo_list_manager.object', {
# 'object': obj
# })
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import ir_ui_view
+20
View File
@@ -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
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 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
@@ -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;
})
}
}
@@ -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);
@@ -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
}
@@ -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;
}
}
}
}
}
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="web.ListView" t-inherit-mode="extension">
<xpath expr="//t[@t-set-slot='layout-buttons']" position="inside">
<div class="o-dropdown dropdown o-dropdown--no-caret" t-ref="ooManagerRef">
<button type="button" class="btn btn-primary" t-on-click="()=>this.ooManager.state = !this.ooManager.state">
<i class="fa fa-cogs" />
</button>
<div class="oo_list_manager o-dropdown--menu dropdown-menu d-block" t-if="ooManager.state" t-ref="ooListRef">
<div class="d-flex align-items-center pb-2">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center">
表头查询:
</div>
<div class="o-checkbox form-check form-switch">
<input type="checkbox" class="form-check-input" t-model="ooManager.filter" t-on-click="onChangeFilter"/>
</div>
</div>
<div class="d-flex align-items-center">
<div class="d-flex align-items-center">
行号:
</div>
<div class="o-checkbox form-check form-switch">
<input type="checkbox" class="form-check-input" t-model="ooManager.serial_num" t-on-click="onChangeSerialNum"/>
</div>
</div>
</div>
<div class="d-flex align-items-center pb-2">
<input type="text" class="form-control" t-model="ooManager.key"/>
<i class="btn btn-primary fa fa-refresh ms-2" t-on-click="()=>this.ooManager.key = ''"/>
</div>
<div class="d-flex flex-column oo_columns" t-ref="ooColumnsRef">
<div class="pb-2 d-flex align-items-center oo_column" t-foreach="ooManager.columns" t-as="column" t-key="column.id" t-att-data-id="column.id" t-if="column.string.includes(ooManager.key)">
<div class="o-checkbox form-check form-switch">
<input type="checkbox" class="form-check-input" t-att-checked="column.visibility" t-on-click="onChangeVisibility" t-att-data-id="column.id"/>
</div>
<input type="text" class="form-control" t-att-value="column.string" disabled="true"/>
<input type="number" class="form-control o_width_input" t-model="column.width" t-on-blur="onChangeColumns"/>
<input type="color" class="o_color_input" t-model="column.color" t-on-blur="onChangeColumns"/>
<div class="oo_sort_cell" t-if="!this.ooManager.key">
<i class="btn fa fa-arrows"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
<xpath expr="//t[@t-component='props.Renderer']" position="replace">
<t t-component="props.Renderer"
list="model.root"
activeActions="activeActions"
archInfo="archInfo"
allowSelectors="props.allowSelectors"
editable="editable"
onOpenFormView="onOpenFormView"
openRecord.bind="openRecord"
noContentHelp="props.info.noContentHelp"
onAdd.bind="createRecord"
onOptionalFieldsChanged.bind="onOptionalFieldsChanged"
evalViewModifier.bind="evalViewModifier"
ooManagerColumns="ooManager.columns"
ooManagerFilter="ooManager.filter"
ooManagerSerialNum="ooManager.serial_num"
/>
</xpath>
</t>
</templates>
@@ -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 = $('<tr>', {class: 'o_filter_box'}).append(columns.map(columns => this._renderHeaderCellSearch(columns)));
// 添加行号占位td
$tr1.prepend($('<td>').addClass('web_list_num'));
if (this.hasSelectors) {
$tr1.prepend($('<td>').addClass('o_list_record_selector'));
}
if (this.isStudioEditable || this.displayOptionalFields) {
$tr1.append($('<td>').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?')
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="web.ListRenderer" t-inherit-mode="extension">
<xpath expr="//th[@t-att-data-name='column.name']" position="attributes">
<attribute name="t-att-data-id">column.id</attribute>
<attribute name="t-att-style">getColumnStyle(column)</attribute>
</xpath>
<xpath expr="//thead//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<th class="list-number">#</th>
</t>
</xpath>
<xpath expr="//tfoot//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<td/>
</t>
</xpath>
</t>
<t t-inherit="web.ListRenderer.RecordRow" t-inherit-mode="extension">
<xpath expr="//td[@t-att-name='column.name']" position="attributes">
<attribute name="t-att-style">getColumnStyle(column)</attribute>
</xpath>
<xpath expr="//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<t t-set="num" t-value="(record_index + 1) + list.offset"/>
<td t-esc="num" t-att-data-tooltip="num" class="cursor-pointer"/>
</t>
</xpath>
</t>
<t t-inherit="web.ListRenderer.Rows" t-inherit-mode="extension">
<xpath expr="//tr[@t-foreach='getEmptyRowIds']//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<td/>
</t>
</xpath>
<xpath expr="//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<td/>
</t>
</xpath>
</t>
<t t-inherit="web.ListRenderer.GroupRow" t-inherit-mode="extension">
<xpath expr="//t[@t-if='numSwitch']" position="replace">
<t t-if="props.ooManagerSerialNum">
<t t-set="num" t-value="(group_index + 1) + list.offset"/>
<td t-esc="num" t-att-data-tooltip="num" class="cursor-pointer"/>
</t>
</xpath>
</t>
</templates>