odoo 17系统水印功能,让数据安全透明!


Odoo 17系统水印功能,让数据安全透明!

在 Odoo 开发中,Owl 框架为我们提供了强大的组件化开发能力。今天,我们就来深入解析一个基于 Owl 的水印组件开发案例,看看它是如何实现的。

一、代码解析

(一)组件定义

class Watermark extends Component {
    static template = xml`
        <div class="watermark_container">
            <t t-foreach="watermarks" t-as="wm" t-key="wm.id">
                <div class="watermark" t-att-style="wm.style">
                    <div><t t-esc="state.name"/> </div>
                    <div><t t-esc="state.currentTime"/></div>
                </div>
            </t>
        </div>
    `;
  • 这里定义了一个名为 Watermark 的 Owl 组件。
  • 使用了 xml 模板语法来构建组件的 HTML 结构。
  • t-foreach 指令用于循环渲染 watermarks 数组中的每个水印对象。
  • t-att-style 动态绑定每个水印的样式,由 wm.style 提供。
  • t-esc 用于输出 state.namestate.currentTime 的值,分别显示用户名和当前时间。

(二)组件状态初始化与数据获取

setup() {
    this.orm = useService("orm");
    this.state = useState({
        name: "加载中...",
        currentTime: this.getFormattedTime(),
    });
    this.watermarks = useState(this.generateWatermarks());
  • setup 方法是组件的初始化方法。
  • 使用 useService 获取了 orm 服务,虽然在这段代码中并未使用到 orm,但可能是为后续扩展预留。
  • 使用 useState 初始化了两个状态:
    • state:包含用户名和当前时间。
    • watermarks:包含水印的布局信息。
  • state.name 初始值为 "加载中...",state.currentTime 初始值通过调用 this.getFormattedTime() 获取当前时间。

(三)时间更新与窗口大小变化监听

setInterval(() => {
    this.state.currentTime = this.getFormattedTime();
}, 60000);
  • 使用 setInterval 每分钟更新一次 state.currentTime,调用 this.getFormattedTime() 方法获取格式化的时间。
onMounted(() => {
    window.addEventListener("resize", () => {
        this.watermarks.splice(0, this.watermarks.length, ...this.generateWatermarks());
    });
});
  • 在组件挂载后,监听窗口大小变化事件。
  • 当窗口大小发生变化时,调用 this.generateWatermarks() 方法重新生成水印布局,并更新 watermarks 状态。

(四)用户信息获取

onMounted(async () => {
    try {
        const userId = session.uid;
        console.log("当前用户 ID:", userId);

        const userName = session.name;
        console.log("当前用户名:", userName);

        this.state.name = userName;
    } catch (error) {
        console.error("❌ 获取用户数据失败:", error);
        this.state.name = "错误";
    }
});
  • 在组件挂载后,异步获取当前用户的信息。
  • session 中获取用户 ID 和用户名。
  • 如果获取成功,将用户名赋值给 state.name;如果失败,捕获错误并设置 state.name 为 "错误"。

(五)时间格式化方法

getFormattedTime() {
    const now = new Date();
    return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
}
  • 这是一个辅助方法,用于获取格式化的时间字符串。
  • 使用 Date 对象获取当前日期和时间,然后按照 "年月日 时:分" 的格式拼接字符串。
  • 使用 padStart 方法确保小时和分钟始终为两位数字。

(六)水印布局生成方法

generateWatermarks() {
    const watermarks = [];
    const screenWidth = window.innerWidth;
    const screenHeight = window.innerHeight;
    const spacingX = 200; // 水平间距
    const spacingY = 150; // 垂直间距

    const cols = Math.ceil(screenWidth / spacingX);
    const rows = Math.ceil(screenHeight / spacingY);

    for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
            const x = col * spacingX + (row % 2) * (spacingX / 2);
            const y = row * spacingY;

            watermarks.push({
                id: `${row}-${col}`,
                style: `
                    position: absolute;
                    left: ${x}px;
                    top: ${y}px;
                    transform: rotate(-30deg);
                    transform-origin: center;
                `,
            });
        }
    }
    return watermarks;
}
  • 这个方法用于生成水印的布局信息。
  • 根据窗口的宽度和高度,以及设定的水平和垂直间距,计算出需要的行列数。
  • 使用嵌套循环生成每个水印的位置和样式。
  • 水印采用交错排列的方式,通过 (row % 2) * (spacingX / 2) 实现。
  • 每个水印的样式包括绝对定位、位置坐标、旋转角度等。

(七)组件挂载到 WebClient

patch(WebClient.prototype, {
    async setup() {
        await super.setup(...arguments);

        const container = document.createElement("div");
        container.classList.add("o_watermark_container");

        const root = document.querySelector(".o_web_client");
        if (root) {
            root.appendChild(container);

            await mount(Watermark, container, {
                props: {},
                env: this.env,
            });

            console.log("✅ 水印已挂载。");
        } else {
            console.warn("⚠️ 未找到 .o_web_client 元素");
        }
    },
});
  • 使用 patch 方法扩展了 WebClient
  • WebClientsetup 方法中,创建了一个容器元素,并将其添加到 .o_web_client 元素中。
  • 使用 mount 方法将 Watermark 组件挂载到容器中。

