办公桌

Frappe 框架自带一个功能丰富的管理界面,称为 Desk。它会读取 DocType 的元数据,
并自动为你的 DocType 构建列表视图、表单视图、报表视图等。
Desk 供类型为“系统用户”的用户使用。

在本节中,我们将讨论 Desk 提供了哪些视图以及如何配置它们。

  • 工作区
  • 快捷导航栏
  • 列表视图
  • 表单视图
  • 表格视图
  • 报表生成器
  • 树形视图
  • 日历视图
  • 甘特图视图
  • 看板视图
  • Desk 主题

工作区

登录后,你会看到 Desk,它带有一个固定的侧边栏,其中包含基于应用模块的一些标准项目。
每个侧边栏项目都链接到一个称为工作区的页面。

关于工作区 →

快捷导航栏

快捷导航栏可帮助你在系统中任意导航、创建新记录、搜索文档,
甚至执行数学运算。


使用快捷导航栏导航待办事项

列表视图

除子表和单文档类型外,所有 DocType 都会生成列表视图。

列表视图功能丰富,其中一些功能包括:

  1. 筛选
  2. 排序
  3. 分页
  4. 按标签筛选
  5. 切换到报表、日历、甘特图、看板等视图

了解更多关于列表 API 的信息。

表单视图

表单视图用于以表单布局查看记录。此视图包含很多功能,
但其主要目的是查看和编辑记录。
文档可以分配给其他用户或与其共享,并且可以包含任意附件和标签,
所有这些都可以在表单侧边栏中看到。


表单视图

当您滚动到表单底部时,会看到表单时间线。
表单时间线按时间倒序显示邮件、评论、编辑和其他事件。


表单时间线

了解更多关于表单 API 的信息。

表格视图

表格视图在表单视图中用作表格,用于插入多条记录。
用户可以在表单中配置表格视图的列。

报表生成器

报表生成器是一个通用工具,用于从 DocType 自定义和构建表格数据。
你可以选择要显示的列、要应用的筛选器、排序顺序,并通过为报表命名来保存此配置。
你还可以显示子表数据,并根据子记录筛选文档。
你还可以对某一列应用分组依据,并使用计数、求和和平均值等聚合方法。


报表生成器功能

树形视图

Frappe 还支持使用嵌套集模型存储树形结构记录。
如果某个 DocType 被配置为树形结构,则可以在树形视图中查看它。


树形视图

日历视图

可以为包含开始日期和结束日期的 DocType 配置日历视图。


日历视图

配置文件应命名为 {doctype}_calendar.js,并且应位于
doctype 目录中。

以下是 Event DocType 的日历视图配置示例,必须设置在 event_calendar.js 文件中。

frappe.views.calendar['Event'] = {
 field_map: {
 start: 'starts_on',
 end: 'ends_on',
 id: 'name',
 allDay: 'all_day',
 title: 'subject',
 status: 'event_type',
 color: 'color'
 },
 style_map: {
 Public: 'success',
 Private: 'info'
 },
 order_by: 'ends_on',
 get_events_method: 'frappe.desk.doctype.event.event.get_events'
}

甘特图视图

甘特图视图使用与日历相同的配置文件,因此每个具有日历视图的 DocType 也具有甘特图视图。

如果需要为 Event DocType 的甘特图视图覆盖某些设置(例如 order_by 字段),可以在 event_calendar.js 文件中设置配置,内容如下。

frappe.views.calendar['Event'] = {
 field_map: {
 start: 'starts_on',
 end: 'ends_on',
 id: 'name',
 allDay: 'all_day',
 title: 'subject',
 status: 'event_type',
 color: 'color'
 },
 gantt: { // The values set here will override the values set in the object just for Gantt View
 order_by: 'starts_on',
 }
 style_map: {
 Public: 'success',
 Private: 'info'
 },
 order_by: 'starts_on',
 get_events_method: 'frappe.desk.doctype.event.event.get_events'
}


甘特图视图

看板视图

可以为任何具有带选项的选择字段的 DocType 创建看板视图。
这些选项将成为看板板的列名。

深色主题

Frappe 框架对深色主题提供了一流支持。要切换主题,请点击屏幕右上角的头像,然后点击“切换主题”。点击“切换主题”后,你将看到以下弹窗,可以轻松在可用主题之间切换。要切换到深色主题,请选择“Timeless Night”。

注意:你也可以按 CTRL + SHIFT + G 打开此弹窗。

表单巡回

Frappe 提供了一种简单的方法,只需很少的配置,就能为你的复杂 DocType 生成表单教程。

创建表单教程

要创建表单教程,请在 Awesomebar 中输入“new form tour”并按回车键。

  1. 输入标题。例如:“创建自定义字段”
  2. 选择参考 DocType。
  3. 添加步骤以定义每个字段。
  4. 保存文档。

