为表单添加自定义按钮

要在表单上创建自定义按钮,您需要编辑与您的文档类型关联的 JavaScript 文件。例如,如果您想向用户表单添加自定义按钮,则必须编辑 user.js。

在此文件中,您需要编写一个新方法 add_custom_button,该方法应向您的表单添加一个按钮。

add_custom_button(...) 的函数签名

frm.add_custom_button(__(buttonName), function() {
// perform desired action such as routing to new form or fetching etc.
}, __(groupName));

示例 1:向用户表单添加按钮

我们应该编辑 frappe/core/doctype/user/user.js

frappe.ui.form.on('User', {
   refresh: function(frm) {
       frm.add_custom_button(__('Get User Email Address'), function() {
           frappe.msgprint(frm.doc.email);
       }, __("Utilities"));
   }
});

您应该会在用户表单上看到一个按钮,如下所示:

运行后台作业

有时,您可能不希望用户请求立即执行,而是将其添加到队列中,由后台工作进程执行。这样做的好处是,您的 Web 工作进程可以保持空闲以执行其他请求,并且较长的任务不会耗尽您的所有资源。

Frappe 使用 Python RQ 来运行后台任务。以下示例说明了如何将任务加入队列:

import frappe

def long_job(arg1, arg2):
 frappe.publish_realtime('msgprint', 'Starting long job...')
 # this job takes a long time to process
 frappe.publish_realtime('msgprint', 'Ending long job...')

def enqueue_long_job(arg1, args2):
 frappe.enqueue('myapp.mymodule.long_job', arg1=arg1, arg2=arg2)

这会将 long_job 加入名为 default 的队列。其他可用的队列包括 long 和 short。您应根据预估的任务执行时长来选择适当的队列。

从版本 14 开始,您还可以通过在入队调用中将 at_front 作为 True 传递,将任务添加到队列的前端。

在文档对象上调用延迟操作

您也可以在文档对象上调用延迟操作,例如在库存盘点中,如果项目超过 100 个,则会作为后台任务执行。

示例:您可以调用 doc.queue_action('submit')

注意:这仅适用于 save、submit、cancel

如果您预计某些操作的执行量非常大,也可以将其推送到后台执行。

例如:

def submit(self):
 if len(self.items) > 100:
  self.queue_action('submit')
 else:
  self._submit()

调试

如果您正在使用 bench start

您将在终端中看到日志。

注意:默认的工作进程不会自动重启,因此您需要在修改后终止 bench 并重新启动。

如何改进标准控制

好的,我已经按照您的要求,将 HTML 内容中的文本翻译成了通俗易懂的中文,同时严格保留了所有 HTML 标签、属性和结构,并原样保留了形如 <pre><code> frappe.ui.form.ControlData = frappe.ui.form.ControlData.$extend({ make_input: function(){ var options = this.df.options; if (!options || options!=="Translatable"){ this._super(); return; } var me = this; $('\ \ \ \\ \ ').prependTo(this.input_area); this.$input_area = $(this.input_area); this.$input = this.$input_area.find('input'); this.$btn = this.$input_area.find('.dialog-btn'); this.set_input_attributes(); this.$input.on("focus", function(){ me.$btn.toggle(true); }); this.$input.on("blur", function(){ setTimeout(function(){ me.$btn.toggle(false) }, 500); }); this.input = $this.input.get(0); this.has_input = true; var me = this; this.setup_button(); }, setup_button: function(){ var me = this; if (this.only_input){ this.$btn.remove(); return; } this.$btn.on("click", function(){ var value = me.get_value(); var options = me.df.options; if (value && options && options==="Translatable"){ this.open_dialog(); } }); }, open_dialog: function(){ if (this.frm && !this.frm.is_dirty()) { new frappe.ui.form.TranslationSelector({ doc: doc, df: this.doc, text: this.value }); } } }); 的代码块占位符。


<article :class="{ 'opacity-50': $store.navigation.loading, 'xl:!max-w-[100ch]': $store.sidebar?.isCollapsed }" class="min-w-0 w-full transition-opacity duration-300 xl:max-w-[85ch]">
<!-- 页面头部 -->

