全屏模式¶
Django Admin Dashboards 提供强大的全屏模式控制功能,允许您在演示、监控或专注分析时最大化数据看板可见性。本指南涵盖全屏功能的所有方面。
目录¶
概述¶
什么是全屏模式?¶
全屏模式通过隐藏周围的界面元素来最大化数据看板显示。提供两种不同的模式:
- 全屏时隐藏:仅在浏览器进入全屏模式时自动隐藏周围元素
- 强制隐藏:无论全屏状态如何,始终隐藏周围元素
全屏模式的优势¶
- 演示就绪:为演示提供干净、无干扰的显示
- 最大化空间:利用整个屏幕进行数据可视化
- 专注模式:移除界面杂乱,便于专注分析
- 监控:适合在专用显示器上进行数据看板监控
- 可访问性:更大的元素,更好的可见性
全屏模式的工作原理¶
- URL 参数检测:JavaScript 从 URL 读取全屏参数
- CSS 类应用:基于全屏状态添加/移除 CSS 类
- 元素隐藏:使用 CSS
display: none隐藏指定元素 - 事件监听:监听浏览器全屏更改事件
- 状态管理:在交互过程中保持全屏状态
快速开始¶
通过 URL 启用全屏¶
通过 URL 参数控制全屏行为:
# 仅在全屏模式下隐藏周围元素
/admin/?_hide_others_in_fullscreen=true
# 始终隐藏周围元素(强制模式)
/admin/?_force_hide_others=true
# 禁用全屏隐藏
/admin/?_hide_others_in_fullscreen=false
数据看板要求¶
全屏控制需要数据看板类配置:
from django_admin_dashboards.base import Dashboard
class MyDashboard(Dashboard):
# 启用全屏时隐藏功能
hide_others_in_fullscreen = True
# 启用强制隐藏功能
force_hide_others = False
# 可选:自定义要隐藏的元素
fullscreen_hide_selectors = [
'#header',
'#footer',
'.sidebar',
'.breadcrumbs',
'.object-tools',
]
基础使用示例¶
# views.py 或数据看板配置
from django_admin_dashboards.contrib.auth.dashboard import AuthAppDashboard
def get_dashboard_for_presentation():
"""返回为演示模式配置的数据看板"""
dashboard = AuthAppDashboard()
# 启用全屏功能
dashboard.hide_others_in_fullscreen = True
dashboard.force_hide_others = False
return dashboard
<a href="{% url 'admin:index' %}?_hide_others_in_fullscreen=true&_dark_mode_on=true"
class="presentation-mode-link">
进入演示模式
</a>
URL 参数¶
_hide_others_in_fullscreen 参数¶
仅在浏览器处于全屏模式时隐藏周围元素:
| 值 | 描述 | 行为 |
|---|---|---|
true |
启用全屏时隐藏 | 浏览器进入全屏时隐藏元素 |
false |
禁用全屏时隐藏 | 无自动隐藏(默认) |
1, yes |
替代真值 | 与 true 相同 |
0, no |
替代假值 | 与 false 相同 |
使用示例:
# 基本用法
/admin/?_hide_others_in_fullscreen=true
# 替代值
/admin/?_hide_others_in_fullscreen=1
/admin/?_hide_others_in_fullscreen=yes
# 禁用
/admin/?_hide_others_in_fullscreen=false
工作原理: 1. 从 URL 检测参数 2. JavaScript 监听浏览器全屏事件 3. 当浏览器进入全屏时,添加 CSS 类 4. 通过 CSS 隐藏指定元素 5. 当浏览器退出全屏时,移除 CSS 类 6. 元素再次可见
_force_hide_others 参数¶
无论全屏状态如何,始终隐藏周围元素:
| 值 | 描述 | 行为 |
|---|---|---|
true |
启用强制隐藏 | 始终隐藏指定元素 |
false |
禁用强制隐藏 | 显示元素(默认) |
1, yes |
替代真值 | 与 true 相同 |
0, no |
替代假值 | 与 false 相同 |
使用示例:
# 始终隐藏周围元素
/admin/?_force_hide_others=true
# 替代值
/admin/?_force_hide_others=1
/admin/?_force_hide_others=yes
# 显示元素
/admin/?_force_hide_others=false
工作原理: 1. 从 URL 检测参数 2. 立即向 body 添加 CSS 类 3. 通过 CSS 隐藏指定元素 4. 无需浏览器全屏交互 5. 元素保持隐藏直到参数被移除
参数组合¶
将全屏参数与其他控制组合:
# 演示模式:全屏隐藏 + 深色模式
/admin/?_hide_others_in_fullscreen=true&_dark_mode_on=true
# 监控模式:强制隐藏 + 自动刷新
/admin/?_force_hide_others=true&_refresh=10
# 组合:强制隐藏优先于全屏隐藏
/admin/?_force_hide_others=true&_hide_others_in_fullscreen=true
# 信息亭模式:强制隐藏 + 无导航
/admin/?_force_hide_others=true&_disable_navigation=true
参数优先级¶
当指定两个参数时: 1. _force_hide_others=true 优先(始终隐藏) 2.
_force_hide_others 激活时忽略 _hide_others_in_fullscreen 3.
为获得可预测的行为,仅使用一个参数
# 此组合:强制隐藏始终激活
/admin/?_force_hide_others=true&_hide_others_in_fullscreen=true
# 更好:选择一种模式
/admin/?_force_hide_others=true # 或
/admin/?_hide_others_in_fullscreen=true
数据看板配置¶
基础配置¶
在数据看板类中启用全屏功能:
from django_admin_dashboards.base import Dashboard
class FullscreenEnabledDashboard(Dashboard):
"""支持全屏的数据看板"""
# 启用全屏时隐藏功能
hide_others_in_fullscreen = True
# 启用强制隐藏功能
force_hide_others = False
# 自定义要隐藏的元素(可选)
fullscreen_hide_selectors = [
'#header', # Django Admin 头部
'#footer', # 页脚(如果存在)
'.sidebar', # 导航侧边栏
'.breadcrumbs', # 面包屑导航
'.object-tools', # 对象操作工具
'.submit-row', # 表单提交行
'.help', # 帮助文本
'.extra-actions', # 任何额外操作按钮
]
def get_context_data(self, request):
context = super().get_context_data(request)
# 添加上下文
context['fullscreen_enabled'] = (
self.hide_others_in_fullscreen or
self.force_hide_others
)
# 检查当前全屏参数
context['is_fullscreen_hide'] = self.is_fullscreen_hide_enabled(request)
context['is_force_hide'] = self.is_force_hide_enabled(request)
return context
高级配置¶
class AdvancedFullscreenDashboard(Dashboard):
"""高级全屏配置"""
# 全屏设置
hide_others_in_fullscreen = True
force_hide_others = False
# 自定义 CSS 类
fullscreen_css_class = 'dashboard-fullscreen-mode'
force_hide_css_class = 'dashboard-force-hide-others'
# 元素选择器及描述
fullscreen_hide_selectors = {
'#header': '主品牌头部',
'#footer': '页面页脚',
'.sidebar': '导航侧边栏',
'.breadcrumbs': '面包屑路径',
'.object-tools': '对象操作按钮',
'.submit-row': '表单提交按钮',
'.help': '帮助文本块',
'h1:first-of-type': '第一个标题(如果不属于数据看板)',
}
# 全屏事件回调
def on_fullscreen_enter(self, request):
"""进入全屏时调用"""
# 记录全屏进入
import logging
logger = logging.getLogger(__name__)
logger.info(f"从 {request.path} 进入全屏")
# 添加上下文
return {
'fullscreen_message': '进入演示模式',
'fullscreen_timestamp': timezone.now(),
}
def on_fullscreen_exit(self, request):
"""退出全屏时调用"""
# 自定义退出处理
return {
'fullscreen_message': '退出演示模式',
}
def get_context_data(self, request):
context = super().get_context_data(request)
# 为模板添加选择器信息
context['fullscreen_selectors'] = list(
self.fullscreen_hide_selectors.keys()
if isinstance(self.fullscreen_hide_selectors, dict)
else self.fullscreen_hide_selectors
)
# 检查全屏状态
if self.is_fullscreen_hide_enabled(request):
context.update(self.on_fullscreen_enter(request))
return context
动态配置¶
基于用户或请求动态配置全屏行为:
class DynamicFullscreenDashboard(Dashboard):
"""基于请求动态配置全屏"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 使用默认值初始化
self.hide_others_in_fullscreen = False
self.force_hide_others = False
self.fullscreen_hide_selectors = []
def configure_for_request(self, request):
"""基于请求参数或用户进行配置"""
# 检查 URL 参数
hide_in_fs = request.GET.get('_hide_others_in_fullscreen', '').lower()
force_hide = request.GET.get('_force_hide_others', '').lower()
# 根据 URL 参数设置
self.hide_others_in_fullscreen = hide_in_fs in ['true', '1', 'yes']
self.force_hide_others = force_hide in ['true', '1', 'yes']
# 根据用户角色配置选择器
if request.user.is_superuser:
self.fullscreen_hide_selectors = [
'#header', '.sidebar', '.breadcrumbs'
]
else:
# 普通用户仅隐藏最小元素
self.fullscreen_hide_selectors = [
'#header', '.breadcrumbs'
]
# 从设置中添加自定义选择器
custom_selectors = getattr(settings, 'DASHBOARD_FULLSCREEN_SELECTORS', [])
self.fullscreen_hide_selectors.extend(custom_selectors)
def get_context_data(self, request):
# 在获取上下文前配置
self.configure_for_request(request)
context = super().get_context_data(request)
# 添加上下文
context['fullscreen_config'] = {
'hide_in_fullscreen': self.hide_others_in_fullscreen,
'force_hide': self.force_hide_others,
'selectors': self.fullscreen_hide_selectors,
'is_active': self.hide_others_in_fullscreen or self.force_hide_others,
}
return context
配置验证¶
class ValidatedFullscreenDashboard(Dashboard):
"""带有验证的全屏配置数据看板"""
hide_others_in_fullscreen = True
force_hide_others = False
def validate_fullscreen_config(self):
"""验证全屏配置"""
errors = []
warnings = []
# 检查冲突设置
if self.hide_others_in_fullscreen and self.force_hide_others:
warnings.append(
"hide_others_in_fullscreen 和 force_hide_others 同时启用。"
"force_hide_others 将优先。"
)
# 验证选择器
if not hasattr(self, 'fullscreen_hide_selectors'):
errors.append("未定义 fullscreen_hide_selectors")
elif not isinstance(self.fullscreen_hide_selectors, (list, tuple, dict)):
errors.append("fullscreen_hide_selectors 必须是列表、元组或字典")
elif len(self.fullscreen_hide_selectors) == 0:
warnings.append("fullscreen_hide_selectors 为空")
# 检查危险选择器
dangerous_selectors = ['body', 'html', '.dashboard', '#content']
for selector in self.fullscreen_hide_selectors:
if selector in dangerous_selectors:
errors.append(f"危险选择器: {selector} 会隐藏数据看板本身")
return {
'valid': len(errors) == 0,
'errors': errors,
'warnings': warnings,
}
def get_context_data(self, request):
context = super().get_context_data(request)
# 添加验证结果
validation = self.validate_fullscreen_config()
context['fullscreen_validation'] = validation
if not validation['valid']:
# 记录错误
import logging
logger = logging.getLogger(__name__)
for error in validation['errors']:
logger.error(f"全屏配置错误: {error}")
return context
CSS 实现¶
基础 CSS 类¶
全屏模式使用 CSS 类隐藏元素:
/* dashboard.css - 全屏部分 */
/* 全屏模式类(进入全屏时添加) */
.dashboard-fullscreen-mode #header,
.dashboard-fullscreen-mode #footer,
.dashboard-fullscreen-mode .sidebar,
.dashboard-fullscreen-mode .breadcrumbs,
.dashboard-fullscreen-mode .object-tools {
display: none !important;
}
/* 强制隐藏类(启用强制隐藏时添加) */
.dashboard-force-hide-others #header,
.dashboard-force-hide-others #footer,
.dashboard-force-hide-others .sidebar,
.dashboard-force-hide-others .breadcrumbs,
.dashboard-force-hide-others .object-tools {
display: none !important;
}
/* 确保数据看板保持可见 */
.dashboard-fullscreen-mode .dashboard,
.dashboard-force-hide-others .dashboard {
display: block !important;
width: 100% !important;
height: 100vh !important;
margin: 0 !important;
padding: 20px !important;
}
/* 改进全屏下的数据看板外观 */
.dashboard-fullscreen-mode .dashboard {
background-color: var(--dashboard-bg-color);
overflow: auto;
}
/* 可选:添加全屏指示器 */
.dashboard-fullscreen-mode::before {
content: '全屏模式';
position: fixed;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
z-index: 10000;
opacity: 0.7;
}
响应式全屏样式¶
/* 针对不同屏幕尺寸的响应式调整 */
/* 小屏幕 */
@media (max-width: 768px)
/* 在小屏幕上隐藏额外元素 */
.dashboard-fullscreen-mode .desktop-only,
.dashboard-force-hide-others .desktop-only {
display: none !important;
}
/* 调整组件间距 */
.dashboard-fullscreen-mode .dashboard-col,
.dashboard-force-hide-others .dashboard-col {
margin-bottom: 15px !important;
}
}
/* 大屏幕 */
@media (min-width: 1200px)
/* 增大字体以提高可读性 */
.dashboard-fullscreen-mode .dashboard h1,
.dashboard-force-hide-others .dashboard h1 {
font-size: 2.5rem !important;
}
}
/* 打印样式,用于全屏类似输出 */
@media print {
/* 打印时隐藏非必要元素 */
#header, #footer, .sidebar, .breadcrumbs, .object-tools {
display: none !important;
}
/* 确保数据看板正确打印 */
.dashboard {
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
/* 避免组件内分页 */
.dashboard-col {
page-break-inside: avoid !important;
}
}
全屏 CSS 变量¶
/* 用于全屏自定义的 CSS 变量 */
:root {
--fullscreen-padding: 20px;
--fullscreen-bg-color: var(--dashboard-bg-color);
--fullscreen-overlay-color: rgba(0, 0, 0, 0.9);
--fullscreen-transition-duration: 300ms;
}
[data-theme="dark"] {
--fullscreen-bg-color: #000000;
--fullscreen-overlay-color: rgba(255, 255, 255, 0.1);
}
/* 应用 CSS 变量 */
.dashboard-fullscreen-mode .dashboard,
.dashboard-force-hide-others .dashboard {
padding: var(--fullscreen-padding) !important;
background-color: var(--fullscreen-bg-color) !important;
transition: all var(--fullscreen-transition-duration) ease !important;
}
/* 全屏覆盖效果 */
.dashboard-fullscreen-mode::after {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--fullscreen-overlay-color);
z-index: 9998;
pointer-events: none;
}
动画和过渡¶
/* 进入/退出全屏的平滑过渡 */
/* 元素隐藏动画 */
.dashboard-fullscreen-mode #header,
.dashboard-fullscreen-mode #footer,
.dashboard-fullscreen-mode .sidebar,
.dashboard-force-hide-others #header,
.dashboard-force-hide-others #footer,
.dashboard-force-hide-others .sidebar {
transition: all 0.3s ease !important;
opacity: 1;
}
/* 隐藏时 */
.dashboard-fullscreen-mode #header,
.dashboard-fullscreen-mode #footer,
.dashboard-fullscreen-mode .sidebar,
.dashboard-force-hide-others #header,
.dashboard-force-hide-others #footer,
.dashboard-force-hide-others .sidebar {
opacity: 0 !important;
height: 0 !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
}
/* 数据看板展开动画 */
.dashboard {
transition: all 0.3s ease;
}
.dashboard-fullscreen-mode .dashboard,
.dashboard-force-hide-others .dashboard {
animation: dashboardExpand 0.3s ease forwards;
}
@keyframes dashboardExpand {
from {
transform: scale(0.95);
opacity: 0.9;
}
to {
transform: scale(1);
opacity: 1;
}
}
/* 全屏指示器动画 */
.dashboard-fullscreen-mode::before {
animation: slideIn 0.3s ease forwards;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 0.7;
}
}
JavaScript API¶
全屏检测与控制¶
// 全屏管理器类
class DashboardFullscreenManager {
constructor() {
this.isFullscreen = false;
this.fullscreenModeEnabled = false;
this.forceHideEnabled = false;
this.initialize();
}
initialize() {
// 检查 URL 参数
this.checkUrlParameters();
// 设置事件监听器
this.setupEventListeners();
// 应用初始状态
this.applyFullscreenState();
// 如果需要,添加控制按钮
this.addControlButtons();
}
checkUrlParameters() {
// 检查全屏隐藏参数
const hideInFs = this.getUrlParameter('_hide_others_in_fullscreen');
this.fullscreenModeEnabled = ['true', '1', 'yes'].includes(hideInFs);
// 检查强制隐藏参数
const forceHide = this.getUrlParameter('_force_hide_others');
this.forceHideEnabled = ['true', '1', 'yes'].includes(forceHide);
}
getUrlParameter(name) {
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
const results = regex.exec(window.location.href);
return results ? decodeURIComponent(results[2].replace(/\+/g, ' ')) : null;
}
setupEventListeners() {
// 跨浏览器全屏事件监听器
document.addEventListener('fullscreenchange', this.handleFullscreenChange.bind(this));
document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange.bind(this));
document.addEventListener('mozfullscreenchange', this.handleFullscreenChange.bind(this));
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange.bind(this));
// 监听参数变化
window.addEventListener('urlparameterschanged', (event) => {
const { param, value } = event.detail;
if (param === '_hide_others_in_fullscreen') {
this.fullscreenModeEnabled = ['true', '1', 'yes'].includes(value);
this.applyFullscreenState();
} else if (param === '_force_hide_others') {
this.forceHideEnabled = ['true', '1', 'yes'].includes(value);
this.applyFullscreenState();
}
});
}
handleFullscreenChange() {
this.isFullscreen = this.checkFullscreenState();
this.applyFullscreenState();
// 派发自定义事件
this.dispatchFullscreenChangeEvent();
}
checkFullscreenState() {
return !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
applyFullscreenState() {
// 移除现有类
document.documentElement.classList.remove('dashboard-fullscreen-mode');
document.body.classList.remove('dashboard-force-hide-others');
// 应用强制隐藏(最高优先级)
if (this.forceHideEnabled) {
document.body.classList.add('dashboard-force-hide-others');
}
// 应用全屏隐藏(仅在全屏时)
else if (this.fullscreenModeEnabled && this.isFullscreen) {
document.documentElement.classList.add('dashboard-fullscreen-mode');
}
// 更新 UI 指示器
this.updateIndicators();
}
updateIndicators() {
// 更新 UI 指示器
const indicator = document.querySelector('.fullscreen-indicator');
if (indicator) {
if (this.forceHideEnabled) {
indicator.textContent = 'Force Hide Active';
indicator.classList.add('force-hide');
} else if (this.fullscreenModeEnabled && this.isFullscreen) {
indicator.textContent = 'Fullscreen Mode';
indicator.classList.add('fullscreen');
} else {
indicator.textContent = 'Normal Mode';
indicator.classList.remove('force-hide', 'fullscreen');
}
}
}
dispatchFullscreenChangeEvent() {
window.dispatchEvent(new CustomEvent('dashboardfullscreenchange', {
detail: {
isFullscreen: this.isFullscreen,
fullscreenModeEnabled: this.fullscreenModeEnabled,
forceHideEnabled: this.forceHideEnabled,
forceHideActive: this.forceHideEnabled,
fullscreenHideActive: this.fullscreenModeEnabled && this.isFullscreen,
}
}));
}
addControlButtons() {
// 添加全屏切换按钮
const button = document.createElement('button');
button.className = 'fullscreen-toggle-btn';
button.innerHTML = '📺 切换全屏';
button.title = '切换浏览器全屏模式';
button.addEventListener('click', () => {
this.toggleFullscreen();
});
// 添加到数据看板工具栏(如果存在)
const toolbar = document.querySelector('.dashboard-toolbar') ||
document.querySelector('.dashboard') ||
document.body;
if (toolbar) {
toolbar.appendChild(button);
}
}
toggleFullscreen() {
if (!this.isFullscreen) {
this.enterFullscreen();
} else {
this.exitFullscreen();
}
}
enterFullscreen() {
const element = document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
// 公共 API 方法
enableFullscreenMode(enable = true) {
this.fullscreenModeEnabled = enable;
this.applyFullscreenState();
this.updateUrlParameter('_hide_others_in_fullscreen', enable ? 'true' : 'false');
}
enableForceHide(enable = true) {
this.forceHideEnabled = enable;
this.applyFullscreenState();
this.updateUrlParameter('_force_hide_others', enable ? 'true' : 'false');
}
updateUrlParameter(param, value) {
const url = new URL(window.location);
if (value === null || value === 'false' || value === '') {
url.searchParams.delete(param);
} else {
url.searchParams.set(param, value);
}
window.history.replaceState({}, '', url);
// 派发参数变化事件
window.dispatchEvent(new CustomEvent('urlparameterschanged', {
detail: { param, value }
}));
}
}
// 初始化全屏管理器
document.addEventListener('DOMContentLoaded', () => {
window.dashboardFullscreenManager = new DashboardFullscreenManager();
});
事件处理¶
// 监听全屏事件
window.addEventListener('dashboardfullscreenchange', (event) => {
const {
isFullscreen,
fullscreenModeEnabled,
forceHideEnabled,
forceHideActive,
fullscreenHideActive
} = event.detail;
console.log('全屏状态变化:', event.detail);
// 基于全屏状态更新 UI
updateUIForFullscreenState(event.detail);
// 调整图表和可视化
if (fullscreenHideActive || forceHideActive) {
resizeChartsForFullscreen();
}
// 保存状态到 localStorage
saveFullscreenState(event.detail);
});
function updateUIForFullscreenState(state) {
// 更新依赖于全屏的 UI 元素
const dashboard = document.querySelector('.dashboard');
if (dashboard) {
if (state.forceHideActive) {
dashboard.classList.add('force-hide-active');
dashboard.classList.remove('fullscreen-active');
} else if (state.fullscreenHideActive) {
dashboard.classList.add('fullscreen-active');
dashboard.classList.remove('force-hide-active');
} else {
dashboard.classList.remove('force-hide-active', 'fullscreen-active');
}
}
// 更新状态指示器
updateStatusIndicator(state);
}
function updateStatusIndicator(state) {
let indicator = document.getElementById('fullscreen-status-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'fullscreen-status-indicator';
indicator.style.cssText = `
position: fixed;
bottom: 10px;
right: 10px;
padding: 5px 10px;
background: rgba(0,0,0,0.7);
color: white;
border-radius: 3px;
font-size: 12px;
z-index: 10000;
`;
document.body.appendChild(indicator);
}
if (state.forceHideActive) {
indicator.textContent = 'Force Hide Mode';
indicator.style.background = '#dc2626';
} else if (state.fullscreenHideActive) {
indicator.textContent = 'Fullscreen Mode';
indicator.style.background = '#059669';
} else {
indicator.textContent = 'Normal Mode';
indicator.style.background = '#6b7280';
}
}
function resizeChartsForFullscreen() {
// 调整图表以利用全屏空间
const charts = document.querySelectorAll('.chart-container canvas');
charts.forEach(canvas => {
const chart = window.Chart.getChart(canvas);
if (chart) {
chart.resize();
}
});
// 触发窗口调整大小事件以供其他组件使用
window.dispatchEvent(new Event('resize'));
}
function saveFullscreenState(state) {
// 保存到 localStorage 以实现持久化
localStorage.setItem('dashboard_fullscreen_state', JSON.stringify({
timestamp: new Date().toISOString(),
state: state
}));
}
实用函数¶
// 全屏实用函数
const DashboardFullscreenUtils = {
// 检查是否支持全屏
isFullscreenSupported() {
return !!(
document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
document.mozFullScreenEnabled ||
document.msFullscreenEnabled
);
},
// 获取当前全屏元素
getFullscreenElement() {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
},
// 检查数据看板是否处于全屏模式
isDashboardFullscreen() {
const element = this.getFullscreenElement();
return element === document.documentElement;
},
// 切换特定元素的全屏状态
toggleElementFullscreen(element = document.documentElement) {
if (!this.isFullscreenSupported()) {
console.warn('此浏览器不支持全屏');
return false;
}
if (this.getFullscreenElement()) {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
return false;
} else {
// 进入全屏
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
return true;
}
},
// 从 URL 获取全屏状态
getFullscreenStateFromUrl() {
const params = new URLSearchParams(window.location.search);
return {
hideInFullscreen: ['true', '1', 'yes'].includes(params.get('_hide_others_in_fullscreen')),
forceHide: ['true', '1', 'yes'].includes(params.get('_force_hide_others')),
rawParams: {
hideInFullscreen: params.get('_hide_others_in_fullscreen'),
forceHide: params.get('_force_hide_others'),
}
};
},
// 生成全屏 URL
generateFullscreenUrl(options = {}) {
const url = new URL(window.location);
// 设置或清除参数
if (options.hideInFullscreen !== undefined) {
if (options.hideInFullscreen) {
url.searchParams.set('_hide_others_in_fullscreen', 'true');
} else {
url.searchParams.delete('_hide_others_in_fullscreen');
}
}
if (options.forceHide !== undefined) {
if (options.forceHide) {
url.searchParams.set('_force_hide_others', 'true');
} else {
url.searchParams.delete('_force_hide_others');
}
}
return url.toString();
},
// 创建全屏控制 UI
createFullscreenControls() {
const container = document.createElement('div');
container.className = 'fullscreen-controls';
container.innerHTML = `
<div class="btn-group">
<button class="btn-toggle-fullscreen" title="切换全屏">
<span class="icon">📺</span>
<span class="text">全屏</span>
</button>
<button class="btn-toggle-force-hide" title="切换强制隐藏">
<span class="icon">👁️</span>
<span class="text">强制隐藏</span>
</button>
<button class="btn-exit-fullscreen" title="退出全屏" style="display: none;">
<span class="icon">🚪</span>
<span class="text">退出</span>
</button>
</div>
<div class="status"></div>
`;
// 添加样式
const styles = document.createElement('style');
styles.textContent = `
.fullscreen-controls {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
}
.fullscreen-controls .btn-group {
display: flex;
gap: 5px;
background: rgba(0,0,0,0.8);
padding: 10px;
border-radius: 5px;
}
.fullscreen-controls button {
background: #4b5563;
color: white;
border: none;
padding: 8px 12px;
border-radius: 3px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
.fullscreen-controls button:hover {
background: #6b7280;
}
.fullscreen-controls .status {
margin-top: 5px;
font-size: 12px;
color: #9ca3af;
text-align: center;
}
`;
document.head.appendChild(styles);
// 添加事件监听器
const toggleFsBtn = container.querySelector('.btn-toggle-fullscreen');
const toggleForceBtn = container.querySelector('.btn-toggle-force-hide');
const exitBtn = container.querySelector('.btn-exit-fullscreen');
const status = container.querySelector('.status');
toggleFsBtn.addEventListener('click', () => {
const current = this.getFullscreenStateFromUrl();
const newUrl = this.generateFullscreenUrl({
hideInFullscreen: !current.hideInFullscreen,
forceHide: false, // 启用全屏时禁用强制隐藏
});
window.location.href = newUrl;
});
toggleForceBtn.addEventListener('click', () => {
const current = this.getFullscreenStateFromUrl();
const newUrl = this.generateFullscreenUrl({
forceHide: !current.forceHide,
hideInFullscreen: false, // 启用强制隐藏时禁用全屏隐藏
});
window.location.href = newUrl;
});
exitBtn.addEventListener('click', () => {
window.dashboardFullscreenManager?.exitFullscreen();
});
// 基于状态更新 UI
const updateUI = () => {
const state = this.getFullscreenStateFromUrl();
const isFullscreen = this.getFullscreenElement();
toggleFsBtn.classList.toggle('active', state.hideInFullscreen);
toggleForceBtn.classList.toggle('active', state.forceHide);
exitBtn.style.display = isFullscreen ? 'flex' : 'none';
if (state.forceHide) {
status.textContent = '强制隐藏激活';
} else if (state.hideInFullscreen && isFullscreen) {
status.textContent = '全屏模式';
} else if (state.hideInFullscreen) {
status.textContent = '全屏就绪(按 F11)';
} else {
status.textContent = '普通模式';
}
};
// 初始更新
updateUI();
// 监听变化
window.addEventListener('dashboardfullscreenchange', updateUI);
window.addEventListener('resize', updateUI);
return container;
}
};
// 向页面添加控件
document.addEventListener('DOMContentLoaded', () => {
if (DashboardFullscreenUtils.isFullscreenSupported()) {
const controls = DashboardFullscreenUtils.createFullscreenControls();
document.body.appendChild(controls);
}
});
浏览器兼容性¶
支持的浏览器¶
跨浏览器的 Fullscreen API 支持:
| 浏览器 | 版本 | Fullscreen API | 前缀 | 备注 |
|---|---|---|---|---|
| Chrome | 15+ | ✅ | webkit |
完全支持 |
| Firefox | 10+ | ✅ | moz |
完全支持 |
| Safari | 5.1+ | ✅ | webkit |
完全支持 |
| Edge | 12+ | ✅ | ms |
完全支持 |
| Opera | 12.1+ | ✅ | - |
完全支持 |
| IE | 11 | ⚠️ | ms |
部分支持,已弃用 |
功能检测¶
// 全面的功能检测
const FullscreenSupport = {
// 检查全屏支持
supported: !!(document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
document.mozFullScreenEnabled ||
document.msFullscreenEnabled),
// 检查特定供应商API
vendors: {
standard: 'fullscreenEnabled' in document,
webkit: 'webkitFullscreenEnabled' in document,
moz: 'mozFullScreenEnabled' in document,
ms: 'msFullscreenEnabled' in document
},
// 获取适当的方法名称
methods: {
requestFullscreen: (
document.documentElement.requestFullscreen ||
document.documentElement.webkitRequestFullscreen ||
document.documentElement.mozRequestFullScreen ||
document.documentElement.msRequestFullscreen
),
exitFullscreen: (
document.exitFullscreen ||
document.webkitExitFullscreen ||
document.mozCancelFullScreen ||
document.msExitFullscreen
),
fullscreenElement: (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
),
fullscreenEnabled: (
document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
document.mozFullScreenEnabled ||
document.msFullscreenEnabled
),
fullscreenchange: (
'onfullscreenchange' in document ? 'fullscreenchange' :
'onwebkitfullscreenchange' in document ? 'webkitfullscreenchange' :
'onmozfullscreenchange' in document ? 'mozfullscreenchange' :
'onMSFullscreenChange' in document ? 'MSFullscreenChange' : null
)
},
// 测试全屏功能
test() {
if (!this.supported) {
return {
supported: false,
message: 'Fullscreen API 不支持'
};
}
const tests = {
canEnter: typeof this.methods.requestFullscreen === 'function',
canExit: typeof this.methods.exitFullscreen === 'function',
canDetect: typeof this.methods.fullscreenElement !== 'undefined',
events: !!this.methods.fullscreenchange
};
const allPassed = Object.values(tests).every(Boolean);
return {
supported: this.supported,
tests: tests,
allPassed: allPassed,
vendor: Object.entries(this.vendors).find(([_, v]) => v)?.[0] || 'unknown'
};
}
};
// 用法
console.log('Fullscreen support:', FullscreenSupport.test());
旧版浏览器 Polyfill¶
// 旧版浏览器简单 polyfill
if (!FullscreenSupport.supported) {
console.warn('全屏 API 不支持,使用回退方案');
// 回退方案:使用 CSS zoom/transform 模拟全屏
document.addEventListener('DOMContentLoaded', () => {
// 添加回退样式
const style = document.createElement('style');
style.textContent = `
.dashboard-fullscreen-fallback {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 99999 !important;
background: white !important;
overflow: auto !important;
margin: 0 !important;
padding: 20px !important;
}
.dashboard-fullscreen-fallback #header,
.dashboard-fullscreen-fallback #footer,
.dashboard-fullscreen-fallback .sidebar {
display: none !important;
}
@media (prefers-color-scheme: dark)
}
`;
document.head.appendChild(style);
// 覆盖全屏管理器方法
const originalEnterFullscreen = window.dashboardFullscreenManager?.enterFullscreen;
const originalExitFullscreen = window.dashboardFullscreenManager?.exitFullscreen;
if (window.dashboardFullscreenManager) {
window.dashboardFullscreenManager.enterFullscreen = function() {
console.log('使用回退全屏');
document.documentElement.classList.add('dashboard-fullscreen-fallback');
this.isFullscreen = true;
this.applyFullscreenState();
this.dispatchFullscreenChangeEvent();
};
window.dashboardFullscreenManager.exitFullscreen = function() {
console.log('退出回退全屏');
document.documentElement.classList.remove('dashboard-fullscreen-fallback');
this.isFullscreen = false;
this.applyFullscreenState();
this.dispatchFullscreenChangeEvent();
};
}
});
}
移动浏览器注意事项¶
// 移动设备特定的全屏处理
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
console.log('检测到移动浏览器,调整全屏行为');
// 移动浏览器有不同全屏行为
document.addEventListener('DOMContentLoaded', () => {
// 在移动设备上禁用某些功能
const manager = window.dashboardFullscreenManager;
if (manager) {
// 在移动设备上,强制隐藏可能比全屏更有用
manager.fullscreenModeEnabled = false;
// 添加移动设备特定的控件
const mobileNotice = document.createElement('div');
mobileNotice.className = 'mobile-fullscreen-notice';
mobileNotice.innerHTML = `
<p>📱 检测到移动浏览器</p>
<p>使用"强制隐藏"获得最佳体验</p>
<button class="enable-force-hide">启用强制隐藏</button>
`;
mobileNotice.style.cssText = `
position: fixed;
bottom: 10px;
left: 10px;
right: 10px;
background: rgba(0,0,0,0.9);
color: white;
padding: 15px;
border-radius: 5px;
z-index: 10000;
text-align: center;
`;
document.body.appendChild(mobileNotice);
// 为按钮添加事件监听器
mobileNotice.querySelector('.enable-force-hide').addEventListener('click', () => {
manager.enableForceHide(true);
mobileNotice.style.display = 'none';
});
// 10 秒后自动隐藏通知
setTimeout(() => {
mobileNotice.style.opacity = '0';
setTimeout(() => {
mobileNotice.style.display = 'none';
}, 500);
}, 10000);
}
});
}
使用场景¶
演示模式¶
# 演示模式配置
class PresentationDashboard(Dashboard):
"""为演示优化的数据看板"""
hide_others_in_fullscreen = True
force_hide_others = False
# 演示特定设置
presentation_settings = {
'large_fonts': True,
'high_contrast': True,
'simple_layout': True,
'auto_advance': False,
'show_timer': True,
}
def get_layout(self):
layout = super().get_layout()
# 简化演示布局
if self.presentation_settings['simple_layout']:
layout = self.get_simple_presentation_layout()
# 添加演示控件
if self.presentation_settings['show_timer']:
layout.add_component(self.create_timer_component())
return layout
def get_context_data(self, request):
context = super().get_context_data(request)
# 添加演示上下文
context['presentation_mode'] = True
context['presentation_settings'] = self.presentation_settings
# 生成演示 URL
from urllib.parse import urlencode
context['presentation_url'] = (
request.build_absolute_uri('/admin/') + '?' + urlencode({
'_hide_others_in_fullscreen': 'true',
'_dark_mode_on': 'true',
'_view': 'presentation',
})
)
return context
数据看板¶
# 用于 TV/监视器显示的监控数据看板
class MonitoringDashboard(Dashboard):
"""用于持续监控的数据看板"""
force_hide_others = True # 始终隐藏界面
hide_others_in_fullscreen = False
# 监控设置
monitoring_settings = {
'refresh_interval': 30, # 秒
'show_status': True,
'alert_threshold': 90, # 百分比
'auto_refresh': True,
}
def get_layout(self):
layout = super().get_layout()
# 添加监控特定组件
layout.add_component(self.create_uptime_component())
layout.add_component(self.create_performance_component())
layout.add_component(self.create_alerts_component())
# 设置自动刷新
if self.monitoring_settings['auto_refresh']:
layout.meta_refresh = self.monitoring_settings['refresh_interval']
return layout
def get_context_data(self, request):
context = super().get_context_data(request)
# 添加监控上下文
context['monitoring_mode'] = True
context['refresh_interval'] = self.monitoring_settings['refresh_interval']
# 生成监控 URL
context['monitoring_url'] = self.generate_monitoring_url(request)
return context
def generate_monitoring_url(self, request):
"""为监控显示生成 URL"""
from urllib.parse import urlencode
params = {
'_force_hide_others': 'true',
'_dark_mode_on': 'true',
'_refresh': str(self.monitoring_settings['refresh_interval']),
'_view': 'monitoring',
}
return request.build_absolute_uri('/admin/') + '?' + urlencode(params)
信息亭模式¶
# 公共显示的信息亭模式
class KioskDashboard(Dashboard):
"""用于信息亭/公共显示的数据看板"""
force_hide_others = True
hide_others_in_fullscreen = False
# 信息亭安全设置
kiosk_settings = {
'disable_input': True,
'disable_right_click': True,
'auto_logout': 300, # 5 分钟
'restricted_access': True,
'rotation_interval': 60, # 数据看板轮换间隔(秒)
}
def get_layout(self):
layout = super().get_layout()
# 添加信息亭通知
layout.add_component(self.create_kiosk_notice_component())
# 如果启用,设置轮换
if self.kiosk_settings['rotation_interval'] > 0:
layout.meta_refresh = self.kiosk_settings['rotation_interval']
return layout
def dispatch(self, request, *args, **kwargs):
# 信息亭安全检查
if self.kiosk_settings['restricted_access']:
# 仅允许特定 IP 或网络
allowed_ips = getattr(settings, 'KIOSK_ALLOWED_IPS', []);
client_ip = request.META.get('REMOTE_ADDR')
if allowed_ips and client_ip not in allowed_ips:
from django.http import HttpResponseForbidden
return HttpResponseForbidden("信息亭访问受限")
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, request):
context = super().get_context_data(request)
# 添加用于禁用输入的信息亭 JavaScript
if self.kiosk_settings['disable_input'] or self.kiosk_settings['disable_right_click']:
context['kiosk_js'] = self.get_kiosk_javascript()
# 添加自动注销计时器
if self.kiosk_settings['auto_logout']:
context['auto_logout_seconds'] = self.kiosk_settings['auto_logout']
return context
def get_kiosk_javascript(self):
"""为信息亭模式生成 JavaScript"""
js = [];
if self.kiosk_settings['disable_right_click']:
js.append('''
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
return false;
});
''')
if self.kiosk_settings['disable_input']:
js.append('''
// 禁用退出全屏的键盘快捷键
document.addEventListener('keydown', function(e) {
if (e.key === 'F11' ||
(e.ctrlKey && e.key === 'f') ||
(e.key === 'Escape')) {
e.preventDefault();
return false;
}
});
''')
if self.kiosk_settings['disable_input']:
js.append('''
// Disable keyboard shortcuts for exiting fullscreen
document.addEventListener('keydown', function(e) {
if (e.key === 'F11' ||
(e.ctrlKey && e.key === 'f') ||
(e.key === 'Escape')) {
e.preventDefault();
return false;
}
});
''')
return '\n'.join(js)
故障排除¶
常见问题与解决方案¶
1. 全屏无法工作¶
症状: 浏览器未进入全屏,或元素未隐藏。
解决方案: - Check browser support using FullscreenSupport.测试() -
Ensure 数据看板 has hide_others_in_fullscreen = True or
force_hide_others = True - Check JavaScript console for errors -
Verify URL parameters are correct (case-sensitive)
// Debug fullscreen
console.log('Fullscreen support:', FullscreenSupport.test());
console.log('URL params:', new URLSearchParams(window.location.search).toString());
console.log('Dashboard config:', window.dashboardFullscreenManager);
2. 元素未隐藏¶
症状: URL 参数有效但元素仍然可见。
解决方案: - Check CSS selectors match your Django Admin 主题 - 验证
CSS 已加载(检查 Network 标签页) - 检查 CSS 特异性问题 - 尝试向 CSS
规则添加 !重要
3. 可见元素闪烁 (FOVE)¶
症状: 元素在被隐藏前短暂出现。
解决方案: - 通过内联样式初始隐藏 - 使用不依赖 JavaScript 的 CSS -
在模板中为关键元素添加 显示: none
<style>
/* 如果存在全屏参数,立即隐藏关键元素 */
{% if request.GET._hide_others_in_fullscreen == 'true' or request.GET._force_hide_others == 'true' %}
#header, #footer, .sidebar {
display: none !important;
}
{% endif %}
</style>
4. 浏览器兼容性问题¶
症状: 在一个浏览器中工作,但在另一个中不工作。
解决方案: - 对旧版浏览器使用 polyfill - 检查供应商前缀 - 在不同浏览器中测试
5. 性能问题¶
症状: 全屏过渡缓慢或卡顿。
解决方案: - 减少隐藏的元素数量 - 简化 CSS 选择器 - 使用 CSS transforms 代替布局属性 - 对快速全屏变化进行防抖处理
调试工具¶
浏览器开发工具¶
- Elements Panel: Check for
dashboard-fullscreen-modeanddashboard-force-hide-othersclasses - Console: Check for JavaScript errors from fullscreen manager
- Network Panel: Verify CSS and JS files are loading
- 应用程序 Panel: Check localStorage for saved state
调试 CSS¶
/* 添加到 dashboard.css 进行调试 */
.dashboard-fullscreen-mode::after {
content: '全屏模式激活';
position: fixed;
top: 10px;
left: 10px;
background: red;
color: white;
padding: 5px;
z-index: 9999;
font-size: 12px;
}
.dashboard-force-hide-others::after {
content: '强制隐藏激活';
position: fixed;
top: 10px;
left: 10px;
background: blue;
color: white;
padding: 5px;
z-index: 9999;
font-size: 12px;
}
/* Highlight hidden elements */
#header, #footer, .sidebar {
transition: outline 0.3s ease;
}
.dashboard-fullscreen-mode #header,
.dashboard-fullscreen-mode #footer,
.dashboard-fullscreen-mode .sidebar,
.dashboard-force-hide-others #header,
.dashboard-force-hide-others #footer,
.dashboard-force-hide-others .sidebar {
outline: 3px dashed red !important;
}
JavaScript 调试助手¶
// 添加到浏览器控制台进行调试
window.debugFullscreen = function() {
console.group('全屏调试');
// 检查管理器
const manager = window.dashboardFullscreenManager;
if (manager) {
console.log('Manager state:', {
isFullscreen: manager.isFullscreen,
fullscreenModeEnabled: manager.fullscreenModeEnabled,
forceHideEnabled: manager.forceHideEnabled
});
}
// 检查 DOM 类
console.log('DOM 类 - html:', document.documentElement.className);
console.log('DOM 类 - body:', document.body.className);
// 检查 URL 参数
const params = new URLSearchParams(window.location.search);
console.log('URL parameters:', {
hideInFullscreen: params.get('_hide_others_in_fullscreen'),
forceHide: params.get('_force_hide_others')
});
// 检查元素可见性
const elements = ['#header', '#footer', '.sidebar'];
elements.forEach(selector => {
const el = document.querySelector(selector);
console.log(`${selector}:`, el ? {
exists: true,
display: getComputedStyle(el).display,
visible: el.offsetParent !== null
} : { exists: false });
});
console.groupEnd();
};
获取帮助¶
如果仍有问题:
- 检查文档:查看本指南和 URL 控制文档
- 使用基础设置测试:创建最小测试用例以隔离问题
- 检查浏览器控制台:查找错误消息
- 与示例比较:检查示例在您的环境中是否工作
- 报告问题:包括浏览器版本、Django 版本和重现步骤