如题。水印内容包含用户名和当前时间,斜着铺满整个页面,置于最顶层。避免用户截图传播系统内容。 --- 20251123更新 还可以进一步把JavaScript也放到工作区文件中,这样配置统一水印,只需要: 1、在应用用户属性中引用JavaScript文件url和css文件url; 2、在应用Page0(全局页)增加水印容器的静态区域。 还可以再进一步,复制共享组件的标准页模板(Standard),在正文(body)中加入水印容易,并引用JavaScript文件url和css文件url,再将该页模板设置为默认模板。这样也可以一劳永逸的使所有使用该模板的页打上水印。 JavaScript: ```js /** * Oracle APEX 全局动态水印脚本(公共版) * * 功能说明: * - 在页面上平铺显示包含用户名与实时时间的半透明水印 * - 自动适配不同屏幕分辨率 * - 每30秒更新一次时间戳 * - 自动隐藏模态对话框(iframe)中的水印,避免重复叠加 * - 支持指定用户豁免显示水印(如管理员、高管等) * * 使用前提: * 1. 页面中必须存在一个 ID 为 'app-watermark' 的 <div> 容器 * 示例:<div class="app-watermark" id="app-watermark"></div> * 2. 已通过 CSS 定义 .app-watermark 样式(建议作为静态文件全局引用) * 3. 本脚本需在 Page 0(Global Page)中加载执行 * * 部署方式: * - 将本文件上传至 APEX 工作区的 "Static Workspace Files" * - 在每个应用的 Page 0 中动态加载此脚本(见调用示例) * * 注意事项: * - 豁免名单基于前端判断,仅用于 UI 层面隐藏,不替代权限控制 * - 时间刷新频率为 30 秒,平衡性能与审计需求 * - 用户名区分大小写,请确保与 APEX 的 APP_USER 值一致(通常为大写) */ (function () { 'use strict'; // ======================================================================== // 1. 【配置区】—— 可根据实际需求调整以下参数 // ======================================================================== /** * 豁免水印显示的用户名列表(区分大小写) * 说明:这些用户登录后将完全看不到水印 * 建议:使用大写,与 APEX 内部存储的 APP_USER 保持一致 */ const EXEMPT_USERS = ["admin1", "admin2", "admin3"]; /** * 水印刷新间隔(毫秒) * 默认:30_000 = 30秒 * 注意:值越小越“实时”,但会增加浏览器负担;30秒已足够满足审计溯源需求 */ const REFRESH_INTERVAL_MS = 30_000; /** * 水印单元尺寸参考(单位:像素) * 说明:每个“水印单元”指一行用户名+时间文本所占的视觉空间 * 调整建议: * - unitWidth:影响水平方向密度(值越大,列数越少) * - unitHeight:影响垂直方向密度(值越大,行数越少) * 当前值经 2560x1440 屏幕调试验证,适用于大多数场景 */ const UNIT_WIDTH = 220; // 水平方向每单元建议宽度 const UNIT_HEIGHT = 90; // 垂直方向每单元建议高度 // ======================================================================== // 2. 【获取当前用户】—— 利用 APEX 替换变量 // ======================================================================== // 从水印容器的 data-user 属性获取真实用户名 const watermarkEl = document.getElementById('app-watermark'); if (!watermarkEl) { console.warn('[Watermark] 水印容器 #app-watermark 未找到,脚本终止'); return; } const currentUser = watermarkEl.getAttribute('data-user'); if (!currentUser) { console.error('[Watermark] 未获取到当前用户名(data-user 为空)'); return; } // ======================================================================== // 3. 【豁免判断】—— 若用户在豁免名单中,直接退出并隐藏水印 // ======================================================================== if (EXEMPT_USERS.includes(currentUser)) { // 查找水印容器 const wm = document.getElementById('app-watermark'); if (wm) { // 隐藏元素并清空内容(双重保险) wm.style.display = 'none'; wm.textContent = ''; } // 终止后续逻辑执行 return; } // ======================================================================== // 4. 【模态对话框检测】—— 防止 iframe 内重复显示水印 // ======================================================================== /** * 判断当前页面是否运行在 iframe 中 * 原理:APEX 的模态对话框(Modal Dialog)通常以 iframe 方式嵌入 * 如果 window.self !== window.top,说明当前是子窗口(即模态内容页) * 此时应隐藏水印,避免与父页面水印叠加 */ if (window.self !== window.top) { const wm = document.getElementById('app-watermark'); if (wm) { wm.style.display = 'none'; wm.textContent = ''; } return; } // ======================================================================== // 5. 【水印生成函数】—— 动态构建平铺文本网格 // ======================================================================== /** * 更新水印内容 * 功能: * - 生成当前时间戳(格式:YYYY-MM-DD HH:mm:ss) * - 结合用户名形成单行文本 * - 根据屏幕尺寸动态计算所需行列数 * - 构建多行多列的平铺文本 * - 注入到水印容器中 */ function updateWatermark() { // --- 生成带前导零的时间字符串 --- const now = new Date(); const timestamp = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + " " + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0"); // --- 构建单行水印文本 --- const textLine = currentUser + " · " + timestamp; // --- 动态计算行列数 --- // 列数 = 屏幕宽度 / 单元宽度 + 1(冗余列,防止旋转后右侧露白) let cols = Math.ceil(window.innerWidth / UNIT_WIDTH) + 1; // 行数 = 屏幕高度 / 单元高度 + 8(较大冗余,确保底部覆盖,经测试+8效果最佳) let rows = Math.ceil(window.innerHeight / UNIT_HEIGHT) + 8; // --- 设置合理边界,防止极端值 --- cols = Math.max(4, Math.min(cols, 20)); // 最少4列,最多20列 rows = Math.max(6, Math.min(rows, 35)); // 最少6行,最多35行 // --- 生成完整水印文本网格 --- // 每行:cols 个 textLine,用8个空格分隔(模拟列间距) const spacedLine = Array(cols).fill(textLine).join(" "); // 全文:rows 行 spacedLine,用换行符连接 const fullText = Array(rows).fill(spacedLine).join("\n"); // --- 注入 DOM --- const el = document.getElementById("app-watermark"); if (el) { el.textContent = fullText; el.style.display = 'block'; // 确保显示(可能之前被隐藏) } } // ======================================================================== // 6. 【初始化与事件绑定】 // ======================================================================== // 首次执行水印渲染 updateWatermark(); // 启动定时器,定期更新时间戳 setInterval(updateWatermark, REFRESH_INTERVAL_MS); // 监听窗口大小变化,重新计算水印布局(提升响应式体验) window.addEventListener('resize', updateWatermark); })(); ``` CSS: ```css .app-watermark { position: fixed; top: -40%; left: -40%; width: 180%; height: 180%; z-index: 9999; pointer-events: none; user-select: none; color: rgba(0, 0, 0, 0.035); font-size: 20px; font-weight: 700; line-height: 4.8; /* 控制行距 */ letter-spacing: 3px; /* 控制列间距(每个字符之间加宽)*/ white-space: pre; /* 保留换行和空格 */ padding: 60px 0 0 50px; transform: rotate(-30deg); transform-origin: center; overflow: hidden; } ``` HTML:(如果作为静态区域放page0,模板选用无或 Blank with Attributes) ```html <div class="app-watermark" id="app-watermark" data-user="&APP_USER." style="display:none;"></div> ``` --- 20251121更新 感谢王老师指导,根据具体需要,对方案进行了细化,完善了相关细节。 ## 1\. 方案概述 本方案实现在 \*\*Oracle APEX 应用中全局显示动态平铺水印\*\*,水印内容包含当前登录用户与实时时间戳,以低透明度、旋转方式覆盖整个页面,用于审计追踪与防截图溯源。 方案支持: * 自适应不同屏幕分辨率 * 自动屏蔽模态对话框(避免重复叠加) * 每 30 秒更新时间(平衡实时性与性能) * 跨应用复用(通过静态文件共享) --- ## 2\. 技术架构 ![](https://cn-oracle-apex.oss-cn-shanghai.aliyuncs.com/file_storage/20251121_ysQ27pr0.png) --- ## 3\. 实施步骤 ### 3.1 创建 CSS 静态文件 1. 编写以下 CSS 内容,保存为 `app-watermark.css`: ```css /* 全局水印基础样式 */ .app-watermark { position: fixed; top: -40%; left: -40%; width: 180%; height: 180%; z-index: 9999; pointer-events: none; user-select: none; color: rgba(0, 0, 0, 0.03); font-size: 20px; font-weight: 700; line-height: 4.8; letter-spacing: 3px; white-space: pre; padding: 60px 0 0 50px; transform: rotate(-30deg); transform-origin: center; overflow: hidden; } /* 可选:打印时隐藏水印 */ @media print { .app-watermark { display: none !important; } } ``` 1. 登录 APEX 工作区 → **Shared Components** → **Static Workspace Files** 2. 点击 \*\***Create**\*\*,上传 `app-watermark.css` > 💡 文件将存储在工作区级别,可被同一工作区下所有应用引用。 --- ### 3.2 在每个应用中配置全局引用 对\*\*每一个需要水印的应用\*\*执行以下操作: #### 步骤 1:引用 CSS 文件 * 进入应用 → **Shared Components** → **User Interface Attributes** * 找到 **CSS File URLs** 字段 * 填入:(具体查看**Static Workspace Files 页面**) > ✅ 此路径会自动解析为当前应用的静态文件 URL。 #### 步骤 2:在 Page 0 添加水印容器 * 进入 **Page 0 (Global Page)** * 在适当位置(如“After Header”或“Before Footer”)添加一个 **Region** * Region Type: **Static Content** * Template: \*\*No Template\*\*(或 Blank with Attributes) * Source: #### 步骤 3:添加 JavaScript 动态操作 * 在 Page 0 的 **Execute when Page Loads** 或新增 **Dynamic Action (Page Load)** 中,添加以下 JavaScript: ```js (function () { // ========== 新增:豁免用户列表 ========== const EXEMPT_USERS = ["ADMIN", "AUDITOR", "CEO"]; // ← 在此添加豁免用户名(大写) const currentUser = "&APP_USER."; // APEX 自动替换为当前用户名 // 如果当前用户在豁免名单中,直接退出 if (EXEMPT_USERS.includes(currentUser)) { var wm = document.getElementById('app-watermark'); if (wm) { wm.style.display = 'none'; wm.textContent = ''; } return; } // ========== 原有逻辑:屏蔽模态对话框 ========== if (window.self !== window.top) { var wm = document.getElementById('app-watermark'); if (wm) { wm.style.display = 'none'; wm.textContent = ''; } return; } // ========== 原有水印生成逻辑 ========== function updateWatermark() { const user = currentUser; // 使用已获取的用户名 const now = new Date(); const timestamp = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + " " + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0"); const textLine = user + " · " + timestamp; const unitWidth = 220; const unitHeight = 90; let cols = Math.ceil(window.innerWidth / unitWidth) + 1; let rows = Math.ceil(window.innerHeight / unitHeight) + 8; cols = Math.max(4, Math.min(cols, 20)); rows = Math.max(6, Math.min(rows, 50)); const spacedLine = Array(cols).fill(textLine).join(" "); const fullText = Array(rows).fill(spacedLine).join("\n"); const el = document.getElementById("app-watermark"); if (el) { el.textContent = fullText; el.style.display = 'block'; } } updateWatermark(); setInterval(updateWatermark, 30_000); window.addEventListener('resize', updateWatermark); })(); ``` --- ## 4\. 多应用复用说明 ✅ \*\*只需一次上传 CSS 文件\*\*(工作区级静态文件) ✅ **每个应用独立配置 Page 0 和 CSS 引用** ✅ \*\*无需修改 JS 逻辑\*\*,天然支持跨应用 > 📌 注意:`&APP_USER.` 是 APEX 内置替换变量,会自动解析为当前登录用户名。 --- ## 5\. 设计要点说明 ![](https://cn-oracle-apex.oss-cn-shanghai.aliyuncs.com/file_storage/20251121_clW7Aq6y.png) --- ## 6\. 维护与扩展建议 * **调整密度**:修改 `unitWidth` / `unitHeight` 即可改变水印疏密 * **更换颜色**:修改 `color` 值(如 `rgba(255,0,0,0.05)` 红色水印) * **关闭水印**:临时注释 Page 0 的 JS 或移除 CSS 引用 * **增强安全**:可加入 `session ID` 或 `IP 地址`(需后端支持) --- ## 7\. 兼容性 * **APEX 版本**:19.2 及以上(推荐 21+) * **浏览器**:Chrome, Edge, Firefox, Safari 最新版 * **设备**:桌面、平板、笔记本(移动端因屏幕小自动降密度) --- > 文档版本:1.0 > > 最后更新:2025年11月21日 > > 适用场景:企业内控、数据防泄漏、操作审计 --- ✅ **部署即用,安全可靠,开箱即得专业级水印效果。**