This commit is contained in:
李鹏宇
2025-07-22 13:34:21 +00:00
committed by 李鹏宇
parent babea81be8
commit dbe05b7e50
12 changed files with 927 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
from . import models
from . import controllers
+29
View File
@@ -0,0 +1,29 @@
{
'name': '快捷菜单访问',
'version': '17.0.1.0.0',
'category': 'Tools',
'summary': '快速访问常用菜单',
'description': """
为用户提供快速访问常用菜单的悬浮按钮功能。
支持自定义配置每个用户的快捷菜单。
""",
'author': 'js',
'website': 'https://www.jsmeiyouwangzhan.com',
'depends': ['base', 'web'],
'data': [
'security/ir.model.access.csv',
'security/ir_rule.xml',
'views/quick_menu_config_views.xml',
],
'assets': {
'web.assets_backend': [
'quick_menu_access/static/src/components/floating_menu/floating_menu.js',
'quick_menu_access/static/src/components/floating_menu/floating_menu.xml',
'quick_menu_access/static/src/components/floating_menu/floating_menu.scss',
],
},
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}
@@ -0,0 +1 @@
from . import main
+41
View File
@@ -0,0 +1,41 @@
from odoo import http
from odoo.http import request
class QuickMenuController(http.Controller):
@http.route('/web/quick_menu/get_menus', type='json', auth='user')
def get_quick_menus(self):
"""获取当前用户的快捷菜单配置"""
QuickMenuConfig = request.env['quick.menu.config']
menus = QuickMenuConfig.search([
('user_id', '=', request.env.user.id),
('active', '=', True)
], order='sequence')
result = []
for menu in menus:
# 检查菜单是否存在且用户有权限访问
if menu.menu_id and menu.menu_id.action and request.env.user.has_group('base.group_user'):
action = menu.menu_id.action.sudo()
action_type = action._name.split('.')[0]
# 获取action的完整信息
action_data = action.read()[0]
if action_type == 'ir':
action_data.update({
'type': 'ir.actions.act_window',
'target': 'current',
})
result.append({
'id': menu.id,
'name': menu.name or menu.menu_id.name,
'icon': menu.icon,
'color': menu.color or '#2ECC71', # 如果颜色为空,使用默认颜色
'action': action_data,
'sequence': menu.sequence,
'menu_id': menu.menu_id.id,
'show_title': menu.show_title,
})
return result
+1
View File
@@ -0,0 +1 @@
from . import quick_menu_config
@@ -0,0 +1,57 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class QuickMenuConfig(models.Model):
_name = 'quick.menu.config'
_description = '快捷菜单配置'
_order = 'sequence, id'
_sql_constraints = [
('user_menu_uniq', 'unique(user_id, menu_id)', '同一用户不能重复配置相同的菜单!')
]
def _get_next_sequence(self):
"""获取当前用户的下一个序号(从1开始递增)"""
user_id = self.env.user.id
max_sequence = self.env['quick.menu.config'].search([
('user_id', '=', user_id),
('active', '=', True)
], order='sequence desc', limit=1).sequence or 0
return max_sequence + 1
name = fields.Char(string='名称', required=True)
user_id = fields.Many2one('res.users', string='用户', required=True, default=lambda self: self.env.user,
ondelete='cascade')
menu_id = fields.Many2one('ir.ui.menu', string='菜单', required=True, ondelete='cascade',
domain=[('action', '!=', False)]) # 只允许选择有action的菜单
sequence = fields.Integer(string='序号', default=lambda self: self._get_next_sequence())
icon = fields.Selection([
('fa-star', '星星'),
('fa-heart', '心形'),
('fa-user', '用户'),
('fa-cog', '设置'),
('fa-home', '主页'),
('fa-list', '列表'),
('fa-calendar', '日历'),
('fa-envelope', '信封'),
('fa-bell', '铃铛'),
('fa-file', '文件')
], string='图标', required=True, default='fa-star')
color = fields.Char(string='颜色', default='#2ECC71',
help='菜单项的颜色,可以使用HEX颜色代码,例如 #FF0000 表示红色')
active = fields.Boolean(string='启用', default=True)
show_title = fields.Boolean(string='默认显示标题', default=False,
help='如果勾选,该菜单项的标题将在菜单展开时默认显示,无需鼠标悬停')
@api.constrains('menu_id')
def _check_menu_count(self):
"""检查每个用户的快捷菜单数量不超过8个"""
for record in self:
menu_count = self.search_count([
('user_id', '=', record.user_id.id),
('active', '=', True),
('id', '!=', record.id)
])
if menu_count >= 8:
raise ValidationError(_('每个用户最多只能配置8个快捷菜单!'))
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_quick_menu_config,access.quick.menu.config,model_quick_menu_config,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_quick_menu_config access.quick.menu.config model_quick_menu_config base.group_user 1 1 1 1
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- 快捷菜单权限规则:用户只能查看和管理自己的快捷菜单 -->
<record id="rule_quick_menu_config_user" model="ir.rule">
<field name="name">用户只能查看和管理自己的快捷菜单</field>
<field name="model_id" ref="model_quick_menu_config"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
</record>
</data>
</odoo>
@@ -0,0 +1,316 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Component, useState, onMounted, onWillUnmount, useRef } from "@odoo/owl";
import { browser } from "@web/core/browser/browser";
export class FloatingMenu extends Component {
setup() {
this.root = useRef("root_container");
this.state = useState({
isOpen: false,
isVisible: true, // 用于在网站预览中隐藏
isComponentVisible: true, // 用于用户关闭组件
isDragging: false,
// 初始位置,这些将通过拖动更新
containerPosition: { right: 50, bottom: 50 },
dragStartMouse: { x: 0, y: 0 },
dragStartContainer: { right: 0, bottom: 0 },
menuItems: [] // 菜单项将从服务器获取
});
// 存储超时ID以便清理
this.hoverTimeout = null;
this.leaveTimeout = null;
this.action = useService("action");
this.rpc = useService("rpc");
this.onMouseMove = this._onMouseMove.bind(this);
this.onMouseUp = this._onMouseUp.bind(this);
this.onDocumentClick = this._onDocumentClick.bind(this);
this.onMouseEnter = this._onMouseEnter.bind(this);
this.onMouseLeave = this._onMouseLeave.bind(this);
onMounted(async () => {
await this.loadMenuItems();
await browser.setTimeout(() => this.setupObserver(), 2000);
// 绑定鼠标移动和抬起事件到文档以实现更流畅的拖动
document.addEventListener('mousemove', this.onMouseMove);
document.addEventListener('mouseup', this.onMouseUp);
document.addEventListener('click', this.onDocumentClick);
});
onWillUnmount(() => {
this.cleanupObserver();
// 清除所有事件监听器和超时
document.removeEventListener('mousemove', this.onMouseMove);
document.removeEventListener('mouseup', this.onMouseUp);
document.removeEventListener('click', this.onDocumentClick);
// 清除所有可能的超时
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
if (this.leaveTimeout) {
clearTimeout(this.leaveTimeout);
this.leaveTimeout = null;
}
});
}
async loadMenuItems() {
try {
const menus = await this.rpc('/web/quick_menu/get_menus');
// 计算每个菜单项的位置
const positions = this.calculateMenuPositions(menus.length);
this.state.menuItems = menus.map((menu, index) => ({
...menu,
x: positions[index].x,
y: positions[index].y,
direction: positions[index].direction,
icon: `fa ${menu.icon}`,
initialStyle: 'transform: translate(0px, 0px);',
currentStyle: 'transform: translate(0px, 0px);'
}));
} catch (error) {
console.error('加载菜单项失败:', error);
}
}
calculateMenuPositions(count) {
const positions = [];
const radius = 90; // 增加半径,使菜单项之间的距离变大
const angleStep = (2 * Math.PI) / count; // 平均分布的角度步长
const threshold = 0.3; // 判断是否在轴上的阈值
for (let i = 0; i < count; i++) {
const angle = i * angleStep - Math.PI / 2; // 从正上方开始分布
const x = Math.round(radius * Math.cos(angle));
const y = Math.round(radius * Math.sin(angle));
// 根据坐标轴位置确定方向
// 0-向右,1-向左,2-向上,3-向下
let direction;
// 判断是否在y轴上
if (Math.abs(Math.cos(angle)) < threshold) {
// 接近y轴
if (y > 0) {
direction = 3; // y轴正半轴,向上展示
} else {
direction = 2; // y轴负半轴,向下展示
}
}
// 判断是否在x轴上
else if (Math.abs(Math.sin(angle)) < threshold) {
// 接近x轴
if (x > 0) {
direction = 0; // x轴正半轴,向右展示
} else {
direction = 1; // x轴负半轴,向左展示
}
}
// 不在轴上,按象限判断
else {
if (x > 0) {
direction = 0; // 一四象限,向右展示
} else {
direction = 1; // 二三象限,向左展示
}
}
positions.push({
x: x,
y: y,
direction: direction
});
}
return positions;
}
setupObserver() {
this.checkWebsitePreview();
this.observer = new MutationObserver(() => {
browser.setTimeout(() => this.checkWebsitePreview(), 100);
});
this.observer.observe(document.body, { attributes: true, childList: true, subtree: true });
}
cleanupObserver() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
}
checkWebsitePreview() {
const websitePreviewElement = document.querySelector('.o_website_preview');
const isWebsitePreview = !!websitePreviewElement;
if (this.state.isVisible !== !isWebsitePreview) {
this.state.isVisible = !isWebsitePreview;
if (!this.state.isVisible && this.state.isOpen) {
this.state.isOpen = false; // 当变为不可见时也关闭菜单
// 当菜单隐藏时重置位置
this.state.menuItems = this.state.menuItems.map(item => ({
...item,
currentStyle: item.initialStyle
}));
}
}
}
onCenterMouseDown(event) {
if (event.target.classList.contains('quick-menu-close-button')) {
// 如果点击的是关闭按钮,不要开始拖动或切换
return;
}
if (!this.state.isDragging) {
this.state.isDragging = true;
this.state.dragStartMouse = { x: event.clientX, y: event.clientY };
// 获取当前容器位置(假设通过style.right和style.bottom设置)
const rect = this.root.el.getBoundingClientRect();
this.state.dragStartContainer = {
right: window.innerWidth - rect.right,
bottom: window.innerHeight - rect.bottom
};
event.preventDefault(); // 防止文本选择
}
}
_onMouseMove(event) {
if (!this.state.isDragging) return;
const deltaX = event.clientX - this.state.dragStartMouse.x;
const deltaY = event.clientY - this.state.dragStartMouse.y;
// 新位置是初始位置加上偏移量。由于我们使用right/bottom,正的deltaX意味着更小的'right'值
this.state.containerPosition = {
right: Math.max(0, this.state.dragStartContainer.right - deltaX),
bottom: Math.max(0, this.state.dragStartContainer.bottom - deltaY)
};
}
_onMouseUp(event) {
if (this.state.isDragging) {
this.state.isDragging = false;
if (!this.state.isOpen && event.target.closest('.menu-center')) {
// 如果是在中心按钮上结束拖动,且菜单处于关闭状态,切换菜单
// 这样可以实现点击中心按钮切换菜单,而不是拖动
this.openMenu();
}
}
}
// 鼠标进入菜单容器
_onMouseEnter() {
// 清除可能的离开超时
if (this.leaveTimeout) {
clearTimeout(this.leaveTimeout);
this.leaveTimeout = null;
}
if (!this.state.isDragging) {
// 添加延迟,避免偶然经过时菜单就打开
this.hoverTimeout = setTimeout(() => {
if (!this.state.isOpen) {
this.openMenu();
}
}, 200); // 200毫秒的悬停延迟
}
}
// 鼠标离开菜单容器
_onMouseLeave() {
// 清除可能的悬停超时
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
// 添加延迟关闭,避免用户意外移出又移入时菜单闪烁
if (!this.state.isDragging && this.state.isOpen) {
this.leaveTimeout = setTimeout(() => {
this.closeMenu();
}, 300); // 300毫秒的离开延迟
}
}
// 打开菜单
openMenu() {
this.state.isOpen = true;
this.applyCustomPositions();
}
// 关闭菜单
closeMenu() {
this.state.isOpen = false;
this.state.menuItems = this.state.menuItems.map(item => ({
...item,
currentStyle: item.initialStyle
}));
}
// 在菜单打开后添加一个短暂的延迟,防止菜单项在动画期间响应hover
openMenu() {
this.state.isOpen = true;
this.applyCustomPositions();
// 添加延迟处理hover事件
setTimeout(() => {
document.querySelectorAll('.menu-item').forEach(item => {
item.classList.add('can-hover');
});
}, 1000); // 500ms延迟,确保所有菜单项动画完成
}
// 重命名以避免与非拖动时的中心按钮点击冲突
toggleMenuDisplay(event) {
// 只有在不拖动且不点击关闭按钮时才切换
if (event && event.target.classList.contains('quick-menu-close-button')) return;
if (this.state.isOpen) {
this.closeMenu();
} else {
this.openMenu();
}
}
applyCustomPositions() {
this.state.menuItems = this.state.menuItems.map(item => {
return { ...item, currentStyle: `transform: translate(${item.x}px, ${item.y}px);` };
});
}
onMenuClick(menuItem) {
// 执行菜单动作
if (menuItem.action) {
this.action.doAction(menuItem.action);
}
// 关闭菜单
this.closeMenu();
}
closeComponent(event) {
event.stopPropagation(); // 防止点击事件冒泡到onCenterMouseDown或toggleMenuDisplay
this.state.isComponentVisible = false;
}
_onDocumentClick(event) {
if (this.state.isOpen && this.root.el) {
// 检查点击是否在菜单容器外部
if (!this.root.el.contains(event.target)) {
this.closeMenu();
}
}
}
}
FloatingMenu.template = "quick_menu_access.FloatingMenu";
registry.category("main_components").add("FloatingMenu", {
Component: FloatingMenu,
});
@@ -0,0 +1,330 @@
// 带有单独定位圆形项目的浮动菜单的SCSS样式
$menu-container-size: 250px; // 增加容器大小,适应更大的菜单分布
$center-button-size: 50px;
$menu-item-size: 45px; // 每个圆形菜单项的直径
$ring-size: 180px; // 增加圆环大小
// 新的配色方案
$main-color: #3498DB; // 主色调:靛蓝色
$secondary-color: #2ECC71; // 次要色调:绿色(默认颜色)
$icon-color: white;
$item-bg-color: white;
$item-border-color: $secondary-color;
$text-color-on-item: $secondary-color;
$text-color-on-hover-label: white;
$close-color: #95A5A6; // 关闭按钮颜色
$close-button-size: 20px;
$ring-color: rgba(46, 204, 113, 0.1); // 圆环颜色,使用次要色调的透明版本
$ring-border-color: rgba(46, 204, 113, 0.2); // 圆环边框颜色
.o_floating_menu_container {
position: fixed;
// 初始位置由JS状态设置
// right: 50px;
// bottom: 50px;
z-index: 1000;
cursor: default; // 容器的默认光标样式
// 在网站预览页面中隐藏
.o_website_preview & {
display: none !important;
}
.o_floating_menu {
position: relative;
// 宽度和高度由项目展开决定,或使用menu-container-size保持视觉一致性
width: $menu-container-size;
height: $menu-container-size;
// 添加圆环
&::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: $ring-size;
height: $ring-size;
transform: translate(-50%, -50%) scale(0.8);
border-radius: 50%;
background: $ring-color;
border: 2px solid $ring-border-color;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: 0;
visibility: hidden;
}
&.menu-open::before {
opacity: 1;
visibility: visible;
transform: translate(-50%, -50%) scale(1);
}
&.dragging .menu-center {
cursor: grabbing; // 拖动时改变光标
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
}
.menu-center {
position: absolute;
left: 50%; // 在.o_floating_menu容器中居中
top: 50%;
transform: translate(-50%, -50%);
width: $center-button-size;
height: $center-button-size;
background: $main-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: grab; // 可拖动时的默认光标(关闭状态)
z-index: 10;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
user-select: none; // 防止在拖动尝试期间选中文本
&:hover {
background: lighten($main-color, 10%);
transform: translate(-50%, -50%) scale(1.05);
box-shadow: 0 6px 16px rgba(0,0,0,0.2);
}
i.fa-compass {
color: $icon-color;
font-size: 20px;
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.quick-menu-close-button {
position: absolute;
top: -5px;
right: -5px;
width: $close-button-size;
height: $close-button-size;
background-color: $close-color;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
cursor: pointer;
opacity: 0;
visibility: hidden;
transform: scale(0.5);
transition: all 0.2s ease;
z-index: 11; // 在菜单中心图标之上
&:hover {
background-color: darken($close-color, 10%);
transform: scale(1.1);
}
}
// 仅在菜单未打开时在悬停时显示关闭按钮
// 这个条件现在主要由XML中的t-if处理
// 我们依赖XML中的t-if="!state.isOpen"
&:hover .quick-menu-close-button {
opacity: 1;
visibility: visible;
transform: scale(1);
}
}
&.menu-open .menu-center {
cursor: pointer; // 菜单打开时重置为普通指针
background: $secondary-color;
box-shadow: 0 6px 18px rgba(0,0,0,0.2);
i.fa-compass {
transform: rotate(135deg);
}
}
.menu-item {
position: absolute;
// 从.o_floating_menu的中心开始,然后通过JS平移
left: 50%;
top: 50%;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transform: scale(0.6);
// 修复z-index,确保菜单项始终显示在上层
z-index: 15;
.menu-item-content { // 可见的圆形项目
width: $menu-item-size;
height: $menu-item-size;
background: $item-bg-color;
// 使用CSS变量替代固定颜色
border: 2px solid var(--menu-item-color, $secondary-color);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
position: relative; // 用于名称标签定位
// 居中圆圈本身,因为.menu-item只是一个定位点
transform: translate(-50%, -50%);
transition: all 0.3s ease;
&:hover {
// 使用CSS变量替代固定颜色
background: var(--menu-item-color, $secondary-color);
border-color: var(--menu-item-color, $secondary-color);
transform: translate(-50%, -50%) scale(1.1);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
i {
color: white;
}
.menu-item-name {
opacity: 1;
visibility: visible;
}
}
i {
// 使用CSS变量替代固定颜色
color: var(--menu-item-color, $secondary-color);
font-size: 16px;
transition: all 0.3s ease;
}
// 菜单项标题样式
.menu-item-name {
position: absolute;
// 使用CSS变量替代固定颜色
background: var(--menu-item-color, $secondary-color);
color: $text-color-on-hover-label;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 20;
min-width: 60px;
text-align: center;
&::before {
content: '';
position: absolute;
border-width: 4px;
border-style: solid;
border-color: transparent;
}
// 方向0 - 向右
&.direction-0 {
left: calc(100% + 10px);
top: 50%;
transform: translateY(-50%);
&::before {
top: 50%;
left: -8px;
transform: translateY(-50%);
// 使用CSS变量替代固定颜色
border-right-color: var(--menu-item-color, $secondary-color);
}
}
// 方向1 - 向左
&.direction-1 {
right: calc(100% + 10px);
top: 50%;
transform: translateY(-50%);
&::before {
top: 50%;
right: -8px;
transform: translateY(-50%);
// 使用CSS变量替代固定颜色
border-left-color: var(--menu-item-color, $secondary-color);
}
}
// 方向2 - 向上
&.direction-2 {
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
&::before {
bottom: -8px;
left: 50%;
transform: translateX(-50%);
// 使用CSS变量替代固定颜色
border-top-color: var(--menu-item-color, $secondary-color);
}
}
// 方向3 - 向下
&.direction-3 {
top: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
&::before {
top: -8px;
left: 50%;
transform: translateX(-50%);
// 使用CSS变量替代固定颜色
border-bottom-color: var(--menu-item-color, $secondary-color);
}
}
}
}
}
&.menu-open .menu-item {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: scale(1);
// 为每个菜单项添加级联延迟显示效果
@for $i from 1 through 10 {
&:nth-child(#{$i + 1}) {
transition-delay: #{$i * 0.05}s;
}
}
// 默认显示带有show-title类的标题
.menu-item-content .menu-item-name.show-title {
opacity: 1;
visibility: visible;
// 根据方向设置正确的transform
&.direction-0 {
transform: translateY(-50%);
}
&.direction-1 {
transform: translateY(-50%);
}
&.direction-2 {
transform: translateX(-50%);
}
&.direction-3 {
transform: translateX(-50%);
}
}
}
}
}
.menu-item:not(.can-hover):hover .menu-item-name {
opacity: 0 !important;
visibility: hidden !important;
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="quick_menu_access.FloatingMenu" owl="1">
<div t-if="state.isComponentVisible and state.isVisible"
class="o_floating_menu_container"
t-attf-style="right: {{ state.containerPosition.right }}px; bottom: {{ state.containerPosition.bottom }}px;"
t-ref="root_container"
t-on-mouseenter="onMouseEnter"
t-on-mouseleave="onMouseLeave">
<div class="o_floating_menu" t-att-class="{ 'menu-open': state.isOpen, 'dragging': state.isDragging }">
<!-- 中心菜单按钮 -->
<div class="menu-center"
t-on-mousedown="onCenterMouseDown"
t-on-click="toggleMenuDisplay">
<i class="fa fa-compass"/>
<!-- 关闭小按钮 -->
<div t-if="!state.isOpen" class="quick-menu-close-button" t-on-click.stop="closeComponent">
<i class="fa fa-times"/>
</div>
</div>
<!-- 菜单项列表 -->
<t t-foreach="state.menuItems" t-as="item" t-key="item.id">
<div class="menu-item" t-att-style="item.currentStyle">
<div class="menu-item-content"
t-att-data-item-id="item.id"
t-on-click="() => this.onMenuClick(item)"
t-att-data-direction="item.direction"
t-attf-style="--menu-item-color: {{ item.color }};">
<!-- 图标和名称作为直接子元素 -->
<i t-att-class="item.icon"/>
<span t-att-class="{'menu-item-name': true, ['direction-' + item.direction]: true, 'show-title': item.show_title}"
t-esc="item.name"/>
</div>
</div>
</t>
</div>
</div>
</t>
</templates>
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 树状视图 -->
<record id="view_quick_menu_config_tree" model="ir.ui.view">
<field name="name">quick.menu.config.tree</field>
<field name="model">quick.menu.config</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="menu_id"/>
<field name="icon"/>
<field name="color" widget="color"/>
<field name="show_title" widget="boolean_toggle"/>
<field name="active" widget="boolean_toggle"/>
</tree>
</field>
</record>
<!-- 表单视图 -->
<record id="view_quick_menu_config_form" model="ir.ui.view">
<field name="name">quick.menu.config.form</field>
<field name="model">quick.menu.config</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="输入配置名称..."/></h1>
</div>
<group>
<group>
<field name="menu_id"/>
<field name="sequence" readonly="1"/>
<field name="user_id" readonly="1"/>
</group>
<group>
<field name="icon"/>
<field name="color" widget="color"/>
<field name="show_title" widget="boolean_toggle"/>
<field name="active" widget="boolean_toggle"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- 搜索视图 -->
<record id="view_quick_menu_config_search" model="ir.ui.view">
<field name="name">quick.menu.config.search</field>
<field name="model">quick.menu.config</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="user_id"/>
<field name="menu_id"/>
<field name="color"/>
<filter string="已启用" name="active" domain="[('active', '=', True)]"/>
<filter string="已禁用" name="inactive" domain="[('active', '=', False)]"/>
<filter string="默认显示标题" name="show_title" domain="[('show_title', '=', True)]"/>
<group expand="0" string="分组">
<filter string="用户" name="group_by_user" context="{'group_by': 'user_id'}"/>
<filter string="菜单" name="group_by_menu" context="{'group_by': 'menu_id'}"/>
</group>
</search>
</field>
</record>
<!-- 动作 -->
<record id="action_quick_menu_config" model="ir.actions.act_window">
<field name="name">快捷菜单配置</field>
<field name="res_model">quick.menu.config</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
创建第一个快捷菜单配置
</p>
<p>
您可以为不同的用户配置快捷菜单,方便快速访问常用功能。
</p>
</field>
</record>
<!-- 菜单 -->
<menuitem id="menu_quick_menu_config"
name="快捷菜单配置"
action="action_quick_menu_config"
parent="base.menu_administration"
sequence="100"/>
</odoo>