续接上一节,今天我们要解析的这个页面是新建请假申请的最后一个页面ConfirmationScreen。
- 页面结构
这个页面初看感觉很简单,稍一拆析发现还是有点意思,这个页面里其实是包括了两个“页面”。一起来看:
最初从左侧的树状视图里选中最上层的 ConfirmationScreen时,右侧的页面布局展示区域显示的是下图中间那个绿色对勾的页面。结合页面的名字来看,第一感觉这个页面就是简单的实现了对请假申请进行确认的功能。把这个页面的第一级展开后,看到的是两个组合:ManagerView,EmployeeView,再具体全部展开,就有意思了。最终的页面结构见下图:
绿对勾的页面是EmployeeVieew视图页面,ManagerView是右边标题为"Declined Request"这个页面。
- 控件解析*
首先还是看最高层的 ConfirmationScreen,设置了OnVisible 的属性。//开始真是小看了这个页面,单单这个OnVisible的代码段长度,就超过了之前解析的其它页面. 下面一起分析一下:
代码的总体是执行了一个IF语句(根据不同状态处理假期申请,发送邮件通知)
和一个Concurrent语句(为新申请把相关参数还原成默认值)
, 见下面精简的代码段。
IF(_submittingRequest, `//如果正在提交,就继续下面的If子语句,否则跳过`
If(_editingRequest, `//如果是正在编辑,就执行下面第一个Concurrent函数`
Concurrent(
Patch(),
If(),
ClearCollect()
),
Concurrent( `//如果不是正在编辑,就执行这里的Concurrent函数`
Patch(),
ClearCollect()
); `//子If 结束`
Forall (); `//与子If语句平级`
Office365Outlook.SendEmailV2(); `//与子If语句平级`
ForAll() `//与子If语句平级`
); `//IF 语句结束`
CONCURRENT()
再来看具体的代码:
If(
_submittingRequest, `//主IF的条件,_submittingRequest是上一节在提交按钮上设置的变量`
If( `//子IF 开始: _submittingRequest为真的代码段`
_editingRequest, `//子If 语句的条件,如果是编辑状态,就执行下面第一个Concurrent函数`
Concurrent( `//并发处理几个任务,1.对当前正在编辑的记录进行修改;2.如果主管批准,相应的处理已用假期天数;3.生成后续要发送的电子邮件的内容模板`
Patch( `//Concurrent的第一个函数`
Leave, `Patch的第一个参数,数据源`
GalleryRequests.Selected, `Patch的第二个参数,要修改的表`
{
Title: First(RequestEdit).Title,
Detail: First(RequestEdit).Detail,
StartDate: First(RequestEdit).StartDate,
EndDate: First(RequestEdit).EndDate,
LeaveType: First(RequestEdit).LeaveType,
Approver: First(RequestEdit).Approver,
Status: First(RequestEdit).Status,
Requester: First(RequestEdit).Requester,
LeaveID: GalleryRequests.Selected.LeaveID
} `Patch的第三个参数,花括号里的就是表里各条记录的新值`
),
If(`//Concurrent的第二个函数`
_managerView && _managerApproved, `如果主管批准请假`
Patch( `就按照员工申请的假期类型,把员工本次申请的天数加到已用的假期天数里,
然后更新到Balance假期天数集合`
Balance,
LookUp(Balance, BalanceID = _requesterBalanceRecord.BalanceID),
If(
Upper(First(RequestEdit).LeaveType) = "VACATION",
{
VacationUsed: Value(_requesterBalanceRecord.VacationUsed) + First(RequestEdit).DaysCount},
Upper(First(RequestEdit).LeaveType) = "SICK LEAVE",
{
SickUsed: Value(_requesterBalanceRecord.SickUsed) + First(RequestEdit).DaysCount},
Upper(First(RequestEdit).LeaveType) = "FLOATING HOLIDAY",
{
FloatingUsed: Value(_requesterBalanceRecord.FloatingUsed) + First(RequestEdit).DaysCount},
Upper(First(RequestEdit).LeaveType) = "JURY DUTY",
{
JuryDutyUsed: Value(_requesterBalanceRecord.JuryDutyUsed) + First(RequestEdit).DaysCount},
Upper(First(RequestEdit).LeaveType) = "BEREAVEMENT",
{
BereavementUsed: Value(_requesterBalanceRecord.BereavementUsed) + First(RequestEdit).DaysCount}
)
)
),
ClearCollect( `//Concurrent的第三个函数- 作用:从RequestEdit记录里获取数据,生成邮件模板`
TemplateData, `更新集合TemplateDate的各个值`
{
Field: "{SubmitterName}",Data: If(_managerView, _requester.DisplayName, _myProfile.DisplayName)},
{
Field: "{LeaveType}",Data: First(RequestEdit).LeaveType},
{
Field: "{LeaveTitle}",Data: First(RequestEdit).Title},
{
Field: "{LeaveDescript}",Data: First(RequestEdit).Detail},
{
Field: "{LeaveStart}",Data: Text(First(RequestEdit).StartDate,"[$-en-US]mmm. dd, yyyy")},
{
Field: "{LeaveEnd}",Data: Text(First(RequestEdit).EndDate,"[$-en-US]mmm. dd, yyyy")},
{
Field: "{LeaveStatus}",Data: First(RequestEdit).Status}
) `ClearCollect结束`
), `//第一个Concurrent结束`
Concurrent( `子If语句对应条件 _editingRequest 为false 的运行逻辑,也就是新建申请的处理`
Patch( `第二个Concurrent函数的第一个子函数 Patch()`
Leave,
Defaults(Leave),
{
Title: AboutLeaveTitleInput.Text,
Detail: AboutLeaveDetailInput.Text,
StartDate: LeaveStartDatePicker.SelectedDate,
EndDate: LeaveEndDatePicker.SelectedDate,
LeaveType: _selectedLeaveType.type,
Approver: _defaultApprover.UserPrincipalName,
Status: "Pending",
Requester: _myProfile.UserPrincipalName,
LeaveID: Text(Now(),DateTimeFormat.LongDateTime24) & _myProfile.UserPrincipalName
}
), `Patch函数到此结束`
ClearCollect( `第二个Concurrent函数的第二个子函数 ClearCollect - 作用:从用户输入的字段里获取数据生成邮件模板`
TemplateData, `为电子邮件模板收集数据`
{
Field: "{SubmitterName}",Data: If(_managerView, _requester.DisplayName, _myProfile.DisplayName)},
{
Field: "{LeaveType}",Data: _selectedLeaveType.type},
{
Field: "{LeaveTitle}", Data: AboutLeaveTitleInput.Text},
{
Field: "{LeaveDescript}",Data: AboutLeaveDetailInput.Text},
{
Field: "{LeaveStart}",Data: Text(LeaveStartDatePicker.SelectedDate,"[$-en-US]mmm. dd, yyyy")},
{
Field: "{LeaveEnd}",Data: Text(LeaveEndDatePicker.SelectedDate,"[$-en-US]mmm. dd, yyyy" )}
) `//ClearCollect结束`
) `//第二个Concurrent结束`
); `//内嵌子IF语句结束,但这里是分号,依旧是 _submittingRequest为真的代码段`
`//给模板数据设置好标识符,主管登录使用第二行的模板数据,否则用第一行的数据`
ForAll(
TemplateData,
Patch(
EmailTemplate,
LookUp(EmailTemplate,If(_managerView,Row = 2,Row = 1)),
{
Value: Substitute(
LookUp(EmailTemplate,If( _managerView,Row = 2,Row = 1)).Value,
Field,
Data
)
}
) `//Patch 结束`
);`//ForAll代码段结束,但这里是分号,接下来依旧是 _submittingRequest为真的代码段`
Office365Outlook.SendEmailV2( `按照上面的模板,发送电子邮件`
If(_managerView, _requester.UserPrincipalName, _defaultApprover.UserPrincipalName),
If(_managerView, "Leave Request " & First(RequestEdit).Status, "New Leave Request"),
LookUp(EmailTemplate,If(_managerView, Row = 2, Row = 1)).Value,
{
Importance: "Normal"}
); `//SendEmailV2代码段结束,但这里是分号,接下来依旧是 _submittingRequest为真的代码段`
ForAll( `//把邮件模板恢复默认值`
TemplateData,
Patch(EmailTemplate,
LookUp(EmailTemplate,If(_managerView,Row = 2,Row = 1)),
{
Value: Substitute(LookUp(EmailTemplate,If(_managerView,Row = 2,Row = 1)).Value,Data,Field)}
) `//Patch 结束`
) `//ForAll 结束`
); `//顶级IF代码段结束`
Concurrent( `//为下一个新的申请把所有相关参数恢复回默认值`
Set(_submittingRequest,false),
Set(_reviewRequest, false),
Reset(AboutLeaveTitleInput),
Reset(AboutLeaveDetailInput),
Reset(LeaveStartDatePicker),
Reset(LeaveEndDatePicker),
Set(_defaultApprover,Blank()),
Set(_defaultApproverPhoto,Blank()),
Set(_selectedLeaveType,Blank()
)
)
好了,后续逐个拆解页面下的控件就比较简单了,先看一下两个组控件的设置
ManagerView - 主管视图组
默认的这个视图是不可见的,相应的设置为: Visible = _managerView
EmployeeView - 员工视图, 没有特殊设置
最后,具体来看每个子控件:
2.1 BtnDone_1
-
OnSelect = //页面最下面的按钮。设置了两个动作:重置变量ChangeRequestMessageInput_1, 然后跳转回HomeScreen
Reset(ChangeRequestMessageInput_1); Navigate(HomeScreen, None)
2.2 LblChangeRequestScreenHeader_1 - 页面标题
-
Text = //根据审批状态,显示Approved Request 或者 Declined Request
If(_managerApproved, “Approved”, “Declined”) & " Request"
2.3 Label23_1 -
-
Text = //组合出来的一个字符串, 用到了下面的 2.5控件
"Message to " & First(Split(ChangeRequestRequster_1.Text, " “)).Result & " (optional)”
2.4 ChangeRequestResult_1 - 审批结果文本描述
-
Text = //根据不同状态,设定不同的审批结果描述
If(_submittingRequest, “Processing request…”, _managerApproved,“The request has been approved.”,“The request has been declined.”)
2.5 ChangeRequestRequster_1 - 申请人名称
-
Text = _requester.DisplayName
放一个截图在下面,可以参考一下实际的效果。//Test62.F1就是这个这个控件的显示结果。
2.6ChangeRequestTitle_1 - 请假申请天的假条抬头
-
Text = First(RequestEdit).Title
//如上图的JuryDuty,对应的就是这个控件
2.7 ChangeRequestDue_1 - 假期起止日期
-
Text = //从RequestEdit集合里获取出假期的起止日,然后用Text函数转成字符串文本
Text(First(RequestEdit).StartDate,"[KaTeX parse error: Expected 'EOF', got '&' at position 30: …mm. dd, yyyy") &̲ If(DateDiff(Fi…-en-US]dddd, mmm. dd, yyyy"))
2.8 ChangeRequestContentSeperator_1 - 区域分割线条
2.9 ChangeRequestMessageInput_1 - 文本输入框
这个文本框用于在主管点击审批按钮跳转到这个页面后,可以再输入一些备注性的文字然后点发送按钮来给申请人发送邮件通知,不过这个是可选的。一旦从上一个页面点击审批按钮跳转到这个页面,页面最下的Done按钮很快就会被启用,来结束整个审批流程。
该文本框自身没有运行逻辑的设置,只是用来为下一个控件提供数据。
2.10 ChangeRequestSendButton_1 - 发送按钮
这个按钮默认是被禁用的(由DisplayMode控制)。
-
DisplayMode = //RequestEdit为空,也就是说当前没有假期申请,就禁用按钮。否则启用按钮。
If(IsEmpty(RequestEdit), Disabled, DisplayMode.Edit)
2.11 icon3 - 右上角的 “X” 按钮
-
Visible = !_submittingRequest && !_managerView //做了可见性的条件设置
-
OnSelect = Navigate(HomeScreen, None) //跳转回HomeScreen
2.12 icon4 - 绿色对勾图标
-
Visible = !_managerView
-
Icon = Icon.Check
-
OnSelect = Navigate(HomeScreen, None)
2.13 Label10 - 状态提示文字标签
-
Visible = !_managerView
-
Text = "Your request " & If(_submittingRequest, “is being processed…”, “has been submitted”)
本节用到了这些函数,其中有几个还是之前没用过的:
IF, Concurrent, Patch, First, LookUp, Upper, ClearCollect, Defaults, Text, ForAll, Substitue, Set, Reset
另外,本节还用到了调用O365邮件功能模块来发送邮件的实现方法: Office365Outlook.SendEmailV2(), 之前某节里应该有用到过,在这里特别提一下,这个SendEmailV2不是PowerApps自身的函数,而是Exchange Online邮件功能模块里的函数,需要通过添加连接器来引入这个功能。具体如何实现请参考另一博文。
好了,原本想在本节最后对之前的解析做个小结,没想到一上来就来了个大体量的内容,在最高的页面层级做了很多运行逻辑的设定。好在接下来的子控件就没有太多新东西了,不过通过两个组控件在一个页面上实现两个子页面倒是这个解析的亮点,可以开拓一下解决问题时的思维方式。
----先到这里,我们下节专门对之前的解析做个小结。下一节再见。。。-----