一个用于解释如何创建自定义字段的教程

配置选项

表单教程

  1. 是否标准:用于创建将存储为 JSON 的标准表单教程。只能在开发者模式开启时设置。
  2. 完成时保存:如果勾选,表单教程的最后一步将提示用户保存文档。
  3. 显示首个文档教程:如果你想展示现有文档的教程而不是新表单的教程,请启用此选项。顾名思义,它会针对该 DocType 创建的第一个文档进行教程演示。
  4. 包含名称字段:对于某些 DocType,名称由用户设置。启用此选项后,名称字段将成为教程的第一步。

表单教程步骤

  1. 字段:从所选 DocType 中选择一个字段。该字段将高亮显示,并带有标题和描述。
  2. 标题 和 描述:用于描述该字段的用途、影响以及其他隐藏的关联逻辑。
  3. 位置:高亮提示框的位置由该字段决定。根据高亮字段的位置,有多个选项可供选择。
  4. 下一步条件:一个代码字段,期望输入适用于该文档的有效 JS 条件。例如,对于任务 DocType 教程,我们可以通过设置如下下一步条件,来检查在进入下一步之前是否已设置任务优先级:

eval: doc.priority != ""
5. 是否为表字段:如果要高亮的字段位于子表内,请勾选此项。
6. 父字段:从所选 DocType 中选择的表字段。仅在勾选 是否为表字段 时可见。允许用户选择子表字段。

触发教程

完成表单及其字段的描述后,你现在就可以通过表单 API 来触发教程了。你只需使用适当的 tour_name 初始化教程,然后使用 frm.tour.start() 直接启动教程即可。

frappe.ui.form.on('Your DocType', {
    onload: function(frm) {
        const tour_name = 'Your Form Tour Name';
        frm.tour.init({ tour_name }).then(() => frm.tour.start());
    }
});

日志记录

日志记录是一种跟踪软件运行时所发生事件的方式。在 bench 架构下,由于多租户的存在,快速追踪和消除各种不确定性可能会变得非常复杂。您可能希望将事件连同相关的、可变的上下文数据一起记录下来。

Frappe 实现了 Python 的日志模块,以维护 bench 和站点级别的日志。Version 13 使用日志轮转来保留最近的 20 个日志文件,以及当前正在运行的每个 100kB 大小的 开箱即用 日志文件。

frappe.log_level

维护 Frappe 进程的日志级别。要了解更多关于日志级别的信息,您可以查看 Python 的文档。

frappe.utils.logger.set_log_level

frappe.utils.logger.set_log_level(level) 可用于设置日志级别并动态重新生成日志记录器。

frappe.loggers

frappe.loggers 维护您进程中活动日志记录器的 dict。键是日志记录器的名称,通常是“{模块}-{站点}”,值则保存着 Logger 实例。

如果 docs.erpnext.com 和 frappeframework.com 是 bench 上的站点,那么一个 Web 工作进程可能拥有类似以下的 frappe.loggers。

{
    "frappe.web-docs.erpnext.com": <logger frappe.web-docs.erpnext.com="">,
    "frappe.web-frappeframework.com": <logger frappe.web-frappeframework.com="">
}
</logger></logger>

frappe.logger

frappe.logger(module, with_more_info, allow_site, filter, max_size, file_count)

返回一个具有站点和 Bench 级别日志记录功能的 logging.Logger 对象。如果日志记录器不存在,则会创建并更新 frappe.loggers。

参数:

  • module:您的日志记录器及其对应日志文件的名称。
  • with_more_info:将把 Form Dict 作为附加信息记录下来,通常对请求很有用。
  • allow_site:传递站点名称以明确记录在其日志下。如果为 True 且未指定,则会猜测日志应保存在哪个站点下。
  • filter:为您的日志记录器添加一个过滤函数。
  • max_size:每个日志文件的最大大小(以字节为单位)。
  • file_count:通过日志轮转保留的最大日志文件数量。

用法

frappe.logger("frappe.web").debug({
    "site": "frappeframework.com",
    "remote_addr": "192.148.1.7",
    "base_url": "https://frappeframework.com/docs/v14/user/en/api/logging",
    "full_path": "/docs/v14/user/en/api/logging",
    "method": "POST",
    "scheme": "https",
    "http_status_code": 200
})
2020-07-31 16:06:55,067 DEBUG frappe.web {'site': 'frappeframework.com', 'remote_addr': '192.148.1.7', 'base_url': 'https://frappeframework.com/docs/v14/user/en/api/logging', 'full_path': '/docs/v14/user/en/api/logging', 'method': 'POST', 'scheme': 'https', 'http_status_code': 200}

