表单巡回

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()
    

    列表

    列表视图是为除子表和单文档类型之外的所有文档类型生成的。

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

    • 筛选
    • 排序
    • 分页
    • 按标签筛选
    • 切换视图为报表、日历、甘特图、看板等。

    列表视图

    标准列表 JS

    要自定义列表视图,您必须在文档类型目录中有一个 {doctype}_list.js 文件。以下是所有可以自定义的选项。

    例如,如果您想自定义 Note 文档类型,您需要创建一个包含以下内容的 note_list.js 文件。

    frappe.listview_settings['Note'] = {
        // add fields to fetch
        add_fields: ['title', 'public'],
        // set default filters
        filters: [
            ['public', '=', 1]
        ],
        hide_name_column: true, // hide the last column which shows the `name`
        hide_name_filter: true, // hide the default filter field for the name column
        onload(listview) {
            // triggers once before the list is loaded
        },
        before_render() {
            // triggers before every render of list records
        },
    
        // set this to true to apply indicator function on draft documents too
        has_indicator_for_draft: false,
    
        get_indicator(doc) {
            // customize indicator color
            if (doc.public) {
                return [__("Public"), "green", "public,=,Yes"];
            } else {
                return [__("Private"), "darkgrey", "public,=,No"];
            }
        },
        primary_action() {
            // triggers when the primary action is clicked
        },
        get_form_link(doc) {
            // override the form route for this doc
        },
        // add a custom button for each row
        button: {
            show(doc) {
                return doc.reference_name;
            },
            get_label() {
                return 'View';
            },
            get_description(doc) {
                return __('View {0}', [`${doc.reference_type} ${doc.reference_name}`])
            },
            action(doc) {
                frappe.set_route('Form', doc.reference_type, doc.reference_name);
            }
        },
        // format how a field value is shown
        formatters: {
            title(val) {
                return val.bold();
            },
            public(val) {
                return val ? 'Yes' : 'No';
            }
        }
    }
    

    自定义列表 JS

    您还可以通过创建系统中的客户端脚本来自定义列表视图。如果逻辑特定于您的站点,您应该编写客户端脚本。如果您想在多个站点之间共享列表视图自定义设置,则必须通过应用程序来包含它们。

    要创建新的客户端脚本,请前往

    首页 > 自定义 > 客户端脚本 > 新建

    用于列表的新客户端脚本

    上述自定义将产生如下所示的列表视图:

    自定义列表视图

    列表 JS 中的多个按钮

    此功能在开发版中可用。

    您现在可以通过列表视图客户端脚本,在列表视图行中的下拉菜单内添加多个按钮。此功能通过直接从列表视图方便地访问各种操作,增强了用户体验。

    frappe.listview_settings["ToDo"] = {
        hide_name_column: true,
        add_fields: ["reference_type", "reference_name"],
    
        button: {
          show: function(doc) {
            return doc.reference_name;
          },
          get_label: function() {
            return __("Open", null, "Access");
          },
          get_description: function(doc) {
            return __("Open {0}", [
              `${__(doc.reference_type)}: ${doc.reference_name}`
            ]);
          },
          action: function(doc) {
            frappe.set_route("Form", doc.reference_type, doc
              .reference_name);
          },
        },
        dropdown_button: {
          get_label: __("Dropdown"),
          buttons: [{
              get_label: __("Button 1"),
              show: function(doc) {
                return true;
              },
              get_description: function(doc) {
                return "Open Button 1 " + doc.reference_name;
              },
              action: function(doc) {
                frappe.msgprint("Dropdown Button 1 Clicked " +
                  doc.reference_name);
              }
            },
            {
              get_label: __("Button 2"),
              show: function(doc) {
                return doc.status != "Closed";
              },
              get_description: function(doc) {
                return "Open Button 2 " + doc.reference_name;
              },
              action: function(doc) {
                frappe.msgprint("Dropdown Button 2 Clicked " +
                  doc.reference_name);
              }
            },
            {
              get_label: __("Button 3"),
              show: function(doc) {
                return doc.status != "Cancelled";
              },
              get_description: function(doc) {
                return "Open Button 3 " + doc.reference_name;
              },
              action: function(doc) {
                frappe.msgprint("Dropdown Button 3 Clicked " +
                  doc.reference_name);
              }
            },
          ]
        }
      };