diff --git a/collapsible_notebook/__init__.py b/collapsible_notebook/__init__.py new file mode 100644 index 000000000..633f86615 --- /dev/null +++ b/collapsible_notebook/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/collapsible_notebook/__manifest__.py b/collapsible_notebook/__manifest__.py new file mode 100644 index 000000000..4d3cf817e --- /dev/null +++ b/collapsible_notebook/__manifest__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Collapsible Notebook', + 'version': '17.0.1.0.0', + 'category': 'Productivity', + 'summary': '支持折叠和展开功能的Notebook组件', + 'description': """ + Collapsible Notebook + =================== + + 这个模块提供了一个支持折叠和展开功能的Notebook组件。 + + 主要功能: + * 可折叠的notebook页面 + * 展开/折叠状态记忆 + * 美观的UI设计 + * 响应式布局 + + 使用方法: + 在XML视图中使用 来启用折叠功能。 + """, + 'author': 'TX Team', + 'website': 'https://www.tx.com', + 'depends': ['base', 'web', 'product'], + 'assets': { + 'web.assets_backend': [ + 'collapsible_notebook/static/src/css/collapsible_notebook.css', + 'collapsible_notebook/static/src/js/collapsible_notebook.js', + ], + }, + 'installable': True, + 'auto_install': False, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/collapsible_notebook/static/src/css/collapsible_notebook.css b/collapsible_notebook/static/src/css/collapsible_notebook.css new file mode 100644 index 000000000..162ea0b09 --- /dev/null +++ b/collapsible_notebook/static/src/css/collapsible_notebook.css @@ -0,0 +1,241 @@ +/* 可折叠Notebook样式 */ + +/* 整个Notebook折叠按钮样式 */ +.o_notebook_collapse_btn { + display: inline-block; + /* margin-right: 10px; */ + /* margin-bottom: 5px; */ + /* padding: 6px; */ + background-color: transparent; + color: #000; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + font-weight: 500; + transition: all 0.2s ease; + user-select: none; + vertical-align: top; + line-height: 1.4; + width: 30px; + height: 30px; + text-align: center; +} + +/* 确保按钮和标签页在同一行 */ +.o_notebook_collapse_btn + .nav-tabs { + display: inline-block; + vertical-align: top; + width: calc(100% - 40px); /* 为按钮留出空间 */ +} + +/* 为包含按钮的容器设置样式 */ +.o_notebook .o_notebook_collapse_btn { + float: left; + margin-top: 5px; + width: 30px; /* 固定按钮宽度 */ +} + +.o_notebook .nav-tabs { + overflow: visible; + white-space: nowrap; + flex-wrap: nowrap; +} + +/* 确保导航标签不换行 */ +.o_notebook .nav-tabs .nav-item { + display: inline-block; + white-space: nowrap; +} + +.o_notebook .nav-tabs .nav-link { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: none; +} + +.o_notebook_collapse_btn:hover { + background-color: #f5f5f5; + color: #000; +} + +.o_notebook_collapse_btn:active { + background-color: #e9e9e9; +} + +.o_notebook_collapse_btn.o_collapsed { + background-color: transparent; + color: #000; +} + +.o_notebook_collapse_btn.o_collapsed:hover { + background-color: #f5f5f5; + color: #000; +} + +/* 折叠状态的整个Notebook */ +.o_collapsible_notebook.o_notebook_collapsed .o_notebook .tab-content { + display: none; + transition: all 0.3s ease; +} + +.o_collapsible_notebook.o_notebook_collapsed .o_notebook .nav-tabs { + opacity: 0.7; + transition: opacity 0.3s ease; +} + +/* 展开状态的整个Notebook */ +.o_collapsible_notebook:not(.o_notebook_collapsed) .o_notebook .tab-content { + display: block; + transition: all 0.3s ease; +} + +.o_collapsible_notebook:not(.o_notebook_collapsed) .o_notebook .nav-tabs { + opacity: 1; + transition: opacity 0.3s ease; +} + +/* 页面内容区域的过渡效果 */ +.o_notebook .tab-pane { + transition: opacity 0.3s ease, height 0.3s ease; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .o_notebook_collapse_btn { + font-size: 14px; + padding: 4px; + width: 25px; + height: 25px; + } + + .o_notebook_collapse_btn + .nav-tabs { + width: calc(100% - 35px); + } + + .o_notebook .nav-tabs .nav-link { + font-size: 12px; + padding: 8px 6px; + } +} + +/* 深色主题支持 */ +@media (prefers-color-scheme: dark) { + .o_notebook_collapse_btn { + background-color: transparent; + color: #fff; + } + + .o_notebook_collapse_btn:hover { + background-color: #333; + color: #fff; + } + + .o_notebook_collapse_btn.o_collapsed { + background-color: transparent; + color: #fff; + } + + .o_notebook_collapse_btn.o_collapsed:hover { + background-color: #333; + color: #fff; + } +} + +/* 动画效果 */ +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideUp { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-10px); + } +} + +/* 展开动画 */ +.o_notebook .tab-pane.expanding { + animation: slideDown 0.3s ease; +} + +/* 折叠动画 */ +.o_notebook .tab-pane.collapsing { + animation: slideUp 0.3s ease; +} + +/* 工具提示样式 */ +.o_collapse_btn[title]:hover::after { + content: attr(title); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background-color: #333; + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 1000; + margin-bottom: 5px; +} + +.o_collapse_btn[title]:hover::before { + content: ''; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: #333; + z-index: 1000; +} + +/* 高对比度模式支持 */ +@media (prefers-contrast: high) { + .o_notebook_collapse_btn { + border: 2px solid currentColor; + } +} + +/* 减少动画模式支持 */ +@media (prefers-reduced-motion: reduce) { + .o_notebook .tab-pane, + .o_notebook_collapse_btn, + .o_collapsible_notebook { + transition: none; + } + + .o_notebook .tab-pane.expanding, + .o_notebook .tab-pane.collapsing { + animation: none; + } +} + +/* 打印样式 */ +@media print { + .o_notebook_collapse_btn { + display: none; + } + + .o_collapsible_notebook.o_notebook_collapsed .o_notebook .tab-content { + display: block !important; + } + + .o_collapsible_notebook.o_notebook_collapsed .o_notebook .nav-tabs { + opacity: 1 !important; + } +} diff --git a/collapsible_notebook/static/src/js/collapsible_notebook.js b/collapsible_notebook/static/src/js/collapsible_notebook.js new file mode 100644 index 000000000..7e075c692 --- /dev/null +++ b/collapsible_notebook/static/src/js/collapsible_notebook.js @@ -0,0 +1,215 @@ +// 可折叠Notebook功能 +console.log('Collapsible Notebook JS loaded'); + +/** + * 页面加载完成后自动初始化所有可折叠notebook + */ +function initCollapsibleNotebooks() { + console.log('Initializing collapsible notebooks...'); + + // 初始化现有的notebook + const existingNotebooks = document.querySelectorAll('.o_collapsible_notebook'); + console.log('Found notebooks:', existingNotebooks.length); + + if (existingNotebooks.length === 0) { + console.log('No notebooks found, retrying in 500ms...'); + setTimeout(initCollapsibleNotebooks, 500); + return; + } + + existingNotebooks.forEach((notebook, index) => { + console.log(`Processing notebook ${index}:`, notebook); + if (!notebook.hasAttribute('data-collapsible-initialized')) { + initializeNotebook(notebook); + notebook.setAttribute('data-collapsible-initialized', 'true'); + } + }); +} + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + console.log('DOM Content Loaded'); + initCollapsibleNotebooks(); +}); + +// 如果DOM已经加载完成,立即初始化 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initCollapsibleNotebooks); +} else { + initCollapsibleNotebooks(); +} + +// 使用MutationObserver来监听DOM变化,确保在动态加载的内容中也能工作 +function setupMutationObserver() { + if (!document.body) { + console.log('Document body not ready, retrying...'); + setTimeout(setupMutationObserver, 100); + return; + } + + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === Node.ELEMENT_NODE) { + const notebooks = node.querySelectorAll ? + node.querySelectorAll('.o_collapsible_notebook') : + (node.classList && node.classList.contains('o_collapsible_notebook') ? [node] : []); + + notebooks.forEach(notebook => { + if (!notebook.hasAttribute('data-collapsible-initialized')) { + console.log('New notebook detected:', notebook); + initializeNotebook(notebook); + notebook.setAttribute('data-collapsible-initialized', 'true'); + } + }); + } + }); + } + }); + }); + + // 开始观察 + observer.observe(document.body, { + childList: true, + subtree: true + }); + + console.log('MutationObserver setup complete'); +} + +// 设置MutationObserver +setupMutationObserver(); + +/** + * 初始化单个notebook + */ +function initializeNotebook(notebook) { + console.log('Initializing notebook:', notebook); + // 为整个notebook添加折叠/展开按钮 + addNotebookCollapseButton(notebook); +} + +/** + * 为整个notebook添加折叠/展开按钮 + */ +function addNotebookCollapseButton(notebook) { + console.log('Adding collapse button to notebook:', notebook); + console.log('Notebook HTML structure:', notebook.outerHTML); + + // 检查是否已经添加了按钮 + if (notebook.querySelector('.o_notebook_collapse_btn')) { + console.log('Collapse button already exists'); + return; + } + + // 创建notebook折叠按钮 + const notebookCollapseBtn = document.createElement('div'); + notebookCollapseBtn.className = 'o_notebook_collapse_btn'; + notebookCollapseBtn.innerHTML = ''; + notebookCollapseBtn.title = '点击折叠/展开整个Notebook'; + + console.log('Created collapse button:', notebookCollapseBtn); + + // 添加点击事件 + notebookCollapseBtn.addEventListener('click', (e) => { + console.log('Collapse button clicked'); + e.preventDefault(); + e.stopPropagation(); + toggleNotebook(notebook, notebookCollapseBtn); + }); + + // 将按钮添加到notebook的nav-tabs前面(与页面标签并排) + try { + const navTabs = notebook.querySelector('.nav-tabs'); + if (navTabs && navTabs.parentNode) { + navTabs.parentNode.insertBefore(notebookCollapseBtn, navTabs); + console.log('Button inserted successfully before nav-tabs'); + } else if (notebook.parentNode) { + notebook.parentNode.insertBefore(notebookCollapseBtn, notebook); + console.log('Button inserted successfully before notebook (fallback)'); + } else { + notebook.insertBefore(notebookCollapseBtn, notebook.firstChild); + console.log('Button inserted successfully inside notebook (fallback)'); + } + } catch (error) { + console.error('Error inserting button:', error); + } + + // 恢复折叠状态 + restoreNotebookState(notebook, notebookCollapseBtn); +} + +/** + * 从localStorage恢复notebook折叠状态 + */ +function restoreNotebookState(notebook, button) { + try { + const notebookId = notebook.getAttribute('id') || 'default_notebook'; + const savedState = localStorage.getItem(`odoo_notebook_collapsed_${notebookId}`); + + if (savedState === 'true') { + console.log('Restoring collapsed state for notebook:', notebookId); + notebook.classList.add('o_notebook_collapsed'); + button.innerHTML = ''; + button.classList.add('o_collapsed'); + + // 直接设置内容隐藏 + const tabContent = notebook.querySelector('.tab-content'); + if (tabContent) { + tabContent.style.display = 'none'; + } + } else { + console.log('Notebook starts in expanded state:', notebookId); + } + } catch (error) { + console.error('Error restoring notebook state:', error); + } +} + +/** + * 切换整个notebook的折叠/展开状态 + */ +function toggleNotebook(notebook, button) { + console.log('Toggling notebook:', notebook); + console.log('Notebook classes before:', notebook.className); + + const isCollapsed = notebook.classList.contains('o_notebook_collapsed'); + console.log('Current state - isCollapsed:', isCollapsed); + + // 查找tab-content元素 + const tabContent = notebook.querySelector('.tab-content'); + console.log('Found tab-content:', tabContent); + + if (isCollapsed) { + // 展开notebook + console.log('Expanding notebook'); + notebook.classList.remove('o_notebook_collapsed'); + button.innerHTML = ''; + button.classList.remove('o_collapsed'); + + if (tabContent) { + tabContent.style.display = 'block'; + console.log('Set tab-content display to block'); + } + } else { + // 折叠notebook + console.log('Collapsing notebook'); + notebook.classList.add('o_notebook_collapsed'); + button.innerHTML = ''; + button.classList.add('o_collapsed'); + + if (tabContent) { + tabContent.style.display = 'none'; + console.log('Set tab-content display to none'); + } + } + + console.log('Notebook classes after:', notebook.className); + + // 保存状态 + const notebookId = notebook.getAttribute('id') || 'default_notebook'; + localStorage.setItem(`odoo_notebook_collapsed_${notebookId}`, isCollapsed ? 'false' : 'true'); + console.log('State saved to localStorage'); +} + diff --git a/yuthon_property_approval/views/property_lease_contract.xml b/yuthon_property_approval/views/property_lease_contract.xml index 6b317a7e3..28399847c 100644 --- a/yuthon_property_approval/views/property_lease_contract.xml +++ b/yuthon_property_approval/views/property_lease_contract.xml @@ -672,7 +672,7 @@ - + @@ -764,7 +764,7 @@ readonly="1"/> - +