<!-- 页面内容 -->
<!-- prose prose-v3 镜像了 SPA 编辑器中的 EditorContent 默认样式 -->
<div class="prose prose-v3 !max-w-none" id="wiki-content">
<p>Frappe 有一些优雅且实用的控件,但有时我们需要对它们进行修改以添加一些小的改进。这篇小文章将介绍如何向标准控件中添加新的资源。</p>
<p>首先,让我解释一下我们的目标:</p>
<blockquote>
<p>在 <code>numerous records 和 lot of doctypes 中添加 many 的替代翻译

看看目标中高亮的部分,我们需要在许多记录和许多文档类型中添加许多翻译,因此,我们有很多工作要做,对吧?

这个问题的答案是:-当然不是!因为我们知道,如果一个元素存在于许多记录和许多文档类型中,那么这个元素就是 Control 或 Widget

所以,我们需要做的是,基于 Control 来改进你的目标,以减少我们的工作量。

但是,我们将在哪里找到这个神奇的元素,即控件呢?-目前,我们可以在 JavaScript 源码中查看它 - 现在让我们看看 Github

如果现在看不懂代码,请不要担心,我们的目标是简化我们的工作。

让我们继续思考!

我们知道需要在何处进行更改,但我们如何区分哪些控件受我们功能的影响,哪些不受影响呢?

我们需要记住,Control 是 DocFields 的实例,并且 DocFields 有一个字段在这种情况下对我们非常重要,这个能帮助我们区分哪些受功能影响、哪些不受影响的字段是 DocField 中的 options 字段。

-等等!我们明白了 options 字段可以帮助我们,但是,它将如何帮助我们呢?

问得好,我们将定义一个词,放在我们需要包含该功能的 DocFields 的 options 中,这个词将是 Translatable。

如果你忘记了如何自定义字段的选项,可以看看这篇文章,它能帮你复习一下相关知识。

好了,在我们选定的 DocFields 的 options 中定义了关键词后,现在是时候编写代码了:

-终于,我们以为我们永远不会停止讨论呢!

 frappe.ui.form.ControlData = frappe.ui.form.ControlData.$extend({
 make_input: function(){
 var options = this.df.options;
 if (!options || options!=="Translatable"){
 this._super();
 return;
 }
 var me = this;
 $('\
 \
 \
 \\
 \
 ').prependTo(this.input_area);
 this.$input_area = $(this.input_area);
 this.$input = this.$input_area.find('input');
 this.$btn = this.$input_area.find('.dialog-btn');
 this.set_input_attributes();
 this.$input.on("focus", function(){
 me.$btn.toggle(true);
 });
 this.$input.on("blur", function(){
 setTimeout(function(){ me.$btn.toggle(false) }, 500);
 });
 this.input = $this.input.get(0);
 this.has_input = true;
 var me = this;
 this.setup_button();
 },
 setup_button: function(){
 var me = this;
 if (this.only_input){
 this.$btn.remove();
 return;
 }
 this.$btn.on("click", function(){
 var value = me.get_value();
 var options = me.df.options;
 if (value && options && options==="Translatable"){
 this.open_dialog();
 }
 });
 },
 open_dialog: function(){
 if (this.frm && !this.frm.is_dirty()) {
 new frappe.ui.form.TranslationSelector({
 doc: doc,
 df: this.doc,
 text: this.value
 });
 }
 }
 });

-我的天,又是一堆字母汤!

事实上,对于新手来说,它确实是一堆字母汤,但我们不是初学者。

让我解释一下这段代码的作用;

  • 第 1 行,代码通过其自身的扩展 Class 覆盖了 ControlData。
  • 方法 make_input 检查 docfield 是否为 Translatable,以创建新的 Control,如果不是,则使用 _super() 调用原始的 make_input。
  • 方法 setup_button 检查 docfield 是否为 Translatable,以启用它显示一个 dialog。
  • 方法 open_dialog 调用我们将在下面代码中创建的 TranslationSelector 的一个新实例。

在文档类型事件上执行代码

要在 DocType 被插入、验证(保存前)、更新、提交、取消、删除时执行代码,你必须在 DocType 的控制器模块中编写代码。

1. 控制器模块

控制器模块位于 DocType 模块的 doctype 文件夹中。例如,ToDo 的控制器位于 frappe/desk/doctype/todo/todo.py(版本 5)。创建 DocType 时会自动生成一个控制器模板,内容如下:

from future import unicode_literals

import frappe
from frappe.model.document import Document

class CustomType(Document):
pass

2. 文档属性

所有字段和子表都可以作为类的属性访问。例如,name 属性就是 self.name

3. 添加方法

在此模块中,你可以向类中添加标准方法,这些方法会在创建该类型的文档时被调用。标准处理器包括:

  1. autoname:在命名时调用。你可以在此方法中设置 self.name 属性。
  2. before_insert:在文档插入前调用。
  3. validate:在文档保存前调用。如果你不希望文档被保存,可以抛出异常。
  4. on_update:在文档插入或更新到数据库后调用。
  5. on_submit:在提交后调用。
  6. on_cancel:在取消后调用。
  7. on_trash:在文档被删除后调用。

链接字段中的自定义操作

您可以通过在命名空间 frappe.ui.form.ControlLink.link_options 中定义函数,为标准链接字段添加新的自定义链接选项。

在 frappe.ui.form.ControlLink.link_options 中,您可以访问链接字段对象。

1. 添加自定义选项

frappe.ui.form.ControlLink.link_options = function(link) {
 return [
 {
 html: ""
 + " "
 + __("Custom Link Option")
 + "",
 label: __("Custom Link Option"),
 value: "custom__link_option",
 action: () => {}
 }
 ];
}

一旦将函数分配给 frappe.ui.form.ControlLink.link_options,链接字段将出现一个新的链接选项:

键盘快捷键

键盘快捷键

键盘快捷键可以帮助你在 Frappe Desk 中快速操作,无需在键盘和鼠标之间来回切换。使用它们可以打开页面、搜索记录、保存表单、打开帮助,以及在 Desk 的任何位置查看完整的快捷键列表。

Desk 中显示的快捷键会跟随你的操作系统。在 macOS 上,Desk 将 Command 显示为 ⌘,将 Option 显示为 ⌥。在 Windows 和 Linux 上,请使用相应的 Ctrl 和 Alt 键。

查看键盘快捷键

按 Shift + / 打开键盘快捷键面板。该面板列出了 Desk 中可用的全局快捷键。

全局快捷键

快捷键 操作
⌘ + S 触发当前页面上的主要操作。在表单上,这通常是保存操作。
⌘ + K 打开 Awesomebar(全局搜索栏)。
⌘ + G 打开 Awesomebar(全局搜索栏)。
⌥ + S 打开设置。
Shift + / 显示键盘快捷键。
⌥ + H 打开帮助。
Shift + ⌘ + R 清除缓存并重新加载。
Shift + T 打开控制台。
Shift + ⌘ + G 切换主题。

按 Alt 键可以显示菜单和侧边栏中的更多快捷键。

使用 Awesomebar 打开页面

按 ⌘ + K 或 ⌘ + G 打开 Awesomebar。开始输入你想打开的页面、报表、文档类型或记录,然后使用方向键在结果中移动,按 Enter 键选择。

  • 使用 上 和 下 方向键在结果中移动。
  • 按 Enter 打开选中的结果。
  • 按 Esc 关闭 Awesomebar 或快捷键面板。

当快捷键不起作用时

某些浏览器、操作系统或扩展程序的快捷键可能会优先于 Desk 的快捷键。如果某个快捷键不起作用,请点击 Desk 页面内部,然后重试。如果光标位于文本字段内,请先关闭该字段或将焦点移回页面,然后再使用全局快捷键。

通知

Frappe 框架中的通知文档类型提供了一种强大的方式,可以在系统中发生特定事件时,通过不同渠道自动发送通知。本指南将介绍如何有效配置和使用通知功能。

常规设置

  • 启用:开关,用于激活或停用通知。
  • 渠道:选择通知的发送方式:
    • 电子邮件:通过电子邮件发送通知
    • Slack:发送通知到 Slack 频道
    • 系统通知:在 Frappe 界面内显示通知
    • 短信:通过短信发送通知(需要配置短信设置)
  • 标准:用于开发者模式;将通知标记为标准模块的一部分

事件触发器

通知可以根据各种事件触发:

文档事件

  • 新建:创建新文档时触发
  • 保存:保存文档时触发
  • 提交:提交文档时触发
  • 取消:取消文档时触发

基于日期的事件

  • 提前天数:在日期字段值之前的指定天数触发
  • 延后天数:在日期字段值之后的指定天数触发
  • 使用这些事件时:
    • 参考日期:选择要监控的日期字段
    • 提前或延后天数:指定触发日期之前/之后的天数

基于值的事件

  • 值变更:当特定字段的值发生变化时触发
    • 变更的字段:选择要监控变化的字段

基于方法的事件

  • 方法:当调用特定文档方法时触发
    • 触发方法:指定方法名称(例如 “before_insert”、”after_update”)

自定义事件

  • 自定义:用于通过编程方式触发的通知

条件触发器

您可以通过添加条件使通知更具针对性:

  • 条件:一个 Python 表达式,必须计算结果为 True 才会发送通知
  • 条件可以访问:
    • doc:触发通知的文档
    • nowdate():当前日期函数
    • frappe.utils:工具函数

示例

  • 文档的状态为”开放”

    doc.status=="Open"
    
  • 文档的截止日期与当前日期匹配

    doc.due_date==nowdate()
    
  • 总额大于 40,000

    doc.total > 40000
    

收件人配置

电子邮件、系统通知和短信渠道

使用收件人表格配置收件人:

  1. 接收者类型:

    • 按文档字段:选择包含电子邮件地址/电话号码的字段
    • 按角色:选择接收通知的用户角色
    • 自定义:指定自定义收件人
  2. 其他选项:

    • 抄送/密送:用于电子邮件通知
    • 条件:用于有条件地包含收件人的 Python 表达式
    • 发送给所有负责人:包括分配给该文档的所有用户

对于 Slack 频道

  • 从下拉菜单中选择预定义的 Slack Webhook URL

消息配置

主题(用于电子邮件、Slack 和系统通知)

  • 输入主题行文本
  • 支持 Jinja 模板:{{ doc.name }} Delivered

消息正文

  • 消息类型:选择 HTML、Markdown 或纯文本(仅在开发者模式下可用)
  • 消息:输入通知内容
  • 使用 Jinja 模板实现动态内容

模板变量

  • {{ doc }}:访问触发文档中的任何字段(例如 {{ doc.customer }})
  • {{ comments }}:访问文档评论(例如 {{ comments[-1].comment }})
  • {% if %}...{% endif %}:条件逻辑
  • {{ nowdate() }}:当前日期

模板示例

<h3>Order Overdue</h3>

<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>

<!-- show last comment -->
{% if comments %}
Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
{% endif %}

<h4>Details</h4>

<ul>
<li>Customer: {{ doc.customer }}
</li>
<li>Amount: {{ doc.grand_total }}
</li></ul>

高级选项

附件设置

  • 附加打印件:附加文档的 PDF 打印件
    • 打印格式:为附加的 PDF 选择特定的打印格式
  • 附加文件:
    • “全部”:发送触发通知的记录所附加的所有文件
    • “来自字段”:从特定的附件字段发送文件
      • 来自附件字段:选择包含您要发送的附件的字段

通知后操作

  • 提醒后设置属性:发送通知后更新字段
  • 要设置的值:在指定字段中设置的值

最佳实践

  1. 测试您的通知:使用“获取今日提醒”按钮测试基于日期的通知。
  2. 保持条件具体明确:确保您的条件具有针对性,以避免通知疲劳。
  3. 合理使用模板:使消息清晰明了,仅包含相关信息。
  4. 验证收件人字段:确保字段包含有效的电子邮件地址或电话号码。
  5. 考虑权限级别:收件人必须具有访问通知中引用文档的权限。

故障排除

  • 如果通知未发送,请检查文档是否满足所有条件。
  • 对于电子邮件通知,请验证电子邮件设置是否正确配置。
  • 对于短信通知,请确保短信设置已配置。
  • 对于 Slack 通知,请验证 Webhook URL 是否有效且处于活动状态。
  • 检查服务器日志中是否有与通知发送相关的错误。

技术说明

  • 通知采用异步处理方式,以避免对系统性能产生影响。
  • 基于日期的事件通过计划任务每天检查。
  • 文档事件通知在相关操作发生时实时触发。
  • 条件和模板在受限环境中进行评估,以确保安全性。

通过有效配置通知文档类型,您可以及时告知用户重要事件,并在 Frappe 应用中实现通信自动化。

任务与待办事项

概述

Frappe Framework 中的 待办事项 文档类型可帮助用户跟踪和管理分配给自己或他人的任务。待办事项可以独立创建,也可以与系统中的其他文档关联,从而提供一个集中式的任务管理系统。

待办事项字段

  • 状态:可以是“未开始”、“已完成”或“已取消”
  • 优先级:设置为“高”、“中”或“低”(默认为“中”)
  • 颜色:可选的彩色标签,用于视觉识别
  • 截止日期:任务应完成的时间
  • 描述:任务的主要内容(必填)
  • 参考类型 和 名称:可选链接到相关文档(例如,特定的工单、线索等)
  • 角色:可选的角色分配
  • 分配人:创建或分配任务的用户
  • 负责人:负责完成任务的用户

创建待办事项

可以通过以下几种方式创建待办事项:

  1. 直接创建:

    • 导航到待办事项列表
    • 点击“添加待办事项”按钮
    • 填写必填信息并保存
  2. 通过分配功能:

    • 从任何文档(例如,工单、任务)中,使用“分配给”功能
    • 系统会创建一个待办事项并将其链接到该文档
  3. 通过电子邮件:

    • 使用适当的设置向系统发送电子邮件
    • 邮件内容会自动转换为待办事项(待办事项位于 email_append_to 设置中)
  4. 通过分配规则:

    • 根据条件设置自动分配
    • 当条件满足时,系统会自动创建待办事项(注意:不能对待办事项文档类型本身设置分配规则)

管理待办事项

标记为已完成

要将 待办事项 标记为已完成:

  • 打开该待办事项
  • 将状态更改为“已完成”
  • 保存文档

只有被分配人(“负责人”字段中的用户)才能关闭分配给他们的待办事项。如果通过界面操作,当其他人试图完成他人的待办事项时,系统会显示错误提示。

取消待办事项

要取消 待办事项:

  • 打开该待办事项
  • 将状态更改为“已取消”
  • 保存文档

取消与完成不同,它表示该任务将不会被执行,而不是已经完成。

截止日期的影响

截止日期字段:

  • 有助于确定任务的优先级
  • 提供目标完成日期
  • 在列表视图中用于对待办事项进行排序和筛选
  • 分配规则会使用它进行自动化的截止日期管理

但是,系统不会在截止日期临近或已过时自动发送提醒。如果您需要此功能,请考虑创建自定义的 通知。

通知

系统在以下场景中会发送通知:

  1. 当文档被分配或分配被取消时:

    • 被分配人会收到通知
    • 通知中包含任务描述以及分配人或取消分配人的信息
  2. 全局通知计数器:

    • 未完成的待办事项会计入 Frappe 界面中的通知计数器
    • 该计数器可帮助用户一目了然地查看自己有多少未完成的任务

权限

  • 用户可以查看分配给自己或由自己创建的待办事项
  • 系统管理员可以访问所有待办事项
  • “所有”角色拥有创建、读取、写入和删除待办事项的权限

与其他文档的集成

当 待办事项 链接到其他文档时:

  • 分配待办事项时,会在被引用的文档中添加一条评论
  • 待办事项完成或取消时,也会添加一条评论
  • 被引用文档的 _assign 字段中的分配用户列表会更新
  • 查看被引用的文档时,用户可以查看该文档分配给了谁

使用分配规则

虽然不能为 待办事项 文档类型本身创建分配规则,但可以为其他文档类型创建规则来生成待办事项:

  • 根据文档条件创建规则
  • 使用轮询、负载均衡或基于字段的分配方式来分配任务
  • 根据文档字段自动设置截止日期
  • 当文档条件发生变化时,关闭或重新分配待办事项

分配规则为整个系统的任务管理提供了强大的自动化功能。

安全常见问题解答

此页面包含一些与安全相关的常见问题。在向 Frappe 团队提出安全问题之前,请先查看此页面。

Frappe 允许上传恶意文件,如 .EXE 文件

Frappe 不是杀毒软件,因此不会扫描上传的任何类型的文件。Frappe 内置了验证功能来阻止某些扩展名,您可以在“系统设置” > “文件” > “允许的文件扩展名”中进行调整。

Frappe 显示完整的错误回溯信息

错误追踪有助于开发人员更快地调试问题。在绝大多数情况下,它们不会泄露我们开源源代码中尚未公开的任何信息。如果您仍希望禁用回溯信息,请前往“系统设置”并取消勾选“显示完整错误并允许向开发者报告问题”。

Frappe 在发送请求时不加密密码

Frappe 建议在生产环境中正确设置 HTTPS(TLS),如果设置正确,每个请求都会在传输层进行加密,因此在此基础上再次手动加密请求内容的安全收益很小。

如果您使用像 Burp Suite 这样的工具,通过设置代理/证书来拦截加密请求,从而查看未加密的流量,那么您实际上并未处于真实的威胁模型中。在现实世界中,如果 TLS 被攻破,您将面临更大的问题。

此外,Frappe 使用 PBKDF2 + SHA256 哈希算法存储登录密码。一旦存储,就无法检索原始密码。需要可检索值的密码字段(例如 API 密钥)则使用 Fernet 进行对称加密存储。

Frappe 在密码字段上启用“自动完成”功能

自动完成和密码管理器被广泛认为是更安全且用户体验更好的选择。禁用自动完成是过时的做法,而且如果您禁用它,主流浏览器甚至不会尊重这一设置。参考:

Frappe 不会每 30/90 天强制密码过期

定期更换密码并不一定安全,甚至可能产生有害的副作用:

如果您仍然需要此功能,可以在“系统设置” > “强制用户重置密码”中启用。

Frappe 不允许配置密码要求以包含大写、小写和特殊字符

硬性规定密码要求通常用户体验不佳。密码强度主要基于熵,请参阅此漫画以更好地理解:

Frappe 转而使用启发式算法来计算密码强度。默认情况下,我们要求密码具有合理的强度,但如果您想提高此强度,可以在“系统设置” > “密码” > “最低密码强度评分”中进行配置。

Frappe 不允许在不同路径上配置不同的应用程序。因此这种情况不会发生。默认情况下,您配置的域名/子域名下的所有页面均由 Frappe 提供服务。

Frappe 不会为 cookie 添加“Secure”标志

Frappe 仅在通过 HTTPS 协议访问时才会为 cookie 添加“Secure”标志。

我们建议在您的域名上启用 HSTS,以便阻止未加密的流量。

我们允许非 HTTPS 连接,以支持用户可能不想设置 TLS 的内部网络部署。

网站会暴露其运行在 Frappe 上

如果您了解应用程序的特征,就很容易判断一个网站运行在什么平台上。例如,预加载头、资源文件名和 API 路径。要掩盖所有这些信息是不可能的。

即使我们从用户界面隐藏品牌标识,也无需太多时间就能发现某个特定网站运行在 Frappe 上。

在像 Frappe 这样的协作环境中,链接到外部工具和资源通常是工作流程中必要的一部分,因此完全禁用它们并不现实。为了解决潜在的网络钓鱼风险,可以在用户离开工作台之前显示一个警告弹窗。此功能可以在“系统设置” > “显示外部链接警告”中启用。

打印格式或开发者字段中的 XSS 漏洞

诸如打印格式、自定义脚本及其他面向开发者的 HTML 字段,特意设计为允许使用原始 HTML/JavaScript 进行自定义。这些字段仅对受信任的用户(如系统管理员/开发者)开放,因此在此类上下文中渲染脚本属于预期行为,不视为 XSS 漏洞。除非不受信任或低权限用户能够利用这些字段,否则此类脚本执行的报告均视为无效。

邮件组

电子邮件群组是一个强大的工具,用于管理和自动化与订阅者、客户或任何联系人组群的电子邮件通信。

电子邮件群组在电子邮件模块下创建,并设置一个唯一的标题。每个电子邮件群组都会自动使用“标题”字段命名,确保易于识别和管理。主要由“新闻通讯管理员”角色管理,这确保了只有授权人员才能创建、修改或删除群组。

总订阅人数字段会动态显示群组中的订阅者数量,让您快速了解群组的覆盖范围。电子邮件群组与各个电子邮件群组成员相关联,便于管理和细分订阅者。

确认邮件模板字段关联到用于发送确认邮件的特定电子邮件模板。

欢迎邮件模板用于与新订阅者进行初次互动。它会在订阅者确认其电子邮件地址后立即发送,并为未来的沟通定下基调。

成功确认后,订阅者可以被引导至自定义的欢迎网址。这可以是一个着陆页或任何相关内容,以便与新订阅者进行即时互动。如果未设置欢迎网址,订阅者将被带到一个通用的确认页面。

向欢迎网址添加查询参数的功能,可以实现个性化互动并跟踪订阅者的参与情况。