【已解决】apex同一工作区中,如何建立一个统一的全局水印?
yidatong1991
17 天之前 (5 天之前更新过)
css水印
如题。水印内容包含用户名和当前时间,斜着铺满整个页面,置于最顶层。避免用户截图传播系统内容。
---
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\. 技术架构

---
## 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\. 设计要点说明

---
## 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日
>
> 适用场景:企业内控、数据防泄漏、操作审计
---
✅ **部署即用,安全可靠,开箱即得专业级水印效果。**
✅ 该话题已被
王方钢 于 2025-11-21 给出答案。
跳到答案