(八)样式添加

const style = document.createElement("style");
style.textContent = `
.o_watermark_container {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 9999;
}

.watermark {
    opacity: 0.3;
    color: #333;
    font-size: 14px;
    padding: 5px 10px;
    border-radius: 3px;
    white-space: nowrap;
}

.watermark > div {
    line-height: 1.5;
}
`;
document.head.appendChild(style);
  • 动态创建了一个 <style> 标签,并添加到页面的 <head> 中。
  • 定义了水印容器和水印的样式,包括透明度、颜色、字体大小、内边距、圆角等。

二、可优化项

(一)性能优化

  • 减少 DOM 操作:在窗口大小变化时,直接更新水印的位置和样式,而不是重新生成整个水印数组并替换。可以通过直接修改每个水印元素的样式属性来实现,这样可以减少 DOM 操作的开销。
  • 优化时间更新逻辑:目前每分钟更新一次时间,但如果用户长时间停留在页面上,可能会导致时间显示不够精确。可以考虑每秒更新一次时间,但为了避免频繁更新 DOM,可以只在时间的分钟部分发生变化时才更新 DOM。

(二)功能增强

  • 支持自定义水印内容:目前水印内容固定为用户名和当前时间,可以扩展为支持自定义水印内容,例如通过配置项或从后端获取水印模板。
  • 水印样式动态配置:将水印的样式(如字体大小、颜色、透明度、旋转角度等)提取为可配置项,方便用户根据需求进行调整。

(三)完整代码

/** @odoo-module **/

import { Component, useState, onMounted, mount, xml } from "@odoo/owl";
import { patch } from "@web/core/utils/patch";
import { WebClient } from "@web/webclient/webclient";
import { session } from "@web/session";
import { useService } from "@web/core/utils/hooks";

// 水印组件
class Watermark extends Component {
    static template = xml`
        <div class="watermark_container">
            <t t-foreach="watermarks" t-as="wm" t-key="wm.id">
                <div class="watermark" t-att-style="wm.style">
                    <div><t t-esc="state.name"/> </div>
                    <div><t t-esc="state.currentTime"/></div>
                </div>
            </t>
        </div>
    `;

    setup() {
        this.orm = useService("orm");
        this.state = useState({
            name: "加载中...",
            currentTime: this.getFormattedTime(),
        });
        this.watermarks = useState(this.generateWatermarks());

        // 每分钟更新时间
        setInterval(() => {
            this.state.currentTime = this.getFormattedTime();
        }, 60000);

        // 监听窗口大小变化,重新生成水印
        onMounted(() => {
            window.addEventListener("resize", () => {
                this.watermarks.splice(0, this.watermarks.length, ...this.generateWatermarks());
            });
        });

        onMounted(async () => {
            try {
                // 获取当前用户 ID
                const userId = session.uid;
                console.log("当前用户 ID:", userId);

                // 获取其他用户信息(如用户名)
                const userName = session.name;
                console.log("当前用户名:", userName);

                // 设置用户的名称
                this.state.name = userName ;
            } catch (error) {
                console.error("❌ 获取用户数据失败:", error);
                this.state.name = "错误";
            }
        });
    }

    // 获取格式化的当前时间(年月日 时:分)
    getFormattedTime() {
        const now = new Date();
        return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日 ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
    }

    // 生成水印位置和样式
    generateWatermarks() {
        const watermarks = [];
        const screenWidth = window.innerWidth;
        const screenHeight = window.innerHeight;
        const spacingX = 200; // 水平间距
        const spacingY = 150; // 垂直间距

        // 计算需要的行列数
        const cols = Math.ceil(screenWidth / spacingX);
        const rows = Math.ceil(screenHeight / spacingY);

        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
                const x = col * spacingX + (row % 2) * (spacingX / 2); // 交错排列
                const y = row * spacingY;

                watermarks.push({
                    id: `${row}-${col}`,
                    style: `
                        position: absolute;
                        left: ${x}px;
                        top: ${y}px;
                        transform: rotate(-30deg);
                        transform-origin: center;
                    `,
                });
            }
        }
        return watermarks;
    }
}

// 扩展 WebClient
patch(WebClient.prototype, {
    async setup() {
        await super.setup(...arguments);

        const container = document.createElement("div");
        container.classList.add("o_watermark_container");

        const root = document.querySelector(".o_web_client");
        if (root) {
            root.appendChild(container);

            await mount(Watermark, container, {
                props: {},
                env: this.env,
            });

            console.log("✅ 水印已挂载。");
        } else {
            console.warn("⚠️ 未找到 .o_web_client 元素");
        }
    },
});

// 添加样式
const style = document.createElement("style");
style.textContent = `
.o_watermark_container {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 9999;
}

.watermark {
    opacity: 0.3;
    color: #333;
    font-size: 14px;
    padding: 5px 10px;
    border-radius: 3px;
    white-space: nowrap;
}

.watermark > div {
    line-height: 1.5;
}
`;
document.head.appendChild(style);
技术
odoo 17系统水印功能,让数据安全透明!
花好月圆 2025年5月9日
我们的博客
存档
登录 留下评论
半小时入门 python
power by kimi