上述条目将记录在 ./logs/frappe.web.log 和 ./sites/frappeframework.com/logs/frappe.web.log 文件中。

用法如 app.py#L102-L110 中所实现。

示例

考虑一个场景:您编写了一个 API,用于更新用户发送的值所对应的计数器,并返回更新后的值。现在,您希望在该 API 中记录信息,以确保其按预期工作。因此,您创建了一个日志记录器 api 来跟踪相关事件。

frappe.utils.logger.set_log_level("DEBUG")
logger = frappe.logger("api", allow_site=True, file_count=50)

@frappe.whitelist()
def update(value):
    user = frappe.session.user
    logger.info(f"{user} accessed counter_app.update with value={value}")

    current_value = frappe.get_single_value("Value", "counter")
    updated_value = current_value + value
    logger.debug(f"{current_value} + {value} = {updated_value}")
    frappe.db.set_value("Value", "Value", "counter", updated_value)
    logger.info(f"{user} updated value to {value}")

    return updated_value

现在,对该端点的 API 调用将开始记录在您的 api.log 中,如下所示:

2020-07-31 16:06:55,067 INFO api [email protected] accessed counter_app.update with value 100
2020-07-31 16:06:55,067 DEBUG api 1000 + 100 = 1100
2020-07-31 16:06:55,068 INFO api [email protected] updated value to 1100

在此处了解更多关于 Frappe 日志记录的信息

服务器调用(AJAX)

frappe.call

frappe.call(method, args)

向服务器发起一个 AJAX 请求,其中 method 是一个点分路径,指向一个
已列入白名单的 Python 方法,该方法会被执行,其返回值将作为
响应发送回来。

// call with no parameters
frappe.call('ping')
 .then(r => {
 console.log(r)
 // {message: "pong"}
 })

// call with a single parameter
frappe.call('frappe.core.doctype.user.user.get_role_profile', {
 role_profile: 'Test'
}).then(r => {
 console.log(r.message)
})

// call with all options
frappe.call({
 method: 'frappe.core.doctype.user.user.get_role_profile',
 args: {
 role_profile: 'Test'
 },
 // disable the button until the request is completed
 btn: $('.primary-action'),
 // freeze the screen until the request is completed
 freeze: true,
 callback: (r) => {
 // on success
 },
 error: (r) => {
 // on error
 }
})

frappe.db.get_doc

frappe.db.get_doc(doctype, name, filters)

返回 doctype 和 name 对应的文档对象。如果未提供 name,
则获取由 filters 匹配到的第一个文档。

// get doc by name
frappe.db.get_doc('Task', 'TASK00002')
 .then(doc => {
 console.log(doc)
 })

// get doc by filters
frappe.db.get_doc('Task', null, { status: 'Open' })
 .then(doc => {
 console.log(doc)
 })

frappe.db.get_list

frappe.db.get_list(doctype, { fields, filters })

返回 doctype 的记录列表,并应用 fields 和 filters 条件。

frappe.db.get_list('Task', {
 fields: ['subject', 'description'],
 filters: {
 status: 'Open'
 }
}).then(records => {
 console.log(records);
})

frappe.db.get_value

frappe.db.get_value(doctype, name, fieldname)

返回文档的字段值或值列表。

// single value
frappe.db.get_value('Task', 'TASK00004', 'status')
 .then(r => {
 console.log(r.message.status) // Open
 })

// multiple values
frappe.db.get_value('Task', 'TASK00004', ['status', 'subject'])
 .then(r => {
 let values = r.message;
 console.log(values.status, values.subject)
 })

// using filters
frappe.db.get_value('Task', {status: 'Open'}, 'subject')
 .then(r => {
 let values = r.message;
 console.log(values.subject)
 })

frappe.db.get_single_value

frappe.db.get_single_value(doctype, field)

从单一文档类型(Single DocType)中返回一个字段值。

frappe.db.get_single_value('System Settings', 'time_zone')
 .then(time_zone => {
 console.log(time_zone);
 })

frappe.db.set_value

frappe.db.set_value(doctype, docname, fieldname, value, callback)

在服务器端使用 frappe.get_doc 和 doc.save 设置文档的属性。

// update a field's value
frappe.db.set_value('Task', 'TASK00004', 'status', 'Open')
 .then(r => {
 let doc = r.message;
 console.log(doc);
 })

// update multiple fields
frappe.db.set_value('Task', 'TASK00004', {
 status: 'Working',
 priority: 'Medium'
}).then(r => {
 let doc = r.message;
 console.log(doc);
})

frappe.db.insert

frappe.db.insert(doc)

插入一个新文档。

frappe.db.insert({
 doctype: 'Task',
 subject: 'New Task'
}).then(doc => {
 console.log(doc);
})

frappe.db.count

frappe.db.count(doctype, filters)

