薪资接口
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
# k_ry_mobile_form - 科瑞移动端表单优化模块
|
||||
|
||||
## 功能说明
|
||||
|
||||
此模块为 Odoo 17 的 form 视图添加移动端优化功能:
|
||||
- 在手机浏览器中,form 视图 header 的工具按钮栏会悬浮在屏幕底部
|
||||
- 提供类似正常 APP 的底部按钮栏体验
|
||||
- 桌面端不受影响,保持原有显示方式
|
||||
|
||||
## 依赖
|
||||
|
||||
- `base`
|
||||
- `web`
|
||||
|
||||
## 安装
|
||||
|
||||
1. 将模块放置在 Odoo 的 addons 目录下
|
||||
2. 更新应用列表
|
||||
3. 安装 `k_ry_mobile_form` 模块
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
k_ry_mobile_form/
|
||||
├── __init__.py
|
||||
├── __manifest__.py
|
||||
├── README.md
|
||||
└── static/
|
||||
└── src/
|
||||
├── js/
|
||||
│ └── status_bar_buttons.js # 覆盖 StatusBarButtons 组件模板
|
||||
├── xml/
|
||||
│ └── status_bar_buttons.xml # 新的模板定义
|
||||
└── scss/
|
||||
└── status_bar_buttons.scss # 移动端底部悬浮栏样式
|
||||
```
|
||||
|
||||
### 实现原理
|
||||
|
||||
1. **JavaScript 覆盖**:使用 `patch` 方法覆盖 `StatusBarButtons` 组件的模板
|
||||
2. **模板替换**:提供新的模板,根据 `env.isSmall` 判断显示方式
|
||||
3. **样式隔离**:使用媒体查询确保移动端和桌面端样式互不影响
|
||||
|
||||
## 使用说明
|
||||
|
||||
安装模块后,无需额外配置。在手机浏览器中打开任何包含 header 的 form 视图时,工具按钮栏会自动显示在底部。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 此模块只影响 form 视图的 header 按钮栏
|
||||
- 桌面端完全不受影响
|
||||
- 支持 iOS 设备的底部安全区域(safe area)
|
||||
@@ -1 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,33 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "k_ry_mobile_form",
|
||||
|
||||
'summary': "移动端表单优化",
|
||||
|
||||
'description': """
|
||||
移动端表单优化模块
|
||||
""",
|
||||
'author': "josh.feng",
|
||||
'website': "",
|
||||
'category': '界面优化',
|
||||
'version': '17.0.0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base', 'web'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'k_ry_mobile_form/static/src/js/status_bar_buttons.js',
|
||||
'k_ry_mobile_form/static/src/xml/status_bar_buttons.xml',
|
||||
'k_ry_mobile_form/static/src/scss/status_bar_buttons.scss',
|
||||
],
|
||||
},
|
||||
'application': False,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { StatusBarButtons } from "@web/views/form/status_bar_buttons/status_bar_buttons";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useEffect } from "@odoo/owl";
|
||||
|
||||
// 检测是否为移动设备(包括iOS Safari)
|
||||
const isMobileDevice = () => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
// 检测触摸设备
|
||||
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// 检测用户代理(iOS设备)
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
|
||||
// 检测屏幕宽度
|
||||
const isSmallScreen = window.innerWidth <= 767;
|
||||
|
||||
// 检测是否为移动设备(综合考虑)
|
||||
return hasTouch && (isIOS || isSmallScreen);
|
||||
};
|
||||
|
||||
// 覆盖 StatusBarButtons 组件,添加移动端检测逻辑
|
||||
patch(StatusBarButtons, {
|
||||
template: "k_ry_mobile_form.StatusBarButtons",
|
||||
setup() {
|
||||
// 如果 env.isSmall 判断不准确,强制显示移动端按钮
|
||||
if (typeof window !== 'undefined') {
|
||||
useEffect(() => {
|
||||
const checkAndFixMobileButtons = () => {
|
||||
try {
|
||||
// 如果是移动设备但 env.isSmall 为 false,强制显示移动端按钮
|
||||
if (isMobileDevice()) {
|
||||
const mobileContainers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed');
|
||||
if (mobileContainers.length === 0) return;
|
||||
|
||||
mobileContainers.forEach(container => {
|
||||
try {
|
||||
// 使用内联样式强制显示,优先级最高
|
||||
container.style.setProperty('display', 'block', 'important');
|
||||
container.style.setProperty('visibility', 'visible', 'important');
|
||||
container.style.setProperty('opacity', '1', 'important');
|
||||
container.style.setProperty('position', 'fixed', 'important');
|
||||
container.style.setProperty('bottom', '0', 'important');
|
||||
container.style.setProperty('left', '0', 'important');
|
||||
container.style.setProperty('right', '0', 'important');
|
||||
container.style.setProperty('z-index', '99999', 'important');
|
||||
// 强制设置背景色,防止被主题覆盖
|
||||
container.style.setProperty('background-color', '#ffffff', 'important');
|
||||
container.style.setProperty('background', '#ffffff', 'important');
|
||||
|
||||
// 同时修复容器内的按钮
|
||||
const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a');
|
||||
buttons.forEach(button => {
|
||||
try {
|
||||
// 强制设置按钮可见性
|
||||
button.style.setProperty('visibility', 'visible', 'important');
|
||||
button.style.setProperty('opacity', '1', 'important');
|
||||
button.style.setProperty('display', 'inline-block', 'important');
|
||||
button.style.setProperty('font-size', '14px', 'important');
|
||||
button.style.setProperty('font-weight', '500', 'important');
|
||||
// 防止文字被设置为透明
|
||||
button.style.setProperty('text-indent', '0', 'important');
|
||||
|
||||
// 检查按钮颜色
|
||||
const computedStyle = getComputedStyle(button);
|
||||
const bgColor = computedStyle.backgroundColor;
|
||||
const textColor = computedStyle.color;
|
||||
|
||||
// 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色
|
||||
const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)';
|
||||
const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff';
|
||||
const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff';
|
||||
const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)';
|
||||
|
||||
// 如果按钮不可见,强制设置颜色
|
||||
if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') {
|
||||
// 根据按钮类型设置颜色
|
||||
const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status');
|
||||
const hasSecondary = button.classList.contains('btn-secondary');
|
||||
const hasSuccess = button.classList.contains('btn-success');
|
||||
const hasDanger = button.classList.contains('btn-danger');
|
||||
const hasWarning = button.classList.contains('btn-warning');
|
||||
const hasInfo = button.classList.contains('btn-info');
|
||||
|
||||
if (hasPrimary || hasSuccess) {
|
||||
button.style.setProperty('background-color', '#007bff', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasSecondary) {
|
||||
button.style.setProperty('background-color', '#6c757d', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasDanger) {
|
||||
button.style.setProperty('background-color', '#dc3545', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasWarning) {
|
||||
button.style.setProperty('background-color', '#ffc107', 'important');
|
||||
button.style.setProperty('color', '#212529', 'important');
|
||||
} else if (hasInfo) {
|
||||
button.style.setProperty('background-color', '#17a2b8', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else {
|
||||
// 默认蓝色按钮
|
||||
button.style.setProperty('background-color', '#007bff', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error fixing button in setup:', e);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error fixing container in setup:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 隐藏桌面端按钮
|
||||
try {
|
||||
const desktopButtons = document.querySelectorAll('.o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons)');
|
||||
desktopButtons.forEach(btn => {
|
||||
btn.style.setProperty('display', 'none', 'important');
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error hiding desktop buttons in setup:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error in checkAndFixMobileButtons:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 延迟执行,确保DOM已渲染
|
||||
setTimeout(checkAndFixMobileButtons, 100);
|
||||
|
||||
// 监听窗口大小变化
|
||||
const handleResize = () => {
|
||||
setTimeout(checkAndFixMobileButtons, 100);
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// iOS Safari修复:确保移动端按钮在iOS上可点击和可见
|
||||
// 使用MutationObserver监听DOM变化,确保新添加的按钮也能正常工作
|
||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
let fixTimeout = null;
|
||||
let observer = null;
|
||||
let checkInterval = null;
|
||||
|
||||
// 防抖函数,避免频繁执行
|
||||
const debounce = (func, wait) => {
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(fixTimeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(fixTimeout);
|
||||
fixTimeout = setTimeout(later, wait);
|
||||
};
|
||||
};
|
||||
|
||||
const fixIOSButtons = () => {
|
||||
try {
|
||||
// 确保移动端按钮容器可见
|
||||
const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed');
|
||||
if (containers.length === 0) return;
|
||||
|
||||
containers.forEach(container => {
|
||||
try {
|
||||
// 如果是移动设备,确保容器可见
|
||||
if (isMobileDevice()) {
|
||||
// 使用 setProperty 的 important 标志强制设置样式
|
||||
container.style.setProperty('display', 'block', 'important');
|
||||
container.style.setProperty('visibility', 'visible', 'important');
|
||||
container.style.setProperty('opacity', '1', 'important');
|
||||
container.style.setProperty('position', 'fixed', 'important');
|
||||
container.style.setProperty('bottom', '0', 'important');
|
||||
container.style.setProperty('left', '0', 'important');
|
||||
container.style.setProperty('right', '0', 'important');
|
||||
container.style.setProperty('z-index', '99999', 'important');
|
||||
// 强制设置背景色,防止被主题覆盖
|
||||
container.style.setProperty('background-color', '#ffffff', 'important');
|
||||
container.style.setProperty('background', '#ffffff', 'important');
|
||||
}
|
||||
|
||||
// 修复按钮的交互性和可见性
|
||||
const buttons = container.querySelectorAll('button, a.btn, .btn, a[role="button"], .o_statusbar_buttons > * > button, .o_statusbar_buttons > * > a');
|
||||
buttons.forEach(button => {
|
||||
try {
|
||||
// 使用 setProperty 强制设置样式
|
||||
button.style.setProperty('pointer-events', 'auto', 'important');
|
||||
button.style.setProperty('cursor', 'pointer', 'important');
|
||||
button.style.setProperty('touch-action', 'manipulation', 'important');
|
||||
button.style.setProperty('visibility', 'visible', 'important');
|
||||
button.style.setProperty('opacity', '1', 'important');
|
||||
button.style.setProperty('display', 'inline-block', 'important');
|
||||
// 确保按钮有最小高度和字体大小
|
||||
button.style.setProperty('min-height', '44px', 'important');
|
||||
button.style.setProperty('font-size', '14px', 'important');
|
||||
button.style.setProperty('font-weight', '500', 'important');
|
||||
// 防止文字被设置为透明
|
||||
button.style.setProperty('text-indent', '0', 'important');
|
||||
|
||||
// 检查按钮颜色
|
||||
const computedStyle = getComputedStyle(button);
|
||||
const bgColor = computedStyle.backgroundColor;
|
||||
const textColor = computedStyle.color;
|
||||
|
||||
// 如果背景色是透明或白色,且文字颜色也是白色或透明,强制设置颜色
|
||||
const isTransparent = bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)';
|
||||
const isWhiteBg = bgColor === 'rgb(255, 255, 255)' || bgColor === '#ffffff' || bgColor === '#fff';
|
||||
const isWhiteText = textColor === 'rgb(255, 255, 255)' || textColor === '#ffffff' || textColor === '#fff';
|
||||
const isTransparentText = textColor === 'transparent' || textColor === 'rgba(0, 0, 0, 0)';
|
||||
|
||||
// 如果按钮不可见,强制设置颜色
|
||||
if (isTransparent || (isWhiteBg && (isWhiteText || isTransparentText)) || textColor === 'rgba(0, 0, 0, 0)') {
|
||||
// 根据按钮类型设置颜色
|
||||
const hasPrimary = button.classList.contains('btn-primary') || button.classList.contains('o_statusbar_status');
|
||||
const hasSecondary = button.classList.contains('btn-secondary');
|
||||
const hasSuccess = button.classList.contains('btn-success');
|
||||
const hasDanger = button.classList.contains('btn-danger');
|
||||
const hasWarning = button.classList.contains('btn-warning');
|
||||
const hasInfo = button.classList.contains('btn-info');
|
||||
|
||||
if (hasPrimary || hasSuccess) {
|
||||
button.style.setProperty('background-color', '#007bff', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasSecondary) {
|
||||
button.style.setProperty('background-color', '#6c757d', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasDanger) {
|
||||
button.style.setProperty('background-color', '#dc3545', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else if (hasWarning) {
|
||||
button.style.setProperty('background-color', '#ffc107', 'important');
|
||||
button.style.setProperty('color', '#212529', 'important');
|
||||
} else if (hasInfo) {
|
||||
button.style.setProperty('background-color', '#17a2b8', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
} else {
|
||||
// 默认蓝色按钮
|
||||
button.style.setProperty('background-color', '#007bff', 'important');
|
||||
button.style.setProperty('color', '#ffffff', 'important');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error fixing button:', e);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error fixing container:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 在移动设备上,确保原始状态栏中的按钮被隐藏
|
||||
if (isMobileDevice()) {
|
||||
try {
|
||||
const desktopButtons = document.querySelectorAll('.o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons)');
|
||||
desktopButtons.forEach(btn => {
|
||||
btn.style.setProperty('display', 'none', 'important');
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error hiding desktop buttons:', e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error in fixIOSButtons:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 防抖版本的修复函数
|
||||
const debouncedFixIOSButtons = debounce(fixIOSButtons, 300);
|
||||
|
||||
// 初始化函数:确保DOM准备好后再设置observer
|
||||
const initIOSButtonFix = () => {
|
||||
try {
|
||||
// 先执行一次修复
|
||||
fixIOSButtons();
|
||||
|
||||
// 确保document.body存在后再创建observer
|
||||
if (document.body && !observer) {
|
||||
// 监听DOM变化,确保动态添加的按钮也能正常工作
|
||||
observer = new MutationObserver((mutations) => {
|
||||
// 只处理相关的变化
|
||||
const hasRelevantChange = mutations.some(mutation => {
|
||||
if (mutation.type === 'childList') {
|
||||
return Array.from(mutation.addedNodes).some(node =>
|
||||
node.nodeType === 1 && (
|
||||
node.classList?.contains('o_statusbar_buttons_mobile_fixed') ||
|
||||
node.querySelector?.('.o_statusbar_buttons_mobile_fixed')
|
||||
)
|
||||
);
|
||||
}
|
||||
if (mutation.type === 'attributes') {
|
||||
return mutation.target.classList?.contains('o_statusbar_buttons_mobile_fixed') ||
|
||||
mutation.target.closest?.('.o_statusbar_buttons_mobile_fixed');
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasRelevantChange) {
|
||||
debouncedFixIOSButtons();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['style', 'class']
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error initializing button fix:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载后执行
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initIOSButtonFix, { once: true });
|
||||
} else {
|
||||
// 使用setTimeout确保DOM完全准备好,不阻塞页面加载
|
||||
setTimeout(initIOSButtonFix, 100);
|
||||
}
|
||||
|
||||
// 监听窗口大小变化和方向变化(iOS Safari 横竖屏切换)
|
||||
window.addEventListener('resize', () => {
|
||||
debouncedFixIOSButtons();
|
||||
}, { passive: true });
|
||||
|
||||
window.addEventListener('orientationchange', () => {
|
||||
setTimeout(debouncedFixIOSButtons, 200);
|
||||
}, { passive: true });
|
||||
|
||||
// 定期检查按钮是否可见(防止被其他样式覆盖)
|
||||
if (isMobileDevice()) {
|
||||
// 延迟启动定时器,避免影响页面加载
|
||||
setTimeout(() => {
|
||||
checkInterval = setInterval(() => {
|
||||
try {
|
||||
const containers = document.querySelectorAll('.o_statusbar_buttons_mobile_fixed');
|
||||
if (containers.length > 0) {
|
||||
containers.forEach(container => {
|
||||
try {
|
||||
const computedStyle = getComputedStyle(container);
|
||||
// 如果按钮被隐藏,强制显示
|
||||
if (computedStyle.display === 'none' ||
|
||||
computedStyle.visibility === 'hidden' ||
|
||||
parseFloat(computedStyle.opacity) < 0.1) {
|
||||
fixIOSButtons();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error checking container:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error in interval check:', e);
|
||||
}
|
||||
}, 2000); // 改为每2秒检查一次,减少频率
|
||||
}, 2000);
|
||||
|
||||
// 页面卸载时清除定时器和observer
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
if (fixTimeout) {
|
||||
clearTimeout(fixTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
// Mobile: 底部悬浮栏样式
|
||||
// 使用高优先级选择器,防止被其他主题样式覆盖
|
||||
.o_statusbar_buttons_mobile_fixed,
|
||||
body .o_statusbar_buttons_mobile_fixed,
|
||||
html body .o_statusbar_buttons_mobile_fixed {
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 99999 !important; // 比主题的9999更高,确保在最上层
|
||||
// 强制设置背景色,防止被主题覆盖
|
||||
background-color: #ffffff !important;
|
||||
background: #ffffff !important;
|
||||
border-top: 1px solid $border-color !important;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
padding: map-get($spacers, 2) map-get($spacers, 3) !important;
|
||||
// 为底部悬浮栏预留空间,避免内容被遮挡(支持 iOS safe area)
|
||||
padding-bottom: calc(#{map-get($spacers, 2)} + env(safe-area-inset-bottom, 0px)) !important;
|
||||
// iOS Safari 修复:确保元素可交互
|
||||
-webkit-transform: translateZ(0) !important;
|
||||
transform: translateZ(0) !important;
|
||||
// iOS Safari 修复:防止滚动时fixed定位失效
|
||||
-webkit-backface-visibility: hidden !important;
|
||||
backface-visibility: hidden !important;
|
||||
// 强制显示属性
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
// 确保元素不会被其他样式隐藏
|
||||
overflow: visible !important;
|
||||
clip: auto !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
|
||||
.o_statusbar_buttons {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
// iOS Safari 修复:确保容器可交互
|
||||
pointer-events: auto !important;
|
||||
-webkit-transform: translateZ(0) !important;
|
||||
transform: translateZ(0) !important;
|
||||
// 强制显示
|
||||
display: flex !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
|
||||
> * {
|
||||
flex: 1 !important;
|
||||
min-width: 0 !important;
|
||||
// iOS Safari 修复:确保子元素可交互
|
||||
pointer-events: auto !important;
|
||||
// 强制显示
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
|
||||
> button,
|
||||
> a,
|
||||
> .btn {
|
||||
width: 100% !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
// 确保按钮有足够的点击区域
|
||||
min-height: 44px !important;
|
||||
// iOS Safari 修复:确保按钮可点击
|
||||
cursor: pointer !important;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1) !important;
|
||||
touch-action: manipulation !important;
|
||||
// iOS Safari 修复:防止文本选择影响点击
|
||||
-webkit-user-select: none !important;
|
||||
user-select: none !important;
|
||||
-webkit-touch-callout: none !important;
|
||||
// iOS Safari 修复:确保按钮在层级之上
|
||||
position: relative !important;
|
||||
z-index: 1 !important;
|
||||
// iOS Safari 修复:确保pointer-events正确
|
||||
pointer-events: auto !important;
|
||||
// iOS Safari 修复:防止transform影响点击
|
||||
-webkit-transform: translateZ(0) !important;
|
||||
transform: translateZ(0) !important;
|
||||
// 强制显示按钮
|
||||
display: inline-block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
// 确保按钮背景和文字可见,防止被主题覆盖
|
||||
background-color: #007bff !important;
|
||||
background: #007bff !important;
|
||||
color: #ffffff !important;
|
||||
// 确保字体大小可见
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
// 确保有边框和内边距
|
||||
border: 1px solid #0056b3 !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 8px 16px !important;
|
||||
text-align: center !important;
|
||||
// 防止文字被设置为透明
|
||||
text-indent: 0 !important;
|
||||
}
|
||||
|
||||
// 特殊处理不同类型的按钮,确保都有可见的样式
|
||||
> button.btn-primary,
|
||||
> a.btn-primary,
|
||||
> .btn-primary {
|
||||
background-color: #007bff !important;
|
||||
background: #007bff !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #0056b3 !important;
|
||||
}
|
||||
|
||||
> button.btn-secondary,
|
||||
> a.btn-secondary,
|
||||
> .btn-secondary {
|
||||
background-color: #6c757d !important;
|
||||
background: #6c757d !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #545b62 !important;
|
||||
}
|
||||
|
||||
> button.btn-success,
|
||||
> a.btn-success,
|
||||
> .btn-success {
|
||||
background-color: #28a745 !important;
|
||||
background: #28a745 !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #1e7e34 !important;
|
||||
}
|
||||
|
||||
> button.btn-danger,
|
||||
> a.btn-danger,
|
||||
> .btn-danger {
|
||||
background-color: #dc3545 !important;
|
||||
background: #dc3545 !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #c82333 !important;
|
||||
}
|
||||
|
||||
> button.btn-warning,
|
||||
> a.btn-warning,
|
||||
> .btn-warning {
|
||||
background-color: #ffc107 !important;
|
||||
background: #ffc107 !important;
|
||||
color: #212529 !important;
|
||||
border-color: #e0a800 !important;
|
||||
}
|
||||
|
||||
> button.btn-info,
|
||||
> a.btn-info,
|
||||
> .btn-info {
|
||||
background-color: #17a2b8 !important;
|
||||
background: #17a2b8 !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #117a8b !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 桌面端隐藏移动端悬浮栏(但保留触摸设备的显示)
|
||||
@include media-breakpoint-up(md) {
|
||||
.o_statusbar_buttons_mobile_fixed,
|
||||
body .o_statusbar_buttons_mobile_fixed,
|
||||
html body .o_statusbar_buttons_mobile_fixed {
|
||||
// 只在非触摸设备上隐藏(桌面浏览器)
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端隐藏原始状态栏中的按钮(避免重复显示)
|
||||
@include media-breakpoint-down(md) {
|
||||
.o_form_statusbar {
|
||||
// 隐藏状态栏中的按钮容器,但保留字段等其他内容
|
||||
> .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 为 form view 添加底部 padding,避免内容被底部悬浮栏遮挡
|
||||
.o_form_view {
|
||||
padding-bottom: 80px; // 为底部悬浮栏预留空间
|
||||
}
|
||||
}
|
||||
|
||||
// iOS Safari 特殊处理:确保在 iOS 设备上按钮可见
|
||||
// 使用高优先级选择器,防止被其他主题样式覆盖
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.o_statusbar_buttons_mobile_fixed,
|
||||
body .o_statusbar_buttons_mobile_fixed,
|
||||
html body .o_statusbar_buttons_mobile_fixed {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons),
|
||||
body .o_form_statusbar > .o_statusbar_buttons:not(.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用移动设备强制显示规则(防止被其他主题覆盖)
|
||||
@media (max-width: 767px) {
|
||||
.o_statusbar_buttons_mobile_fixed,
|
||||
body .o_statusbar_buttons_mobile_fixed,
|
||||
html body .o_statusbar_buttons_mobile_fixed {
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
z-index: 99999 !important;
|
||||
// 强制背景色,防止被主题覆盖
|
||||
background-color: #ffffff !important;
|
||||
background: #ffffff !important;
|
||||
}
|
||||
|
||||
// 确保按钮容器内的所有按钮都可见
|
||||
.o_statusbar_buttons_mobile_fixed .o_statusbar_buttons,
|
||||
body .o_statusbar_buttons_mobile_fixed .o_statusbar_buttons,
|
||||
html body .o_statusbar_buttons_mobile_fixed .o_statusbar_buttons {
|
||||
button, a, .btn {
|
||||
// 防止文字被设置为透明
|
||||
color: #ffffff !important;
|
||||
text-indent: 0 !important;
|
||||
// 确保有背景色
|
||||
background-color: #007bff !important;
|
||||
background: #007bff !important;
|
||||
// 确保字体可见
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<!-- 覆盖 web.StatusBarButtons 模板,实现移动端底部悬浮栏 -->
|
||||
<t t-name="k_ry_mobile_form.StatusBarButtons">
|
||||
<t t-set="slots" t-value="visibleSlotNames" />
|
||||
<!-- 移动端:底部悬浮栏(始终渲染,通过CSS控制显示) -->
|
||||
<div class="o_statusbar_buttons_mobile_fixed" t-att-style="env.isSmall ? '' : 'display: none;'">
|
||||
<div class="o_statusbar_buttons d-flex align-items-center justify-content-center gap-2">
|
||||
<t t-foreach="slots" t-as="slot" t-key="slot">
|
||||
<t t-slot="{{ slot }}" />
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 桌面端:正常显示 -->
|
||||
<div class="o_statusbar_buttons d-flex align-items-center align-content-around flex-wrap gap-1" t-att-style="env.isSmall ? 'display: none;' : ''">
|
||||
<t t-foreach="slots" t-as="slot" t-key="slot">
|
||||
<t t-slot="{{ slot }}" />
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user