You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Odoo 18中<video>标签poster缩略图不显示,悬停懒加载播放逻辑失效问题排查

Odoo 18中

我帮你梳理下这个问题的可能原因和解决办法,你遇到的情况在Odoo里很常见,主要是几个细节没处理到位:

一、首先排查最容易忽略的错误:海报URL生成逻辑不对

你在静态HTML里用的海报URL是https://storage.googleapis.com/simstar-videos/50001593/still.jpg,对应的视频是https://storage.googleapis.com/simstar-videos/50001593/3D.mp4。但你Python代码里的替换逻辑是把.mp4换成/still.jpg,这样生成的海报URL会变成https://storage.googleapis.com/simstar-videos/50001593/3D/still.jpg——这和正确的路径完全不符,自然加载不出海报,显示空白!

修复后的海报URL生成逻辑:
我们需要把视频URL里的文件名去掉,再拼接still.jpg,而不是直接替换后缀:

from urllib.parse import urlparse, urlunparse

def build_video_tag(url, label, width="180px", height="180px", extra_class=""):
    if url:
        # 解析URL结构,分离出路径部分
        parsed_url = urlparse(url)
        path_segments = parsed_url.path.split('/')
        # 移除最后一段的视频文件名(比如3D.mp4)
        path_segments.pop()
        # 拼接海报路径
        poster_path = '/'.join(path_segments) + '/still.jpg'
        poster_url = urlunparse(parsed_url._replace(path=poster_path))
        
        # 同时把外层容器的class改成video-container,对应后面JS的选择器
        return f"""
        <div class="video-container {extra_class}" style="position:relative; display:inline-block; margin:5px;">
            <video data-src="{url}" poster="{poster_url}" muted playsinline preload="none" loop class="media-video" 
                style="width: {width}; height: {height}; object-fit: cover; border: 1px solid #ccc; border-radius: 5px;">
            </video>
        </div>
        """
    return ""

二、Odoo的HTML安全过滤可能拦截了属性

Odoo默认会对渲染的HTML进行 sanitize 处理,可能会移除data-src这类自定义属性,或者某些内联样式,这会直接导致你的懒加载逻辑失效。

解决办法分两种场景:

  1. 如果是通过Python控制器直接返回HTML:
    要在返回Response时关闭sanitize,避免属性被过滤:
    from odoo import http
    from odoo.http import make_response
    
    class MyDashboardController(http.Controller):
        @http.route('/my/custom/dashboard', type='http', auth='user', website=True)
        def get_dashboard(self):
            # 生成视频HTML
            video_html = self.build_video_tag("https://storage.googleapis.com/simstar-videos/50001593/3D.mp4", "Demo Video")
            # 用make_response返回,明确关闭sanitize
            return make_response(f"""<div class="dashboard">{video_html}</div>""", 
                                headers=[('Content-Type', 'text/html')], 
                                sanitize=False)
    
  2. 如果是在QWeb模板中使用这个HTML:
    需要在模板里添加t-options="{'sanitize': False}"来禁用过滤:
    <t t-esc="build_video_tag(url, label)" t-options="{'sanitize': False}"/>
    

三、JS事件绑定的时机和选择器不匹配

你原来的JS代码里用video.closest('.video-container')找外层容器,但Python生成的是video-wrapper类,导致找不到容器,后续的模糊、缩放逻辑报错,甚至可能影响视频播放。另外,直接用MutationObserver的初始化方式在Odoo里可能时机不对,应该用Odoo的组件体系来管理。

修复后的JS代码:
用Odoo的publicWidget来绑定事件,确保在页面加载完成后初始化,并且正确监听DOM变化:

odoo.define('my_module.video_lazy_loader', function (require) {
    "use strict";

    var publicWidget = require('web.public.widget');

    publicWidget.registry.videoLazyLoader = publicWidget.Widget.extend({
        start: function () {
            this._bindAllVideos();
            // 监听DOM新增元素,绑定新加载的视频
            const observer = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    if (mutation.addedNodes.length) {
                        this._bindAllVideos();
                    }
                });
            });
            observer.observe(document.body, { childList: true, subtree: true });
            return this._super.apply(this, arguments);
        },

        _bindAllVideos: function () {
            // 只绑定未处理过的视频
            document.querySelectorAll('video.media-video:not([data-bound="1"])').forEach(video => {
                video.dataset.bound = "1";
                
                video.addEventListener('mouseenter', () => {
                    const container = video.closest('.video-container');
                    const allContainers = document.querySelectorAll('.video-container');
                    
                    // 加载并播放视频
                    if (!video.src && video.dataset.src) {
                        video.src = video.dataset.src;
                        video.load();
                    }
                    video.play().catch(err => console.error("视频播放失败:", err));
                    
                    // 调整当前视频样式
                    video.style.transform = 'scale(1.8)';
                    video.style.zIndex = '1000';
                    video.style.position = 'relative';
                    
                    // 模糊其他视频容器
                    if (container) {
                        allContainers.forEach(c => {
                            if (c !== container) c.classList.add('blur');
                        });
                    }
                });

                video.addEventListener('mouseleave', () => {
                    const container = video.closest('.video-container');
                    const allContainers = document.querySelectorAll('.video-container');
                    
                    // 重置视频状态
                    video.pause();
                    video.currentTime = 0;
                    
                    // 恢复样式
                    video.style.transform = 'scale(1)';
                    video.style.zIndex = '';
                    video.style.position = '';
                    
                    // 移除其他容器的模糊效果
                    if (container) {
                        allContainers.forEach(c => c.classList.remove('blur'));
                    }
                });
            });
        }
    });

    return publicWidget.registry.videoLazyLoader;
});

四、别忘了加模糊效果的CSS

最后,在你的模块CSS文件里补充blur类的样式:

.video-container {
    transition: all 0.3s ease;
}
.video-container.blur {
    filter: blur(4px);
    opacity: 0.7;
}

验证步骤

  1. 打开页面后,右键检查元素,确认video标签的poster和data-src属性正确存在;
  2. 打开浏览器控制台,查看是否有404错误(海报/视频)或JS报错;
  3. 测试鼠标hover,看视频是否正常加载播放,其他容器是否模糊。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:13:09