返回给定 doctype 和 filters 条件下的记录数量。

// total number of Task records
frappe.db.count('Task')
 .then(count => {
 console.log(count)
 })

// total number of Open Tasks
frappe.db.count('Task', {
 filters: {
 status: 'Open'
 }
}).then(count => {
 console.log(count);
})

frappe.db.delete_doc

frappe.db.delete_doc(doctype, name)

删除由 doctype 和 name 标识的文档。

frappe.db.delete_doc('Task', 'TASK00004')

frappe.db.exists

frappe.db.exists(doctype, name)

如果文档记录存在,则返回 true。

frappe.db.exists('Task', 'TASK00004')
 .then(exists => {
 console.log(exists) // true
 })

扫描仪接口

Frappe 使用开源库 Html5-QRCode,通过设备摄像头提供一种灵活的方式来处理条形码、二维码等输入。

frappe.ui.Scanner

new frappe.ui.Scanner({ ... })

创建一个新的 Scanner 实例,可用于扫描单个或多个条形码。

选项:

  • container:用于显示摄像头视频流的 DOM 元素。
  • dialog:如果设置为 true,将打开一个对话框来显示摄像头视频流。
  • multiple:如果设置为 false,将在成功扫描一次后停止扫描。
  • on_scan:用于处理扫描输入的回调方法。

以下是一个示例客户端代码,用于扫描单个条形码并将其记录到控制台。

new frappe.ui.Scanner({
 dialog: true, // open camera scanner in a dialog
 multiple: false, // stop after scanning one value
 on_scan(data) {
 console.log(data.decodedText);
 }
});

以下代码可用于持续扫描并处理扫描输入。

const scanner = new frappe.ui.Scanner({
 dialog: true, // open camera scanner in a dialog
 multiple: true, // stop after scanning one value
 on_scan(data) {
 handle_scanned_barcode(data.decodedText);
 }
});

要停止扫描,您可以关闭对话框或使用 scanner.stop_scan();

图表API

Frappe 提供了易于使用且完全可配置的 SVG 图表。您可以在 Frappe Chart 的文档中了解它们。

frappe.ui.RealtimeChart

new frappe.ui.RealtimeChart(dom_element, event_name, max_label_count, data)

创建一个新的 RealtimeChart 实例,在 Frappe Chart API 的基础上添加实时数据更新功能。

dom_element:用于渲染图表的 HTML 元素。
event_name:提供数据流的 Socket 事件。
max_label_count:x 轴上允许的最大标签数量。
data:用于初始化图表的数据。

// Empty data array
const data = {
 datasets: [
 {
 name: "Some Data",
 values: [],
 },
 ],
};

// Realtime Chart initialization
let chart = new frappe.ui.RealtimeChart("#chart", "test_event", 8, {
 title: "My Realtime Chart",
 data: data,
 type: "line",
 height: 250,
 colors: ["#7cd6fd", "#743ee2"]
});

以下是一段示例客户端代码,用于在指定的 socket 事件上渲染图表。

以下 Python 代码可以使用 Hook 功能作为定时任务执行。

data = {
 'label': 1,
 'points': [10]
}
frappe.publish_realtime('test_event', data)

label 键指定要添加到图表中的标签。points 键指定要绘制的数据点数组。points 数组中的值的数量取决于数据集的数量。

这将生成一个如下所示的图表

frappe.ui.RealtimeChart.start_updating

frappe.ui.RealtimeChart.start_updating()

开始监听指定的 socket 事件,并相应地更新 RealtimeChart。

frappe.ui.RealtimeChart.start_updating();


frappe.ui.RealtimeChart.start_updating

frappe.ui.RealtimeChart.stop_updating

frappe.ui.RealtimeChart.stop_updating()

停止监听 RealtimeChart 初始化时使用的 socket 事件。

frappe.ui.RealtimeChart.stop_updating();

frappe.ui.RealtimeChart.update_chart

frappe.ui.update_chart(label, data)

通过将标签和相关数据追加到图表末尾来手动更新 RealtimeChart。

frappe.ui.update_chart(2, [30]);

对话框API

Frappe 提供了一组标准、交互式且灵活的对话框,它们易于配置和使用。同时也有一个针对 Python 的 API。

frappe.ui.Dialog

new frappe.ui.Dialog({ title, fields, primary_action })

创建一个新的 Dialog 实例。

let d = new frappe.ui.Dialog({
    title: 'Enter details',
    fields: [
        {
            label: 'First Name',
            fieldname: 'first_name',
            fieldtype: 'Data'
        },
        {
            label: 'Last Name',
            fieldname: 'last_name',
            fieldtype: 'Data'
        },
        {
            label: 'Age',
            fieldname: 'age',
            fieldtype: 'Int'
        }
    ],
    size: 'small', // small, large, extra-large 
    primary_action_label: 'Submit',
    primary_action(values) {
        console.log(values);
        d.hide();
    }
});

