跳到主要内容

BinaryField解析(Odoo16)

BinaryField
/** @odoo-module **/

import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { isBinarySize, toBase64Length } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import { standardFieldProps } from "../standard_field_props";
import { FileUploader } from "../file_handler";
import { _lt } from "@web/core/l10n/translation";

import { Component, onWillUpdateProps, useState } from "@odoo/owl";

export const MAX_FILENAME_SIZE_BYTES = 0xFF; // filenames do not exceed 255 bytes on Linux/Windows/MacOS

export class BinaryField extends Component {
setup() {
this.notification = useService("notification");
this.state = useState({
fileName: this.props.record.data[this.props.fileNameField] || "",
});
onWillUpdateProps((nextProps) => {
this.state.fileName = nextProps.record.data[nextProps.fileNameField] || "";
});
}

get fileName() {
return (this.state.fileName || this.props.value || "").slice(0, toBase64Length(MAX_FILENAME_SIZE_BYTES));
}

update({ data, name }) {
this.state.fileName = name || "";
const { fileNameField, record } = this.props;
const changes = { [this.props.name]: data || false };
if (fileNameField in record.fields && record.data[fileNameField] !== name) {
changes[fileNameField] = name || false;
}
return this.props.record.update(changes);
}

async onFileDownload() {
await download({
data: {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename_field: this.fileName,
filename: this.fileName || "",
download: true,
data: isBinarySize(this.props.value) ? null : this.props.value,
},
url: "/web/content",
});
}
}

BinaryField.template = "web.BinaryField";
BinaryField.components = {
FileUploader,
};
BinaryField.props = {
...standardFieldProps,
acceptedFileExtensions: { type: String, optional: true },
fileNameField: { type: String, optional: true },
};
BinaryField.defaultProps = {
acceptedFileExtensions: "*",
};

BinaryField.displayName = _lt("File");
BinaryField.supportedTypes = ["binary"];

BinaryField.extractProps = ({ attrs }) => {
return {
acceptedFileExtensions: attrs.options.accepted_file_extensions,
fileNameField: attrs.filename,
};
};

registry.category("fields").add("binary", BinaryField);


导入依赖

各依赖作用:

  • registry: Odoo注册表,用于注册组件
  • useService: 获取系统服务的钩子
  • isBinarySize, toBase64Length: 二进制数据处理工具
  • download: 文件下载功能
  • standardFieldProps: 标准字段属性定义
  • FileUploader: 文件上传组件
  • _lt: 国际化翻译函数
  • Component, onWillUpdateProps, useState: OWL框架核心组件和钩子

BinaryField Component

Function

  • setup()

    初始化组件状态和服务。

    各部分功能:

    • this.notification: 获取通知服务
    • this.state: 管理组件状态,包含文件名
    • onWillUpdateProps: 监听props变化,同步更新文件名状态
  • get fileName()

    计算属性:获取截断后的文件名字符串(使用toBase64Length转换确保base64编码后的长度符合要求,最大字节数:MAX_FILENAME_SIZE_BYTES = 0xFF: 允许的最大文件名大小为255字节,兼容主流操作系统。)

  • update({data, name})

    参数:

    • data: 文件内容
    • name: 文件名

    处理文件更新。功能详解

    1. 状态更新: 更新本地文件名状态
    2. 数据准备: 构建要更新的数据对象
    3. 字段检查: 检查文件名字段是否存在且需要更新
    4. 记录更新: 调用记录的update方法提交变更
  • onFileDownload()

    处理文件下载。 使用download服务发起下载请求。(路由:/web/content

组件属性

  • template:

    模板的xml_id: "web.BinaryField"

  • components:

    FileUploader: 详情见FileUploader组件分析

  • props:

    ...standardFieldProps: 标准字段props

    acceptedFileExtensions: 支持的文件类型/扩展名( default: "*" )。通过<field/>标签内的options.accepted_file_extensions设置。

    fileNameField: 存储文件名的field字段名。通过<field/>标签内的filename属性设置。

  • supportedTypes

    支持的字段类型: binary

registry

将组件注册到fields中,field_widget name: binary

BinaryField template

模板结构

  1. 可编辑模式(非只读时):

    • 当有值时: (Component: FileUploader)

      • 显示下载按钮(记录已保存且未修改时显示): t-on-click=onFileDownload
      • 在只读输入框中显示文件名, 编辑(铅笔)按钮,<slot: 'toggler'>作为FileUploader的toggler插槽内容
      • 清除(垃圾桶)按钮: t-on-click="() => this.update({})"
    • 当无值时: (Component: FileUploader)

      • <slot: 'toggler'> 显示带有"上传文件"标签的上传按钮
  2. 只读模式:

    • 显示带文件名的下载链接(如果记录存在且有值): t-on-click.prevent="onFileDownload"

.prevent 用于阻止事件的默认行为,等同于 event.preventDefault()。


核心逻辑

文件处理

  • 上传: 通过核心上传组件FileUploader处理。
  • 下载: 使用带适当参数的Odoo /web/content端点
  • 更新: 原子性地处理文件内容和文件名更新
  • 清除: 允许移除当前文件

状态管理

  • 使用Owl的useState独立跟踪文件名
  • 通过onWillUpdateProps在属性变更时更新文件名

安全考虑

  • 数据验证: 文件大小和类型限制, 文件名长度截断至255字节以防止文件系统问题
  • 权限控制: 只读模式下禁用编辑功能
  • 数据处理: 安全的base64编码处理,通过Odoo的二进制工具正确处理二进制数据

组件交互流程

文件上传流程

  1. 用户点击: 点击上传按钮或编辑按钮
  2. 文件选择: 打开文件选择对话框
  3. 文件验证: FileUploader验证文件大小和类型
  4. 数据处理: 转换文件为base64格式
  5. 回调执行: 调用BinaryField的update方法
  6. 状态更新: 更新文件名和数据
  7. 界面刷新: 重新渲染显示新文件

文件下载流程

  1. 用户点击: 点击下载按钮或文件名链接
  2. 参数构建: 构建下载请求参数
  3. 服务调用: 调用下载服务
  4. 文件传输: 浏览器开始下载文件

文件清除流程

  1. 用户点击: 点击清除按钮
  2. 数据清空: 调用update方法传入空对象
  3. 状态重置: 清空文件名和数据
  4. 界面更新: 切换到无文件状态

扩展建议

保证原有核心处理:上传、下载、更新、清除

在上述基础上可对一些用户操作进行优化。

例如:

  • 上传:拖拽文件上传、从剪贴板复制、从URL上传、从网盘上传等等。
  • 文件上传前的处理:图片上传可以支持再编辑(裁剪等)。