当前位置: 首页 > news >正文

第14讲、Odoo 18 实现一个Markdown Widget模块

目录

  1. 模块概述
  2. 安装与配置
  3. 前端实现详解
  4. 依赖库分析
  5. 使用示例与最佳实践
  6. 技术架构与设计模式分析
  7. 总结

模块概述

模块地址https://apps.odoo.com/apps/modules/18.0/web_widget_markdown

Odoo 18 Markdown Widget 是一个为 Odoo 18.0 社区版开发的全局通用模块,它允许在 Odoo 表单中为 HTML 和 Text 类型字段添加 Markdown 编辑和预览功能。该模块通过自定义字段小部件 bootstrap_markdown 实现,使用户能够以 Markdown 格式编写内容,并实时预览渲染后的 HTML 效果。

模块结构

模块采用标准的 Odoo 插件结构组织,主要包含以下部分:

  1. 模块定义文件

    • __init__.py:模块初始化文件
    • __manifest__.py:模块清单文件,定义模块的基本信息和依赖
  2. 前端资源

    • static/src/js/web_widget_text_markdown.js:核心 JavaScript 实现
    • static/src/css/web_widget_text_markdown.css:样式定义
    • static/src/lib/:依赖库文件目录
  3. 依赖库

    • showdown.js:Markdown 转 HTML 的核心库
    • bootstrap-markdown.js:基于 Bootstrap 的 Markdown 编辑器
    • 其他辅助库:如 showdown-toc.jsshowdown-table.js 等扩展功能

实现细节分析

模块清单文件 (__manifest__.py)

模块清单定义了模块的基本信息、依赖和资产:

{"name": "Widget Text Markdown","version": "18.0","author": "melon, ","description": "Markdown格式插件","category": "Tools","license": 'LGPL-3',"website": "https://hxmelon.com","summary": "Widget adds markdown support","images": ["static/description/img2.png"],"depends": ["web", "base"],"data": [],"installable": True,"auto_install": False,"application": True,"assets": {"web.assets_backend": [# CSS"web_widget_markdown/static/src/css/web_widget_text_markdown.css",# Component"web_widget_markdown/static/src/js/web_widget_text_markdown.js",# Dependencies"/web/static/src/views/fields/text/text_field.js",],},
}

关键点:

  • 模块依赖于 webbase 模块
  • 资产定义使用 Odoo 18 的新资产管理系统
  • 没有定义 data 文件,说明不需要额外的 XML 数据文件
  • 模块被标记为可安装、非自动安装的应用程序
集成与使用方式

根据 README.md 和代码分析,该模块的使用非常简单:

  1. 安装模块
  2. 在 XML 视图定义中,为 HTML 或 Text 类型字段添加 widget="bootstrap_markdown" 属性
  3. 完整示例:<field name="ai_response" widget="bootstrap_markdown"/>

这种简单的集成方式使得该模块可以轻松应用于任何需要 Markdown 编辑功能的场景。

安装与配置

安装要求

在安装 Odoo 18 Markdown Widget 模块前,请确保您的环境满足以下要求:

  1. Odoo 版本:Odoo 18.0 社区版
  2. 依赖模块
    • web:Odoo 的 Web 客户端基础模块
    • base:Odoo 的基础模块

安装步骤

方法一:通过 ZIP 文件安装
  1. 下载模块 ZIP 文件
  2. 登录 Odoo 管理后台
  3. 进入 应用 菜单
  4. 点击顶部的 更新应用列表 按钮
  5. 点击顶部的 安装应用 按钮
  6. 在搜索框中输入 “Markdown”
  7. 找到 “Widget Text Markdown” 模块并点击 安装 按钮
方法二:通过文件系统安装
  1. 解压模块 ZIP 文件
  2. 将解压后的 web_widget_markdown 目录复制到 Odoo 的 addons 目录中
    cp -r web_widget_markdown /path/to/odoo/addons/
    
  3. 重启 Odoo 服务
    service odoo restart
    # 或
    systemctl restart odoo
    
  4. 登录 Odoo 管理后台
  5. 进入 应用 菜单
  6. 点击顶部的 更新应用列表 按钮
  7. 在搜索框中输入 “Markdown”
  8. 找到 “Widget Text Markdown” 模块并点击 安装 按钮

配置与使用

安装完成后,无需额外配置即可使用。该模块是一个全局通用模块,可以在任何表单视图中为 HTML 和 Text 类型的字段添加 Markdown 编辑功能。

基本用法

在 XML 视图定义中,为需要支持 Markdown 的字段添加 widget="bootstrap_markdown" 属性:

<field name="description" widget="bootstrap_markdown"/>
适用字段类型

该小部件支持以下字段类型:

  • text:文本字段
  • html:HTML 字段
示例配置

以下是在一个自定义模块中使用 Markdown 小部件的完整示例:

<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_my_model_form" model="ir.ui.view"><field name="name">my.model.form</field><field name="model">my.model</field><field name="arch" type="xml"><form string="My Model"><sheet><group><field name="name"/><field name="description" widget="bootstrap_markdown"/></group></sheet></form></field></record>
</odoo>

在上面的例子中,description 字段将使用 Markdown 编辑器,允许用户输入 Markdown 格式的内容并实时预览渲染效果。

验证安装

安装完成后,可以通过以下步骤验证模块是否正常工作:

  1. 创建或编辑一个包含 HTML 或 Text 类型字段的表单视图
  2. 为该字段添加 widget="bootstrap_markdown" 属性
  3. 保存视图并刷新页面
  4. 打开表单,检查字段是否显示为 Markdown 编辑器
  5. 尝试输入一些 Markdown 格式的文本(如 # 标题**粗体** 等)
  6. 点击 “预览” 按钮,验证 Markdown 是否正确渲染为 HTML

如果以上步骤都正常工作,则说明模块安装成功并可以正常使用。

前端实现详解

前端架构概述

Odoo 18 Markdown Widget 的前端实现基于 Odoo 18 的 OWL (Odoo Web Library) 框架,采用组件化的开发方式。核心实现位于 web_widget_text_markdown.js 文件中,定义了一个名为 MarkdownField 的 OWL 组件,并将其注册为字段小部件。

OWL 组件实现

组件定义与注册
/** @odoo-module **/import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { Component, markup, xml, useState } from "@odoo/owl";
import { useRecordObserver } from "@web/model/relational_model/utils";
import { Mutex } from "@web/core/utils/concurrency";export class MarkdownField extends Component {// 组件实现...
}// 注册字段组件
export const markdownField = {component: MarkdownField,displayName: _t("Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
};registry.category("fields").add("bootstrap_markdown", markdownField);

这段代码展示了组件的基本结构和注册过程:

  1. 导入必要的 OWL 和 Odoo 框架组件
  2. 定义 MarkdownField 组件类
  3. 创建 markdownField 配置对象,指定支持的字段类型和属性提取方法
  4. 通过 registry.category("fields").add() 将组件注册为名为 “bootstrap_markdown” 的字段小部件
组件模板
static template = xml`<div class="o_field_markdown" t-att-class="{'o_field_readonly': props.readonly}"><div class="d-flex justify-content-end mb-2" t-if="!props.readonly"><button class="btn btn-sm btn-secondary toggle-preview-btn" t-on-click="togglePreview"><t t-esc="state.isPreview ? '编辑' : '预览'"/></button></div><div t-if="state.isPreview || props.readonly" class="markdown-preview border p-2 rounded"><div class="markdown-content" t-out="state.htmlContent"/></div><textarea t-else=""class="o_field_text_markdown_editor o_input" t-ref="input"t-att-name="props.name"t-on-input="onInput"t-on-blur="onBlur"rows="10"t-model="state.value"/></div>
`;

模板定义了组件的 UI 结构:

  1. 外层容器 div.o_field_markdown,根据只读状态添加不同的类
  2. 编辑/预览切换按钮,仅在非只读模式下显示
  3. Markdown 预览区域,在预览模式或只读模式下显示
  4. 文本编辑区域 textarea,在编辑模式下显示

模板使用 OWL 的指令:

  • t-att-class:动态设置类名
  • t-if/t-else:条件渲染
  • t-on-click/t-on-input/t-on-blur:事件绑定
  • t-esc:转义输出文本
  • t-out:非转义输出 HTML
  • t-model:双向数据绑定
组件属性
static props = {...standardFieldProps,placeholder: { type: String, optional: true },
};

组件属性继承了标准字段属性 standardFieldProps,并添加了一个可选的 placeholder 属性。

组件初始化与状态管理
setup() {this.mutex = new Mutex();this.isDirty = false;this.state = useState({isPreview: false,value: this.props.record.data[this.props.name] || "",htmlContent: markup(this.convertToHtml(this.props.record.data[this.props.name] || "")),});useRecordObserver((record) => {if (!this.isDirty) {const newValue = record.data[this.props.name] || "";if (this.lastValue !== newValue) {this.state.value = newValue;this.state.htmlContent = markup(this.convertToHtml(newValue));this.lastValue = newValue;}}});// 监听模型的保存事件const { model } = this.props.record;this.env.bus.addEventListener("WILL_SAVE_URGENTLY", () => this.commitChanges({ urgent: true }));model.bus.addEventListener("NEED_LOCAL_CHANGES", ({ detail }) => {detail.proms.push(this.commitChanges());});
}

setup 方法在组件初始化时执行,完成以下工作:

  1. 创建互斥锁 mutex,用于控制异步操作的顺序
  2. 初始化 isDirty 标志,用于跟踪编辑状态
  3. 使用 useState 创建响应式状态对象,包含:
    • isPreview:是否处于预览模式
    • value:当前文本值
    • htmlContent:转换后的 HTML 内容
  4. 使用 useRecordObserver 监听记录变化,当记录数据更新时更新组件状态
  5. 监听保存事件,确保在保存前提交更改
编辑与预览切换
togglePreview() {if (this.props.readonly) {return;}this.state.isPreview = !this.state.isPreview;if (this.state.isPreview) {this.state.htmlContent = markup(this.convertToHtml(this.state.value));}
}

togglePreview 方法实现编辑和预览模式的切换:

  1. 如果字段是只读的,则不执行任何操作
  2. 切换 isPreview 状态
  3. 如果切换到预览模式,则将当前文本转换为 HTML 并更新 htmlContent
数据同步与提交
onInput(ev) {if (this.props.readonly) {return;}this.state.value = ev.target.value;this.isDirty = true;this.props.record.model.bus.trigger("FIELD_IS_DIRTY", true);
}async onBlur() {return this.commitChanges();
}async commitChanges({ urgent } = {}) {if (urgent) {this._commitChanges({ urgent });} else {return this.mutex.exec(() => this._commitChanges({ urgent }));}
}async _commitChanges({ urgent }) {if (this.isDirty) {this.lastValue = this.state.value;this.isDirty = false;await this.props.record.update({ [this.props.name]: this.state.value }).catch(() => {this.isDirty = true;});this.props.record.model.bus.trigger("FIELD_IS_DIRTY", this.isDirty);}
}

这些方法处理数据同步和提交:

  1. onInput:处理输入事件,更新状态并标记为脏
  2. onBlur:处理失焦事件,提交更改
  3. commitChanges:提交更改的外部接口,使用互斥锁控制并发
  4. _commitChanges:内部提交方法,将更改同步到记录模型
Markdown 转换实现
convertToHtml(text) {if (!text) return "";// 简单的 Markdown 转换规则const html = text// 标题.replace(/^# (.*$)/gm, '<h1>$1</h1>').replace(/^## (.*$)/gm, '<h2>$1</h2>').replace(/^### (.*$)/gm, '<h3>$1</h3>')// 斜体和粗体.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>')// 链接.replace(/\[([^\[]+)\]\(([^\)]+)\)/g, '<a href="$2">$1</a>')// 列表.replace(/^\s*\n\*/gm, '<ul>\n*').replace(/^(\*.+)\s*\n([^\*])/gm, '$1\n</ul>\n\n$2').replace(/^\*(.+)/gm, '<li>$1</li>')// 代码块.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')// 内联代码.replace(/`(.*?)`/g, '<code>$1</code>')// 引用.replace(/^\> (.*$)/gm, '<blockquote>$1</blockquote>');return html;
}

convertToHtml 方法实现了 Markdown 到 HTML 的转换:

  1. 使用一系列正则表达式替换规则
  2. 支持基本的 Markdown 语法,包括标题、粗体、斜体、链接、列表、代码块和引用
  3. 返回转换后的 HTML 字符串

值得注意的是,虽然模块包含了 Showdown.js 等第三方 Markdown 转换库,但实际实现中使用了自定义的转换函数,这可能是为了简化实现或减少依赖。

CSS 样式实现

CSS 样式定义在 web_widget_text_markdown.css 文件中,为编辑器和预览区域提供了样式支持:

编辑器样式
.o_field_text_markdown_editor {width: 100%;min-height: 120px;border: 1px solid #ccc;border-radius: 3px;padding: 8px;font-family: monospace;
}

编辑器样式设置了宽度、最小高度、边框、圆角和内边距,并使用等宽字体以便于编辑 Markdown。

预览区域样式
.markdown-preview {min-height: 100px;background-color: #f8f9fa;
}

预览区域设置了最小高度和背景色,确保内容显示清晰。

Markdown 元素样式
.markdown-content h1 { font-size: 2.5rem; }
.markdown-content h2 { font-size: 2rem; }
.markdown-content h3 { font-size: 1.75rem; }
/* 更多样式... */

为各种 Markdown 元素(标题、列表、代码块等)定义了样式,确保渲染后的 HTML 美观一致。

资产管理与加载

模块使用 Odoo 18 的资产管理系统加载 CSS 和 JavaScript 文件:

"assets": {"web.assets_backend": [# CSS"web_widget_markdown/static/src/css/web_widget_text_markdown.css",# Component"web_widget_markdown/static/src/js/web_widget_text_markdown.js",# Dependencies"/web/static/src/views/fields/text/text_field.js",],
},

这段配置将 CSS 和 JavaScript 文件添加到 Odoo 的后端资产包中,确保它们在后端界面加载时被正确引入。
在这里插入图片描述

前端实现总结

Odoo 18 Markdown Widget 的前端实现采用了现代化的组件开发方式,基于 OWL 框架构建,具有以下特点:

  1. 组件化架构:使用 OWL 组件封装 UI 和逻辑,便于维护和扩展
  2. 响应式状态管理:使用 useState 管理组件状态,实现 UI 与数据的自动同步
  3. 声明式模板:使用 XML 模板声明 UI 结构,提高代码可读性
  4. 事件驱动:通过事件处理用户交互和数据同步
  5. 自定义 Markdown 转换:实现简单高效的 Markdown 到 HTML 转换
  6. 样式隔离:通过 CSS 类名前缀确保样式不会影响其他组件

这种实现方式符合 Odoo 18 的前端开发最佳实践,提供了良好的用户体验和开发者友好性。

依赖库分析

依赖库概述

Odoo 18 Markdown Widget 模块包含了多个第三方库,这些库为 Markdown 编辑和渲染提供了支持。虽然在核心实现中使用了自定义的 Markdown 转换函数,但这些库仍然被包含在模块中,可能是为了提供更丰富的功能或作为备选方案。本章将详细分析这些依赖库的功能、版本和集成方式。

Showdown.js

基本信息
  • 文件路径/static/src/lib/showdown.js
  • 版本:2.0.0-alpha1
  • 官方网站:https://showdownjs.com/
  • 许可证:MIT
功能介绍

Showdown.js 是一个功能强大的 JavaScript Markdown 解析器,可以将 Markdown 文本转换为 HTML。它是 John Gruber 的原始 Markdown 规范的 JavaScript 实现,支持标准 Markdown 语法以及多种扩展。

主要特性
  1. 标准 Markdown 支持:完整支持 John Gruber 的 Markdown 规范
  2. 扩展系统:通过扩展机制支持额外的语法和功能
  3. 配置选项:提供丰富的配置选项,可以自定义转换行为
  4. 浏览器和 Node.js 兼容:可以在浏览器和 Node.js 环境中使用
  5. 双向转换:不仅支持 Markdown 到 HTML 的转换,还支持 HTML 到 Markdown 的转换
在模块中的集成

虽然 Showdown.js 被包含在模块中,但在核心 JavaScript 实现中并没有直接使用它。模块使用了自定义的 convertToHtml 方法进行 Markdown 转换。这可能是出于以下考虑:

  1. 简化实现:自定义转换函数可能更简单,更容易维护
  2. 减少依赖:避免对第三方库的依赖,减少潜在的兼容性问题
  3. 性能优化:针对特定需求优化的转换函数可能比通用库更高效
  4. 备选方案:保留 Showdown.js 作为备选方案,以便在需要更复杂功能时使用

Bootstrap-Markdown.js

基本信息
  • 文件路径/static/src/lib/bootstrap-markdown.js
  • 版本:2.10.0
  • 官方仓库:https://github.com/toopay/bootstrap-markdown
  • 许可证:Apache License 2.0
功能介绍

Bootstrap-Markdown.js 是一个基于 Bootstrap 的 Markdown 编辑器插件,提供了一个用户友好的界面,用于编辑 Markdown 文本。它包含工具栏按钮,用于插入常见的 Markdown 元素,如标题、列表、链接等。

主要特性
  1. 工具栏:提供常用 Markdown 元素的按钮
  2. 实时预览:支持实时预览 Markdown 渲染结果
  3. 快捷键:支持键盘快捷键
  4. 自定义按钮:允许添加自定义工具栏按钮
  5. 事件系统:提供丰富的事件钩子
  6. 国际化:支持多语言界面
在模块中的集成

与 Showdown.js 类似,Bootstrap-Markdown.js 也被包含在模块中,但在核心实现中并未直接使用。模块使用了自定义的 UI 组件,而非 Bootstrap-Markdown.js 提供的编辑器。这可能是因为:

  1. OWL 框架集成:使用 OWL 组件可以更好地集成到 Odoo 的前端框架中
  2. UI 一致性:自定义组件可以保持与 Odoo 界面的一致性
  3. 简化依赖:减少对第三方库的依赖
  4. 备选方案:保留 Bootstrap-Markdown.js 作为备选方案

Showdown 扩展库

基本信息

模块包含了多个 Showdown.js 的扩展库:

  • showdown-toc.js:为 Showdown 添加目录生成功能
  • showdown-table.js:为 Showdown 添加表格支持
  • showdown-footnotes.js:为 Showdown 添加脚注支持
功能介绍

这些扩展库为 Showdown.js 添加了额外的功能,使其能够支持更多的 Markdown 语法和特性:

  1. showdown-toc.js:自动生成文档目录,基于标题层级
  2. showdown-table.js:支持 Markdown 表格语法
  3. showdown-footnotes.js:支持脚注语法
在模块中的集成

这些扩展库与 Showdown.js 一起被包含在模块中,但同样未在核心实现中直接使用。它们可能是为了在未来版本中提供更丰富的功能,或者作为备选方案。

Marked.js

基本信息
  • 文件路径/static/src/lib/marked.js
  • 官方网站:https://marked.js.org/
  • 许可证:MIT
功能介绍

Marked.js 是另一个流行的 JavaScript Markdown 解析器,以其速度和安全性而闻名。它支持标准 Markdown 语法,并提供了丰富的配置选项。

主要特性
  1. 高性能:Marked.js 以其高效的解析速度而著称
  2. 安全性:内置防止 XSS 攻击的安全措施
  3. 扩展性:支持自定义渲染器和扩展
  4. 兼容性:支持多种环境,包括浏览器和 Node.js
在模块中的集成

Marked.js 也被包含在模块中,但未在核心实现中直接使用。它可能是作为 Showdown.js 的替代方案,或者为未来版本提供更多选择。

jQuery

基本信息
  • 文件路径/static/lib/jquery.min.js
  • 官方网站:https://jquery.com/
  • 许可证:MIT
功能介绍

jQuery 是一个快速、小巧、功能丰富的 JavaScript 库,它简化了 HTML 文档遍历、事件处理、动画和 Ajax 交互。

在模块中的集成

jQuery 被包含在模块中,可能是为了支持 Bootstrap-Markdown.js,因为后者依赖于 jQuery。然而,在 Odoo 18 中,前端框架已经不再依赖 jQuery,而是使用原生 JavaScript 和 OWL 框架。

依赖库集成策略分析

通过分析模块中包含的依赖库和实际实现,可以看出模块采用了一种灵活的依赖管理策略:

  1. 自定义实现优先:核心功能使用自定义实现,避免对第三方库的直接依赖
  2. 备选方案保留:保留第三方库作为备选方案,以便在需要时使用
  3. 渐进增强:为未来版本的功能扩展预留空间

这种策略有以下优势:

  1. 减少依赖:降低对外部库的依赖,减少潜在的兼容性问题
  2. 提高可控性:自定义实现更容易控制和维护
  3. 性能优化:针对特定需求优化的实现可能比通用库更高效
  4. 灵活性:保留多种选择,可以根据需要切换实现方式

依赖库版本管理

模块包含的依赖库版本相对较旧,例如:

  • Showdown.js:2.0.0-alpha1
  • Bootstrap-Markdown.js:2.10.0

这可能是因为:

  1. 稳定性:选择经过验证的稳定版本
  2. 兼容性:确保与 Odoo 环境的兼容性
  3. 最小变更:避免因库更新带来的不必要变更

在未来版本中,可以考虑更新这些库到最新版本,以获取新功能和安全修复。

依赖库安全性考虑

包含第三方库时,安全性是一个重要考虑因素。模块中的库可能存在潜在的安全风险,特别是较旧版本的库。建议:

  1. 定期更新:定期检查并更新依赖库到最新版本
  2. 安全审计:对包含的库进行安全审计
  3. 最小化使用:只包含必要的库,减少攻击面

总结

Odoo 18 Markdown Widget 模块包含了多个功能强大的第三方库,为 Markdown 编辑和渲染提供了支持。虽然在当前实现中并未直接使用这些库,但它们为模块提供了扩展性和灵活性,为未来版本的功能增强奠定了基础。模块采用的依赖管理策略平衡了自定义实现和第三方库的优势,提供了一个稳定、可靠的 Markdown 编辑解决方案。

使用示例与最佳实践

基本使用示例

Odoo 18 Markdown Widget 的使用非常简单直观,只需在视图定义中为字段添加 widget="bootstrap_markdown" 属性即可。以下是几个常见场景的使用示例。

在表单视图中使用

以下是在客户模型表单视图中为备注字段添加 Markdown 支持的示例:

<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_partner_form_markdown" model="ir.ui.view"><field name="name">res.partner.form.markdown</field><field name="model">res.partner</field><field name="inherit_id" ref="base.view_partner_form"/><field name="arch" type="xml"><field name="comment" position="attributes"><attribute name="widget">bootstrap_markdown</attribute></field></field></record>
</odoo>

这个示例继承了标准的合作伙伴表单视图,并为 comment 字段添加了 Markdown 小部件。用户现在可以使用 Markdown 格式编辑备注,并在预览模式下查看渲染后的 HTML。

在自定义模型中使用

以下是在自定义模型中定义一个支持 Markdown 的字段的示例:

# models/document.py
from odoo import models, fields, apiclass Document(models.Model):_name = 'custom.document'_description = 'Custom Document'name = fields.Char(string='Title', required=True)content = fields.Text(string='Content')  # 将使用 Markdown 小部件
<!-- views/document_views.xml -->
<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_document_form" model="ir.ui.view"><field name="name">custom.document.form</field><field name="model">custom.document</field><field name="arch" type="xml"><form string="Document"><sheet><group><field name="name"/><field name="content" widget="bootstrap_markdown"/></group></sheet></form></field></record>
</odoo>

在这个示例中,我们创建了一个名为 custom.document 的自定义模型,并为其 content 字段添加了 Markdown 小部件。

在网站内容中使用

如果您想在网站内容中使用 Markdown,可以这样配置:

<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_website_page_form_markdown" model="ir.ui.view"><field name="name">website.page.form.markdown</field><field name="model">website.page</field><field name="inherit_id" ref="website.website_page_form_view"/><field name="arch" type="xml"><field name="content" position="attributes"><attribute name="widget">bootstrap_markdown</attribute></field></field></record>
</odoo>

这个示例为网站页面的内容字段添加了 Markdown 支持,使网站编辑人员可以使用 Markdown 格式编写页面内容。

高级使用场景

与富文本编辑器结合使用

在某些情况下,您可能希望为用户提供选择使用 Markdown 或富文本编辑器的选项。以下是一个实现这种功能的示例:

<?xml version="1.0" encoding="utf-8"?>
<odoo><record id="view_document_form_with_editor_choice" model="ir.ui.view"><field name="name">custom.document.form.editor.choice</field><field name="model">custom.document</field><field name="arch" type="xml"><form string="Document"><sheet><group><field name="name"/><field name="editor_type" widget="radio"/><field name="content" widget="bootstrap_markdown" attrs="{'invisible': [('editor_type', '!=', 'markdown')]}"/><field name="content" widget="html" attrs="{'invisible': [('editor_type', '!=', 'html')]}"/></group></sheet></form></field></record>
</odoo>
# models/document.py
from odoo import models, fields, apiclass Document(models.Model):_name = 'custom.document'_description = 'Custom Document'name = fields.Char(string='Title', required=True)content = fields.Text(string='Content')editor_type = fields.Selection([('markdown', 'Markdown'),('html', 'Rich Text')], string='Editor Type', default='markdown')

在这个示例中,我们添加了一个 editor_type 字段,允许用户选择使用 Markdown 或富文本编辑器。根据用户的选择,显示相应的编辑器。

自定义 Markdown 预览样式

如果您想自定义 Markdown 预览的样式,可以通过继承 CSS 类来实现:

/* /static/src/css/custom_markdown.css */
.markdown-content h1 {color: #3498db;border-bottom: 1px solid #eee;padding-bottom: 10px;
}.markdown-content h2 {color: #2ecc71;margin-top: 20px;
}.markdown-content blockquote {background-color: #f9f9f9;border-left: 5px solid #3498db;padding: 10px 15px;
}.markdown-content code {background-color: #f8f8f8;color: #e74c3c;padding: 2px 4px;border-radius: 3px;
}

然后在模块的 __manifest__.py 中添加这个 CSS 文件:

"assets": {"web.assets_backend": [# 其他资产..."your_module/static/src/css/custom_markdown.css",],
},

这样,您就可以为 Markdown 预览区域应用自定义样式,使其更符合您的应用程序的整体设计。

最佳实践

1. 字段选择

Markdown 小部件最适合用于以下类型的字段:

  • 长文本描述:产品描述、项目说明等
  • 技术文档:帮助文档、指南等
  • 结构化内容:需要标题、列表、表格等格式的内容

不建议用于:

  • 简短的单行文本:名称、标题等
  • 高度格式化的内容:需要复杂布局和样式的内容
  • 需要嵌入复杂元素的内容:如需要嵌入表单、交互元素等
2. 用户培训

虽然 Markdown 语法相对简单,但对于不熟悉的用户可能需要一些指导。考虑提供以下资源:

  • 简短的 Markdown 语法指南
  • 常用格式的示例
  • 预览功能的使用说明
3. 性能考虑

在处理大量 Markdown 内容时,请注意以下性能优化措施:

  • 避免在列表视图中渲染 Markdown 内容
  • 考虑在服务器端预渲染 Markdown 内容
  • 对于只读场景,可以缓存渲染后的 HTML
4. 安全性考虑

Markdown 转换为 HTML 可能带来安全风险,特别是当内容来自不受信任的来源时。建议:

  • 在服务器端验证和清理 Markdown 内容
  • 使用安全的 Markdown 解析器
  • 限制允许的 HTML 标签和属性
5. 与其他功能的集成

Markdown 小部件可以与其他 Odoo 功能集成,提供更丰富的体验:

  • 文件上传:允许在 Markdown 中引用上传的图片
  • 链接自动完成:自动完成对其他记录的链接
  • 版本控制:跟踪 Markdown 内容的变更历史
6. 移动兼容性

确保 Markdown 编辑器在移动设备上也能正常工作:

  • 测试不同屏幕尺寸下的编辑体验
  • 优化移动设备上的工具栏布局
  • 考虑为移动用户提供简化的编辑界面
7. 国际化支持

如果您的应用程序需要支持多种语言,请确保:

  • 编辑器界面元素已翻译
  • Markdown 内容可以包含不同语言的字符
  • 预览正确渲染各种语言的内容

常见问题与解决方案

问题:Markdown 内容在保存后丢失格式

解决方案:确保字段类型为 TextHTML,而不是 CharChar 类型字段长度有限,可能会截断 Markdown 内容。

问题:预览中的样式与最终显示不一致

解决方案:确保 CSS 样式一致应用于预览和最终显示。可能需要调整 .markdown-content 类的样式,使其与最终显示环境匹配。

问题:Markdown 编辑器在某些浏览器中显示异常

解决方案:检查浏览器兼容性,可能需要添加特定的 CSS 修复或 polyfill。确保测试主流浏览器,包括 Chrome、Firefox、Safari 和 Edge。

问题:图片上传和引用困难

解决方案:考虑扩展 Markdown 编辑器,添加图片上传功能,或提供清晰的指导,说明如何在 Markdown 中引用已上传的图片。

问题:Markdown 内容在搜索结果中不可见

解决方案:考虑在搜索视图中添加纯文本版本的字段,或使用 Odoo 的全文搜索功能。

总结

Odoo 18 Markdown Widget 提供了一种简单而强大的方式,为表单中的文本字段添加 Markdown 编辑功能。通过遵循本章介绍的最佳实践和示例,您可以充分利用这个小部件,为用户提供更好的内容编辑体验。无论是用于内部文档、产品描述还是客户沟通,Markdown 都能帮助用户创建格式丰富、结构清晰的内容,同时保持编辑过程的简单直观。

技术架构与设计模式分析

整体架构设计

Odoo 18 Markdown Widget 模块采用了现代化的前端架构设计,充分利用了 Odoo 18 的 OWL 框架和组件化开发模式。本章将从架构设计、设计模式、代码组织和技术选型等方面深入分析该模块的技术实现。

分层架构

模块采用了清晰的分层架构,各层职责明确:

  1. 表现层

    • 用户界面组件,包括编辑器和预览区域
    • CSS 样式定义,确保视觉一致性
  2. 业务逻辑层

    • Markdown 转换逻辑
    • 状态管理和数据同步
  3. 数据层

    • 与 Odoo 模型的交互
    • 数据持久化

这种分层设计使得代码结构清晰,各部分职责明确,便于维护和扩展。

模块化设计

模块采用了高度模块化的设计,主要体现在:

  1. 资源组织

    • JavaScript 文件:实现核心逻辑
    • CSS 文件:定义样式
    • 依赖库:提供扩展功能
  2. 功能分离

    • 编辑功能
    • 预览功能
    • 数据同步

这种模块化设计使得各部分可以独立开发和测试,提高了代码的可维护性和可扩展性。

设计模式分析

组件模式

模块使用 OWL 框架实现了组件模式,将 UI 和逻辑封装在一起:

export class MarkdownField extends Component {static template = xml`...`;static props = {...};setup() {// 组件初始化}// 组件方法
}

组件模式的优势:

  1. 封装性:UI 和逻辑封装在一起,形成独立单元
  2. 可复用性:组件可以在不同场景中复用
  3. 可测试性:组件可以独立测试
  4. 可维护性:修改一个组件不会影响其他组件
观察者模式

模块使用 useRecordObserver 实现了观察者模式,监听记录变化并更新 UI:

useRecordObserver((record) => {if (!this.isDirty) {const newValue = record.data[this.props.name] || "";if (this.lastValue !== newValue) {this.state.value = newValue;this.state.htmlContent = markup(this.convertToHtml(newValue));this.lastValue = newValue;}}
});

观察者模式的优势:

  1. 松耦合:观察者和被观察者之间松散耦合
  2. 事件驱动:基于事件的通信机制
  3. 实时更新:数据变化时自动更新 UI
状态管理模式

模块使用 OWL 的 useState 实现了响应式状态管理:

this.state = useState({isPreview: false,value: this.props.record.data[this.props.name] || "",htmlContent: markup(this.convertToHtml(this.props.record.data[this.props.name] || "")),
});

状态管理模式的优势:

  1. 集中管理:状态集中管理,便于跟踪
  2. 响应式更新:状态变化自动触发 UI 更新
  3. 可预测性:状态变化路径清晰可预测
策略模式

模块在 Markdown 转换实现中采用了策略模式的思想,通过 convertToHtml 方法实现转换策略:

convertToHtml(text) {if (!text) return "";// 转换策略实现const html = text.replace(/^# (.*$)/gm, '<h1>$1</h1>').replace(/^## (.*$)/gm, '<h2>$1</h2>')// 更多转换规则...return html;
}

策略模式的优势:

  1. 算法封装:将算法封装在独立方法中
  2. 可替换性:可以轻松替换为其他转换策略
  3. 扩展性:可以添加新的转换规则
工厂模式

模块在注册字段组件时使用了工厂模式的思想:

export const markdownField = {component: MarkdownField,displayName: _t("Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
};registry.category("fields").add("bootstrap_markdown", markdownField);

工厂模式的优势:

  1. 对象创建抽象:抽象了组件的创建过程
  2. 配置灵活:可以通过配置创建不同的组件实例
  3. 统一接口:提供统一的接口创建对象

技术选型分析

前端框架选择

模块选择了 Odoo 18 的 OWL 框架作为前端开发框架,这是一个明智的选择:

  1. 与 Odoo 生态集成:OWL 是 Odoo 官方前端框架,与 Odoo 生态系统深度集成
  2. 轻量级:OWL 相比其他前端框架更轻量,性能更好
  3. 组件化:支持组件化开发,提高代码复用性
  4. 响应式:内置响应式状态管理
Markdown 转换实现

模块选择了自定义实现 Markdown 转换,而非直接使用第三方库:

  1. 优势

    • 减少依赖
    • 更好的控制性
    • 可能的性能优化
    • 简化实现
  2. 劣势

    • 功能可能不如专业库完整
    • 需要自行维护转换逻辑
    • 可能存在边缘情况处理不当
CSS 样式实现

模块使用了原生 CSS 而非预处理器(如 SCSS 或 LESS):

  1. 优势

    • 简单直接
    • 无需额外编译步骤
    • 与 Odoo 默认样式系统一致
  2. 劣势

    • 缺少变量、嵌套等高级功能
    • 可能导致代码重复
    • 维护性相对较低

代码质量与最佳实践

代码组织

模块的代码组织遵循了 Odoo 的最佳实践:

  1. 目录结构:符合 Odoo 模块标准结构
  2. 文件命名:清晰明确,反映文件用途
  3. 代码分离:JS、CSS 分离,职责明确
命名规范

模块使用了一致的命名规范:

  1. 类名:使用 PascalCase(如 MarkdownField
  2. 方法名:使用 camelCase(如 convertToHtml
  3. CSS 类名:使用带前缀的 kebab-case(如 o_field_markdown
注释与文档

模块代码中包含了适当的注释:

/*** Markdown 文本字段小部件* 支持在表单视图中编辑和显示 Markdown 格式文本*/
export class MarkdownField extends Component {// ...
}/*** 将 Markdown 转换为 HTML* @param {string} text * @returns {string} HTML*/
convertToHtml(text) {// ...
}

这些注释有助于理解代码的用途和功能,提高了代码的可读性和可维护性。

错误处理

模块实现了基本的错误处理机制:

await this.props.record.update({ [this.props.name]: this.state.value }).catch(() => {this.isDirty = true;
});

在数据更新失败时,将 isDirty 标志重置为 true,确保数据不会丢失。

性能优化

渲染优化

模块实现了一些渲染优化措施:

  1. 条件渲染:使用 t-ift-else 条件渲染组件
  2. 懒加载:只在需要时转换 Markdown 为 HTML
  3. 状态缓存:缓存上次值,避免不必要的更新
并发控制

模块使用 Mutex 控制并发操作:

async commitChanges({ urgent } = {}) {if (urgent) {this._commitChanges({ urgent });} else {return this.mutex.exec(() => this._commitChanges({ urgent }));}
}

这确保了多个异步操作按顺序执行,避免了竞态条件。

扩展性分析

扩展点

模块提供了多个扩展点:

  1. 样式扩展:可以通过 CSS 自定义样式
  2. 转换逻辑扩展:可以替换 convertToHtml 方法
  3. UI 扩展:可以修改组件模板
继承与扩展示例

以下是扩展该模块的示例:

// 扩展 MarkdownField 组件
import { MarkdownField } from "web_widget_markdown/static/src/js/web_widget_text_markdown";export class ExtendedMarkdownField extends MarkdownField {// 重写转换方法,使用第三方库convertToHtml(text) {if (!text) return "";return showdown.makeHtml(text);}// 添加新方法exportAsPdf() {// 实现导出为 PDF 的功能}
}// 注册扩展组件
registry.category("fields").add("extended_markdown", {component: ExtendedMarkdownField,displayName: _t("Extended Markdown"),supportedTypes: ["text", "html"],extractProps({ attrs }) {return {placeholder: attrs.placeholder,};},
});

安全性考虑

XSS 防护

模块在处理 Markdown 转 HTML 时需要注意 XSS 攻击风险:

  1. 当前实现:使用简单的正则替换,可能存在安全风险
  2. 改进建议
    • 使用安全的 Markdown 解析库
    • 实现 HTML 清理和过滤
    • 限制允许的 HTML 标签和属性
输入验证

模块目前没有实现输入验证,可能导致安全风险或数据问题:

  1. 当前实现:直接处理用户输入
  2. 改进建议
    • 添加输入验证
    • 限制输入长度
    • 过滤危险字符

未来改进方向

功能增强
  1. 工具栏:添加 Markdown 编辑工具栏,提高用户友好性
  2. 图片上传:集成图片上传功能
  3. 实时协作:支持多用户实时编辑
  4. 版本历史:跟踪内容变更历史
技术改进
  1. 使用专业 Markdown 库:替换自定义转换逻辑,使用成熟的 Markdown 库
  2. 性能优化:优化大文档的渲染性能
  3. 测试覆盖:添加单元测试和集成测试
  4. 国际化:完善多语言支持
架构优化
  1. 组件拆分:将大组件拆分为更小的子组件
  2. 状态管理优化:使用更结构化的状态管理
  3. 插件系统:实现插件系统,支持功能扩展

总结

Odoo 18 Markdown Widget 模块采用了现代化的前端架构和设计模式,实现了一个简洁而功能完备的 Markdown 编辑器。通过组件化设计、响应式状态管理和清晰的代码组织,该模块展示了 Odoo 18 前端开发的最佳实践。

虽然在某些方面还有改进空间,如使用专业 Markdown 库、增强安全性和添加更多功能,但总体而言,该模块提供了一个稳定、可靠的 Markdown 编辑解决方案,可以满足大多数业务场景的需求。

通过本章的分析,我们不仅了解了该模块的技术实现细节,还探讨了其架构设计思想和未来发展方向,为开发者提供了全面的技术参考。

相关文章:

  • Java 面试中的数据库设计深度解析
  • C++读写锁以及实现方式
  • QtConcurrent run中抛出异常在QFutureWatcher传递原理
  • Lighttpd CGI配置:404错误排查实录
  • 对选择基于模型编程(MBD)的工作对职业发展影响的讨论 (2025)
  • 数据库安全性
  • Python训练打卡Day39
  • cursor升级至0.505,运行统计视频中的人数
  • GIS数据类型综合解析
  • NodeJS全栈开发面试题讲解——P3数据库(MySQL / MongoDB / Redis)
  • 6级翻译学习
  • 计算机视觉---YOLOv5
  • Python打卡训练营Day42
  • 深入理解短链服务:原理、设计与实现全解析
  • 鸿蒙OSUniApp结合机器学习打造智能图像分类应用:HarmonyOS实践指南#三方框架 #Uniapp
  • ERP系统中商品定价功能设计:支持渠道、会员与批发场景的灵活定价机制
  • 如何用利用deepseek的API能力来搭建属于自己的智能体-优雅草卓伊凡
  • 【无标题】安富莱V5程序移植到原子探索者F4控制板带TFT LCD显示屏
  • 进程信号简述
  • 6.01打卡
  • 营销网站的方法/seo这个职位是干什么的
  • 阿里云一个域名做两个网站/宁德seo
  • 中国建设银网站/免费发布网站seo外链
  • 网站空间报价单/专业seo站长工具全面查询网站
  • 哪些网站可以做付费视频/2023年10月疫情还会严重吗
  • 扬州 网站建设/百度账号快速注册