d.show();

frappe.ui.Dialog

frappe.msgprint

frappe.msgprint(message) 或 frappe.msgprint({ title, message, indicator })

在模态框中显示 message。

// only message
frappe.msgprint(__('Document updated successfully'));

// with options
frappe.msgprint({
    title: __('Notification'),
    indicator: 'green',
    message: __('Document updated successfully')
});

frappe.msgprint

你也可以通过在 primary_action 中传递 action(作为一个方法)来为此对话框绑定一个主要操作。或者,primary_action 可以包含 server_action 或 client_action。

server_action 和 client_action 是指向相应方法的点分路径,这些方法将在点击主要按钮时执行。

// with primary action
 frappe.msgprint({
    title: __('Notification'),
    message: __('Are you sure you want to proceed?'),
    primary_action:{
        action(values) {
            console.log(values);
        }
    }
});

// with server and client action
frappe.msgprint({
    title: __('Notification'),
    message: __('Are you sure you want to proceed?'),
    primary_action: {
    'label': 'Proceed',
    // either one of the actions can be passed
    'server_action': 'dotted.path.to.method',
    'client_action': 'dotted_path.to_method',
    'args': args
    }
});

绑定了主要操作的 frappe.msgprint

frappe.throw

frappe.throw(error_message)

在模态框中显示 error_message 并抛出 throw 异常。

frappe.throw(__('This is an Error Message'))

frappe.throw

frappe.prompt

frappe.prompt(label) 或 frappe.prompt(df) 或 frappe.prompt(fields)

提示用户输入一个值或一组值。

// prompt for single value of type Data
frappe.prompt('First Name', ({ value }) => console.log(value))

// Set title and button label
frappe.prompt('First Name', console.log, 'Enter First Name', 'Submit');

// prompt for single value of any type
frappe.prompt({
    label: 'Birth Date',
    fieldname: 'date',
    fieldtype: 'Date'
}, (values) => {
    console.log(values.date);
})

// prompt for multiple values
frappe.prompt([
    {
        label: 'First Name',
        fieldname: 'first_name',
        fieldtype: 'Data'
    },
    {
        label: 'Last Name',
        fieldname: 'last_name',
        fieldtype: 'Data'
    },
], (values) => {
    console.log(values.first_name, values.last_name);
})

frappe.prompt

frappe.confirm

frappe.confirm(message, if_yes, if_no)

显示一个确认模态框,如果确认则执行 if_yes,否则执行 if_no。

frappe.confirm('Are you sure you want to proceed?',
    () => {
        // action to perform if Yes is selected
    }, () => {
        // action to perform if No is selected
    })

frappe.confirm

frappe.warn

frappe.warn(title, message_html, proceed_action, primary_label, is_minimizable)

显示一个警告模态框,如果确认则执行 proceed_actiion。它可以设置为 minimizable,这允许对话框被最小化。

frappe.warn('Are you sure you want to proceed?',
    'There are unsaved changes on this page',
    () => {
        // action to perform if Continue is selected
    },
    'Continue',
    true // Sets dialog as minimizable
)

frappe.confirm

frappe.showalert

frappe.show_alert(message, seconds) 或 frappe.show_alert({message, indicator}, seconds)

警报对话框用于显示非阻塞性的消息。

其参数包括 message,它也可以包含指示器颜色,以及它的显示时长。默认是 7 秒。

frappe.show_alert('Hi, you have a new message', 5);

//show_alert with indicator
frappe.show_alert({
    message:__('Hi, you have a new message'),
    indicator:'green'
}, 5);

frappe.showalert

frappe.showprogress

frappe.show_progress(title, count, total, description)

显示一个进度条,其中 count(作为当前进度)和 total(作为最大进度值)。

frappe.show_progress('Loading..', 70, 100, 'Please wait');

frappe.showprogress

frappe.newdoc

frappe.new_doc(doctype, route_options, init_callback)

打开指定 DocType 的新表单,允许编辑和保存它。如果为该 DocType 启用了“快速录入”(允许输入最重要的字段),则会显示“快速录入”弹出窗口。否则,你将被重定向到通常的文档录入表单。

例如,让我们创建一个新的 任务:

frappe.new_doc("Task");

通常,当你在用户界面中创建新文档时,你可能希望根据触发创建的用户交互来初始化其某些字段。另外两个参数可用于此类初始化。

具体来说,route_options 参数是一种快速便捷的方式,用于设置新文档中任何类型为 Link、Select、Data 或 Dynamic Link 的字段。其值应为一个对象,对象的键是所需的字段名,值则是初始值。

frappe.new_doc("Task", {subject: "New Task"});

如果你需要对无法通过 route_options 完成的新文档进行任何其他初始化,init_callback 可以让你完全控制。它应该是一个带一个参数的函数。如果 doctype 使用“快速录入”表单初始化,则在控制权交还给用户之前,会使用“快速录入”对话框对象调用该回调。否则,在允许用户在标准表单中编辑新文档之前,会使用新文档调用该回调。

frappe.new_doc("Task", {subject: "New Task"},
                doc => {doc.description = "Do what's necessary";});

请注意,subject 是一个类型为“Data”的字段,因此我们可以利用 route_options 参数来设置它。description 是一个类型为“Text Editor”的字段,所以如果我们想初始化它,必须在回调中完成。

举一个稍微复杂一点的例子,下面这个调用创建了一个类型为“Bank Entry”的新 日记账分录,并填充了交易的一方:

frappe.new_doc("Journal Entry", {"voucher_type": "Bank Entry"}, doc => {
    doc.posting_date = frappe.datetime.get_today();
    let row = frappe.model.add_child(doc, "accounts");
    row.account = 'Bank - A';
    row.account_currency = 'USD';
    row.debit_in_account_currency = 100.0;
    row.credit_in_account_currency = 0.0;
});

frappe.ui.form.MultiSelectDialog

new frappe.ui.form.MultiSelectDialog({ doctype, target, setters, date_field, get_query, action })

MultiSelectDialog 由筛选字段和一个多选列表组成。主要按钮将对选中的选项执行传递的 action。

默认情况下,搜索词字段和日期范围字段将构成筛选字段。

参数列表包括:

  • doctype:用于获取和显示选择条目的数据源。
  • target:模态框要显示的目标位置。
  • setters:这些将构成筛选字段及其填充值。这些也会转换为选择列表的自定义列。
  • read_only_setters:如果你希望将设置器(筛选器)设为只读,以便用户无法更改筛选器的值,则将这些字段添加到“read_only_setters”中。
  • add_filters_group:一个布尔值,用于在 setters 下方的对话框中添加/移除筛选组。该筛选组与列表视图筛选器相同。
  • date_field:必须传递所考虑 DocType 的 date_field。
  • get_query:一个函数,返回 query 和 filters 以查询选择列表。可以通过 query 传入自定义的服务器端方法,并且 filters 将被传递给该方法。
  • action:包含要对所选选项执行的主要操作。它接受 selections 作为参数,该参数包含所选选项。
  • columns:自定义查询返回的字段数组,这些字段将成为结果数据表中的列。仅适用于自定义查询(get_query 参数返回一个 query)。
  • 假设我们想要将物料申请获取到对话框中。然后我们可以按以下方式调用 MultiSelectDialog:

    new frappe.ui.form.MultiSelectDialog({
        doctype: "Material Request",
        target: this.cur_frm,
        setters: {
            schedule_date: null,
            status: 'Pending'
        },
        add_filters_group: 1,
        date_field: "transaction_date",
        get_query() {
            return {
                filters: { docstatus: ['!=', 2] }
            }
        },
        action(selections) {
            console.log(selections);
        }
    });
    
    // MultiSelectDialog with custom query method
    let query_args = {
        query:"dotted.path.to.method",
        filters: { docstatus: ["!=", 2], supplier: "John Doe" }
    }
    
    new frappe.ui.form.MultiSelectDialog({
        doctype: "Material Request",
        target: this.cur_frm,
        setters: {
            schedule_date: null,
            status: 'Pending'
        },
        add_filters_group: 1,
        date_field: "transaction_date",
        columns: ["name", "transaction_date", "status"],
        get_query() {
            return query_args;
        },
        action(selections) {
            console.log(selections);
        }
    });
    

    frappe.ui.form.MultiSelectDialog

    这里,所有满足筛选条件的物料申请都会被获取到选择区域中。setter company 会连同其传递的值一起添加到筛选字段中。date_field 将用于从指定的 DocType 中获取和查询日期。

    创建物料申请(或 Make {DocType})次要操作按钮将重定向您到一个新表单,以便向传递的 DocType 中创建新条目。

    现在,如果我们只想从物料申请中选择特定项目,则可以使用可选的 child_selection_mode 来启用子项选择。

    
    // MultiSelectDialog for individual child selection
    new frappe.ui.form.MultiSelectDialog({
        doctype: "Material Request",
        target: this.cur_frm,
        setters: {
            schedule_date: null,
            status: null
        },
        add_filters_group: 1,
        date_field: "transaction_date",
        allow_child_item_selection: 1,
        child_fieldname: "items", // child table fieldname, whose records will be shown & can be filtered
        child_columns: ["item_code", "qty"], // child item columns to be displayed
        get_query() {
            return {
                filters: { docstatus: ['!=', 2] }
            }
        },
        action(selections, args) {
            console.log(args.filtered_children); // list of selected item names
        }
    });
    

    frappe.ui.form.MultiSelectDialog

    在这里,您会看到一个复选框 选择单个项目,用于在子项选择与父项选择之间切换。一旦切换,所有查询到的物料申请中的各个物料申请项目都会被列出,您现在可以筛选这些项目以进行选择。

    要访问选中的子项,您可以使用 args.filtered_children 列表,其中包含选中的子项名称。

    对话框中的表格/网格

    可以向对话框中添加表格,就像添加任何其他字段一样,如下所示:

    const dialog = new frappe.ui.Dialog({
      title: __("Create Logs"),
      fields: [
          {
              fieldname: "logs",
              fieldtype: "Table",
              label: __("Logs"),
              in_place_edit: true,
              reqd: 1,
              fields: [
                  {
                      fieldname: "log_type",
                      label: __("Log Type"),
                      fieldtype: "Select",
                      options: "
    IN
    OUT",
                      in_list_view: 1,
                      reqd: 1,
                  },
                  {
                      fieldname: "time",
                      label: __("Time"),
                      fieldtype: "Time",
                      in_list_view: 1,
                      reqd: 1,
                  }
              ],
              on_add_row: (idx) => {
                // idx = visible idx of the row starting from 1
                // eg. set `log_type` as alternating IN/OUT in the table on row addition
                  let data_id = idx - 1;
                  let logs = dialog.fields_dict.logs;
                  let log_type = (data_id % 2) == 0 ? "IN" : "OUT";
    
                  logs.df.data[data_id].log_type = log_type;
                  logs.grid.refresh();
              },
          },
      ],
      primary_action: (values) => { ... },
      primary_action_label: __("Create"),
    });
    

    on_add_row:一个在向表格添加行时触发的事件。您可以通过向此事件挂钩添加功能来执行数据操作或其他类型的计算等操作。

    公共工具API

    frappe.get_route

    frappe.get_route()

    将当前路由以数组形式返回。

    frappe.get_route()
    // ["List", "Task", "List"]
    

    frappe.set_route

    frappe.set_route(route)

    将当前路由更改为 route。

    // route in parts
    frappe.set_route('List', 'Task', 'List')
    
    // route as array
    frappe.set_route(['List', 'Task', 'Gantt'])
    
    // route as string
    frappe.set_route('List/Event/Calendar')
    
    // route with options
    frappe.set_route(['List', 'Task', 'Task'], { status: 'Open' })
    

    frappe.format

    frappe.format(value, df, options, doc)

    将原始值格式化为用户可读的展示格式。

    frappe.format('2019-09-08', { fieldtype: 'Date' })
    // "09-08-2019"
    
    frappe.format('2399', { fieldtype: 'Currency', options: 'currency' }, { inline: true })
    // "2,399.00"
    

    frappe.provide

    frappe.provide(namespace)

    如果 window 对象上不存在该命名空间,则创建并挂载到 window 对象上。

    frappe.provide('frappe.ui.form');
    
    // has the same effect as
    window.frappe = {}
    window.frappe.ui = {}
    window.frappe.ui.form = {}
    

    frappe.require

    frappe.require(asset_path, callback)

    异步加载 JS 或 CSS 资源。用于加载不常用的库。

    // load a single asset
    frappe.require('/assets/frappe/chat.js', () => {
     // chat.js is loaded
    })
    
    // load multiple assets
    frappe.require(['/assets/frappe/chat.js', '/assets/frappe/chat.css'], () => {
     // chat.js and chat.css are loaded
    })
    

    树

    树形视图是为所有启用了“是树形”属性的 DocType 生成的。


    树形视图

    标准树形 JS

    要自定义树形视图,你必须在 doctype 目录中有一个 {doctype}_tree.js 文件。
    以下是所有可以自定义的选项。

    例如,如果你想配置 Account DocType,你需要创建一个包含以下内容的 account_tree.js 文件。

    frappe.treeview_settings["Account"] = {
        breadcrumb: "Accounting",
        title: "Chart of Accounts",
    
        filters: [
            {
                fieldname: "company",
                fieldtype: "Select",
                label: "Company",
                options: "Company 1\nCompany 2",
                on_change: handle_company_change(),
            },
        ],
    
        get_tree_nodes: "path.to.whitelisted_method.get_children",
        add_tree_node: "path.to.whitelisted_method.handle_add_account",
    
        fields: [
            {
                fieldtype: "Data",
                fieldname: "account_name",
                label: "New Account Name",
                reqd: true,
            },
            {
                fieldtype: "Link",
                fieldname: "account_currency",
                label: "Currency",
                options: "Currency",
            },
            {
                fieldtype: "Check",
                fieldname: "is_group",
                label: "Is Group",
            },
        ],
    
        ignore_fields: ["parent_account"],
    
        menu_items: [
            {
                label: "New Company",
                action: function () {
                    frappe.new_doc("Company", true);
                },
                condition: "frappe.boot.user.can_create.indexOf('Company') !== -1",
            },
        ],
    
        onload: function (treeview) {},
    
        post_render: function (treeview) {},
    
        onrender: function (node) {},
    
        on_get_node: function (nodes) {},
    
        extend_toolbar: true,
    
        toolbar: [
            {
                label: "Add Child",
                condition: function (node) {
                    return node && node.is_group;
                },
                click: function (node) {
                    frappe.treeview_settings["Account"].add_node(node);
                },
                btnClass: "hidden-xs",
            },
        ],
    };
    

    页面接口

    Desk 中的每个屏幕都渲染在一个 frappe.ui.Page 对象内。

    frappe.ui.make_app_page

    创建一个新的页面并将其附加到父级。

    let page = frappe.ui.make_app_page({
        title: 'My Page',
        parent: wrapper // HTML DOM Element or jQuery object
        single_column: true // create a page without sidebar
    })
    

    新建页面

    页面方法

    本节列出了页面实例对象上可用的常用方法。

    page.set_title

    设置页面标题以及文档标题。文档标题显示在浏览器标签页中。

    page.set_title('My Page')
    

    页面标题

    page.set_title_sub

    设置页面的副标题。它显示在页面标题的右侧。

    page.set_title_sub('Subtitle')
    

    页面副标题

    page.set_indicator

    设置指示器的标签和颜色。

    page.set_indicator('Pending', 'orange')
    

    页面指示器

    page.clear_indicator

    清除指示器的标签和颜色。

    page.clear_indicator()
    

    page.set_primary_action

    设置主要操作按钮的标签和处理函数。第三个参数是图标类,将在移动视图中显示。

    let $btn = page.set_primary_action('New', () => create_new(), 'octicon octicon-plus')
    

    页面主要操作

    page.clear_primary_action

    清除主要操作按钮和处理函数。

    page.clear_primary_action()
    

    page.set_secondary_action

    设置次要操作按钮的标签和处理函数。第三个参数是图标类,将在移动视图中显示。

    let $btn = page.set_secondary_action('Refresh', () => refresh(), 'octicon octicon-sync')
    

    页面次要操作

    page.clear_secondary_action

    清除次要操作按钮和处理函数。

    page.clear_secondary_action()
    

    page.add_menu_item

    在菜单下拉框中添加菜单项。

    // add a normal menu item
    page.add_menu_item('Send Email', () => open_email_dialog())
    
    // add a standard menu item
    page.add_menu_item('Send Email', () => open_email_dialog(), true)
    

    页面菜单下拉框

    page.clear_menu

    移除包含菜单项的下拉菜单。

    page.clear_menu()
    

    page.add_action_item

    在操作下拉框中添加菜单项。

    // add a normal menu item
    page.add_action_item('Delete', () => delete_items())
    

    页面操作下拉框

    page.clear_actions_menu

    移除包含菜单项的操作下拉菜单。

    page.clear_actions_menu()
    

    page.add_inner_button

    在内部工具栏中添加按钮。

    // add a normal inner button
    page.add_inner_button('Update Posts', () => update_posts())
    

    页面内部按钮

    // add a dropdown button in a group
    page.add_inner_button('New Post', () => new_post(), 'Make')
    

    页面内部按钮组

    page.change_custombuttontype

    通过标签(和组)更改特定自定义按钮的类型。

    // change type of ungrouped button
    page.change_inner_button_type('Update Posts', null, 'primary');
    
    // change type of a button in a group
    page.change_inner_button_type('Delete Posts', 'Actions', 'danger');
    

    page.remove_inner_button

    移除内部工具栏中的按钮。

    // remove inner button
    page.remove_inner_button('Update Posts')
    
    // remove dropdown button in a group
    page.remove_inner_button('New Posts', 'Make')
    

    page.clear_inner_toolbar

    移除内部工具栏。

    page.clear_inner_toolbar()
    

    page.add_field

    在页面表单工具栏中添加一个表单控件。

    let field = page.add_field({
        label: 'Status',
        fieldtype: 'Select',
        fieldname: 'status',
        options: [
            'Open',
            'Closed',
            'Cancelled'
        ],
        change() {
            console.log(field.get_value());
        }
    });
    

    页面表单工具栏

    page.get_form_values

    以对象形式获取页面表单工具栏中的所有表单值。

    let values = page.get_form_values()
    // { status: 'Open', priority: 'Low' }
    

    page.clear_fields

    清除页面表单工具栏中的所有字段。

    page.clear_fields()