Предыдущие части:
В этой части рассмотрим создание карточек объектов для выполнения заданий в ЛК.
Для создания возьмем за основу решение из прошлых частей. В папку «EssConfig» добавим новую с названием «ObjectCards». Туда будем размещать карточки объектов, все о них здесь. Так как для примера нам подойдет любое задание, которое доступно в сервисе интеграции, то возьмем базовое задание согласование от задачи на свободное согласование, в сервисе интеграции у нее имя «IFreeApprovalAssignments». Создадим под нее папку с названием «FreeApprovalAssignmentsObjCard». Далее добавим в файл следующий текст:
<?xml version="1.0" encoding="utf-8"?>
<objectCard id="05bb7e05-d17b-455f-a9fa-783667f72c59" name="IFreeApprovalAssignments" header="Задание на согласование" icon="clock">
<authorization>
<allow roles="Employee"/>
</authorization>
<views>
<view deviceIdioms="Desktop" deviceOrientations="Landscape, Portrait">
<fields>
</fields>
<content>
{{instruction}}
</content>
<attachmentsAreas>
</attachmentsAreas>
</view>
</views>
<buttons>
<button name="Complete" isPrimary="true" isDefault="true">
<caption>Заполнить</caption>
<clickEventHandler><![CDATA[]]></clickEventHandler>
</button>
</buttons>
<openedEventHandler><![CDATA[]]></openedEventHandler>
<closedEventHandler><![CDATA[]]></closedEventHandler>
</objectCard>
В целом карточка готова, в ней будет кнопка «Заполнить», логику которой заполним позже. Теперь нам необходимо добавить путь к нашей карточке в файле решения. Для этого добавим в узел «objectCards» решения путь к нашей карточке.
<objectCards>
<include xmlns="http://www.w3.org/2001/XInclude" href="EssConfig\objectCards\IFreeApprovalAssignments.xml" />
</objectCards>
Теперь нам необходимо добавить в виджет «Требуют действий» наше задание, для этого необходимо заполнить узел «extensions»:
<extensions>
<extension path="Projections/activeWorks">
<sources>
<source path="/api/workitems/HRPro/IFreeApprovalAssignments?filter=((Status eq 'InProcess') or (IsRead eq false))">
<mapping>
<map sourceProperty="Created" destProperty="created" mode="PropertyToProperty" />
<map sourceProperty="Completed" destProperty="completed" mode="PropertyToProperty" />
<map sourceProperty="Deadline" destProperty="deadline" mode="PropertyToProperty" />
<map sourceProperty="Finished" destProperty="finished" mode="PropertyToProperty" />
<map sourceProperty="HasAttachments" destProperty="hasAttachments" mode="PropertyToProperty" />
<map sourceProperty="Id" destProperty="id" mode="PropertyToProperty" />
<map sourceProperty="IsExpired" destProperty="isExpired" mode="PropertyToProperty" />
<map sourceProperty="IsRead" destProperty="isRead" mode="PropertyToProperty" />
<map sourceProperty="Name" destProperty="name" mode="PropertyToProperty" />
<map sourceProperty="ProcessName" destProperty="processName" mode="PropertyToProperty" />
<map sourceProperty="Result" destProperty="result" mode="PropertyToProperty" converter="HRProConverter" />
<map sourceProperty="Status" destProperty="status" mode="PropertyToProperty" />
<map sourceProperty="System" destProperty="system" mode="PropertyToProperty" />
<map sourceProperty="Title" destProperty="title" mode="PropertyToProperty" />
<map sourceProperty="Icon" destProperty="icon" mode="ConstantToProperty" constValue="clock" />
<map destProperty="defaultActionName" mode="ConstantToProperty" constValue="Approved" />
<map destProperty="defaultActionCaption" mode="ConstantToProperty" constValue="Согласовать" />
</mapping>
</source>
</sources>
</extension>
</extensions>
Здесь мы указываем имя сущности в сервисе интеграции, и с помощью фильтрации мы берем только те задания, которые в процессе и текущий пользователь может ее редактировать. В узлах «map» мы передаем в список объектов виджета, значения из свойств задания. Также с помощью строк:
<map destProperty="defaultActionName" mode="ConstantToProperty" constValue="Complete" />
<map destProperty="defaultActionCaption" mode="ConstantToProperty" constValue="Выполнить" />
Мы добавляем кнопку быстрого выполнения с текстом «Согласовать», при нажатии на которую у нас будет выполняться действие, которое привязано к кнопке «Approved» в карточке, если указанной кнопки не будет в карточке, то мы получим ошибку при нажатии. Если нам она не нужна, то просто удаляем эти строки. Вот пример с кнопкой подписания:
В целом, мы можем не создавать карточки для заданий, если их не будет, то автоматически будет использоваться карточка по умолчанию, но с ней мы ничего сделать не сможем, поэтому она лишь служит проверкой того, что с нашей карточкой что-то не так.
Все готово для обновления, смело обновляем решение. Файл решения целиком:
<?xml version="1.0" encoding="utf-8"?>
<solution id="e2e79fbe-8cd1-4062-aa6c-85df2a61d361" name="CertifySolution">
<authorization>
<allow roles="Employee" />
</authorization>
<title>CertifySolution</title>
<description />
<version>1.0.0.0</version>
<dataSchemeVersion>1.4.61</dataSchemeVersion>
<facilityGroups>
<facilityGroup id="9a8aed67-95dd-40fb-aed1-31a27bc694be" name="CertifyCore">
<icon>applications</icon>
<title>Удостоверения</title>
<priority>1</priority>
<isVisible>true</isVisible>
<collectAllNotifications>false</collectAllNotifications>
<views>
<view device="Web" container="StackPanel" name="statementsWeb">
<tileslots>
<tileslot tileName="Passes" size="Wide" />
</tileslots>
<orientation>Vertical</orientation>
<bounds>
<bound widgetName="CreatedCertify" />
</bounds>
</view>
<view device="Mobile" container="StackPanel" name="statementsMobile">
<tileslots>
<tileslot tileName="Passes" size="Wide" />
</tileslots>
<orientation>Vertical</orientation>
<bounds>
<bound widgetName="CreatedCertify" />
</bounds>
</view>
</views>
<facilities>
<include xmlns="http://www.w3.org/2001/XInclude" href="EssConfig\facilities\TransportPass.xml" />
</facilities>
</facilityGroup>
<facilityGroup id="23a3b93e-5292-47db-af2c-4af1f0949712" name="CertifyPasses">
<icon>applications</icon>
<title>Пропуска</title>
<priority>1</priority>
<isVisible>false</isVisible>
<collectAllNotifications>false</collectAllNotifications>
<views>
<view device="Web" container="StackPanel" name="statementsWeb">
<tileslots>
</tileslots>
<orientation>Vertical</orientation>
<bounds>
<bound widgetName="CreatePasses" />
</bounds>
</view>
<view device="Mobile" container="StackPanel" name="statementsMobile">
<tileslots>
</tileslots>
<orientation>Vertical</orientation>
<bounds>
<bound widgetName="CreatePasses" />
</bounds>
</view>
</views>
</facilityGroup>
</facilityGroups>
<objectCards>
<include xmlns="http://www.w3.org/2001/XInclude" href="EssConfig\objectCards\FreeApprovalAssignmentsObjCard.xml" />
</objectCards>
<extensions>
<extension path="Projections/activeWorks">
<sources>
<source path="/api/workitems/HRPro/IFreeApprovalAssignments?filter=((Status eq 'InProcess') or (IsRead eq false))">
<mapping>
<map sourceProperty="Created" destProperty="created" mode="PropertyToProperty" />
<map sourceProperty="Completed" destProperty="completed" mode="PropertyToProperty" />
<map sourceProperty="Deadline" destProperty="deadline" mode="PropertyToProperty" />
<map sourceProperty="Finished" destProperty="finished" mode="PropertyToProperty" />
<map sourceProperty="HasAttachments" destProperty="hasAttachments" mode="PropertyToProperty" />
<map sourceProperty="Id" destProperty="id" mode="PropertyToProperty" />
<map sourceProperty="IsExpired" destProperty="isExpired" mode="PropertyToProperty" />
<map sourceProperty="IsRead" destProperty="isRead" mode="PropertyToProperty" />
<map sourceProperty="Name" destProperty="name" mode="PropertyToProperty" />
<map sourceProperty="ProcessName" destProperty="processName" mode="PropertyToProperty" />
<map sourceProperty="Result" destProperty="result" mode="PropertyToProperty" converter="HRProConverter" />
<map sourceProperty="Status" destProperty="status" mode="PropertyToProperty" />
<map sourceProperty="System" destProperty="system" mode="PropertyToProperty" />
<map sourceProperty="Title" destProperty="title" mode="PropertyToProperty" />
<map sourceProperty="Icon" destProperty="icon" mode="ConstantToProperty" constValue="clock" />
<map destProperty="defaultActionName" mode="ConstantToProperty" constValue="Approved" />
<map destProperty="defaultActionCaption" mode="ConstantToProperty" constValue="Согласовать" />
</mapping>
</source>
</sources>
</extension>
</extensions>
<include xmlns="http://www.w3.org/2001/XInclude" href="EssConfig\widgets\CertifyWidgets.xml" />
<include xmlns="http://www.w3.org/2001/XInclude" href="EssConfig\tiles\CertifyTiles.xml" />
</solution>
Для теста создадим приказ и отправим его на свободное согласование, после отправки получаем следующее:
Если нажать на кнопку "Согласовать" получим ошибку:
В карточке задания нет такой кнопки, поэтому и выходит ошибка, исправим это позже.
Наша карточка задания выглядит так:
Пустовато, добавим информацию из задания в карточку. Для этого добавим узлы "field" таким образом
<fields>
<field caption="Тема" binding="title" />
</fields>
Это создаст нам строку с темой, подробнее здесь. Как же нам узнать, какие свойства можно использовать для отображения? Всё просто, в карточке нам доступны data и configuration, они берутся из модели указанного задания. Теперь посмотрим, что же там лежит. Для этого впишем в событие открытой карточки это:
<openedEventHandler><![CDATA[
console.log(data);
console.log(configuration);
]]></openedEventHandler>
Обновимся, зайдем в консоль и откроем карточку.
Снизу у нас конфигурация карточки, то есть ее элементы, а информация хранится в data, тут мы и видим title. Всю эту информацию можно использовать и располагать ее в fields. Свойства задания хранятся в requests. Исходя из этого, добавим тему и срок, если одно из свойств null, то оно просто не будет отображаться, оставим так.
<fields>
<field caption="Тема" binding="title" />
<field caption="Срок" binding="deadline" type="DateTime" converter="EssBase.convertDeadline"/>
</fields>
Время добавить кнопки, т.к. в RX три результата выполнения, то и добавим их все сюда.
<buttons>
<button name="Approved">
<caption>Согласовать</caption>
<clickEventHandler><![CDATA[]]></clickEventHandler>
</button>
<button name="ForRework">
<caption>Отправить на доработку</caption>
<clickEventHandler><![CDATA[]]></clickEventHandler>
</button>
<button name="Forward">
<caption>Переадресовать</caption>
<clickEventHandler><![CDATA[]]></clickEventHandler>
</button>
</buttons>
О кнопках можно почитать здесь. Для того, чтоб просто выполнить задание с каким-то результатом можно вызвать функцию:
ESS.actions.workProcessActions.changeWorkItemState(data, 'Complete', 'Approved').then(function () {
ESS.closeElement(sender);
ESS.toasts.showToast(ESS.L('Документы согласованы.'), 'success', true);
ESS.widgets.updateWidgets();
}).catch(function (error) {
ESS.toasts.showErrorToastWithLog(ESS.L('При выполнении задания произошла ошибка. Обратитесь к администратору.'), error.message);
});
Первый параметр указываем data, тут можно ничего не менять, если мы хотим, чтоб состояние задания изменилось, пишем вторым параметром «Complete», тут тоже менять ничего не нужно, а вот результат, с которым необходимо выполнить, пишем третьим параметром, у нас в RX результат называется «Approved», тем самым при нажатии на кнопку «Согласовать» мы выполняем задание с этим результатом. ESS.closeElement(sender); закрывает карточку, далее пишем сообщение об удачном выполнении и обновляем виджет со списком заданий.
Теперь заполним кнопку, которая отправит задание на доработку. Для этого добавим код, который откроет базовую услугу, которая выполняет задание и заполняет текст причиной доработки:
ESS.actions.facilityActions.runFacility({id: 'd3f7270a-2680-44fe-b231-1708b532beb3'}, {parameters: {ItemId: data.id, ItemName: data.name}}, sender);
Можно использовать и свою услугу, но эта отлично подходит, она относится к решению «HrProStatements» и называется «DocumentApprovalOnRework». В функции просто открываем услугу и заполняем параметры услуги значениями задания. Обратим внимание только на шаги услуги:
<facilityFlowScheme firstStepIndex="0" lastStepIndex="-1">
<flowSteps>
<flowStep index="0" name="ChangeWorkItemState" title="@.FACILITIES_REWORK_STEP_COMPLETE" type="ChangeWorkItemStateStep">
<property name="WorkItemName" binding="ItemName" />
<property name="TargetSystem">HRPro</property>
<property name="NextStepIndex">-1</property>
<property name="WorkItemId" binding="ItemId" />
<property name="State">Complete</property>
<property name="StateChangeReason">ForRework</property>
<property name="ActiveText" binding="Reason" />
</flowStep>
</flowSteps>
</facilityFlowScheme>
Здесь те же действия, указываем состояние задачи и результат. Обновляем и тестируем.
При нажатии на нашу кнопку «Отправить на доработку» открывается услуга. После заполнения отправляем и задание выполняется.
Сейчас наша карточка выглядит так:
<?xml version="1.0" encoding="utf-8"?>
<objectCard id="05bb7e05-d17b-455f-a9fa-783667f72c59" name="IFreeApprovalAssignments" header="Задание на согласование" icon="clock">
<authorization>
<allow roles="Employee"/>
</authorization>
<views>
<view deviceIdioms="Desktop" deviceOrientations="Landscape, Portrait">
<fields>
<field caption="Тема" binding="title" />
<field caption="Срок" binding="deadline" type="DateTime" converter="EssBase.convertDeadline"/>
</fields>
<content>
{{instruction}}
</content>
<attachmentsAreas>
</attachmentsAreas>
</view>
</views>
<buttons>
<button name="Approved">
<caption>Согласовать</caption>
<clickEventHandler><![CDATA[
ESS.actions.workProcessActions.changeWorkItemState(data, 'Complete', 'Approved').then(function () {
ESS.closeElement(sender);
ESS.toasts.showToast(ESS.L('Документы согласованы'), ESS.enums.ToastType.SUCCESS, true);
ESS.widgets.updateWidgets();
}).catch(function (error) {
ESS.toasts.showErrorToastWithLog(ESS.L('Возникла ошибка'), error.message);
});
]]></clickEventHandler>
</button>
<button name="ForRework">
<caption>Отправить на доработку</caption>
<clickEventHandler><![CDATA[
ESS.actions.facilityActions.runFacility({id: '670fb3fb-f8a9-4907-8655-e118a7601e6a'}, {parameters: {ItemId: data.id, ItemName: data.name}}, sender);
]]></clickEventHandler>
</button>
<button name="Forward">
<caption>Переадресовать</caption>
<clickEventHandler><![CDATA[
]]></clickEventHandler>
</button>
</buttons>
<openedEventHandler><![CDATA[
console.log(data);
console.log(configuration);
]]></openedEventHandler>
<closedEventHandler><![CDATA[]]></closedEventHandler>
</objectCard>
Теперь нам необходимо разработать свою услугу, которая будет переадресовывать задание:
<?xml version="1.0" encoding="utf-8"?>
<facility id="eb9fb4e7-da40-4598-9e79-cb7e32856b0f" name="Forward assignment">
<title>Переадресовать задание</title>
<description></description>
<facilityParameters>
<facilityParameter type="FacilityParameterString" name="ItemId" defaultValue="" isRequired="false" isCollection="false">
<maxLength>200</maxLength>
<multiline>false</multiline>
</facilityParameter>
<facilityParameter type="FacilityParameterString" name="ItemName" defaultValue="" isRequired="false" isCollection="false">
<maxLength>200</maxLength>
<multiline>false</multiline>
</facilityParameter>
<facilityParameter type="FacilityParameterReferenceRecord" name="Addressee" isRequired="true" isCollection="false">
<systemName>EssBase</systemName>
<referenceName>IAddressees</referenceName>
<displayRequisite>Name</displayRequisite>
<storeRequisite>Id</storeRequisite>
<filter>Status eq 'Active'</filter>
<isUserQuerySupported>true</isUserQuerySupported>
</facilityParameter>
<facilityParameter type="FacilityParameterDateTime" name="AddresseeDeadline" isRequired="true" isCollection="false">
<showAddresseeDeadline>false</showAddresseeDeadline>
</facilityParameter>
</facilityParameters>
<facilityFormItems>
<facilityFormItem type="FacilityFormTextblock">
<hint />
<content>
<![CDATA[
<strong>Выберете сотрудника, которому необходимо переадресовать задание и новый срок для него</span></strong>
]]>
</content>
</facilityFormItem>
<facilityFormItem type="FacilityFormParameter">
<parameterName>Addressee</parameterName>
<isHidden>false</isHidden>
<showHeaders>false</showHeaders>
<label>Переадресовать сотруднику</label>
</facilityFormItem>
<facilityFormItem type="FacilityFormParameter">
<parameterName>AddresseeDeadline</parameterName>
<isHidden>false</isHidden>
<showHeaders>false</showHeaders>
<label>Новый срок</label>
</facilityFormItem>
</facilityFormItems>
<facilityFlowScheme firstStepIndex="0" lastStepIndex="-1">
<flowSteps>
<!-- <flowStep index="0" name="ChangeWorkItemState" title="Выполнить задание" type="ChangeWorkItemStateStep">
<property name="WorkItemName" binding="ItemName" />
<property name="TargetSystem">CertifySolution</property>
<property name="NextStepIndex">-1</property>
<property name="WorkItemId" binding="ItemId" />
<property name="Addressee" binding="Addressee, Format={{'Id':{0}}}" type="Int"/>
<property name="AddresseeDeadline" binding="AddresseeDeadline" type="DateAddresseeDeadline"/>
<property name="State">Complete</property>
<property name="StateChangeReason">Forward</property>
</flowStep> -->
<flowStep index="0" name="SetInfo" title="Переадресовать и выполнить задание" type="ExecuteFunctionStep">
<property name="TargetSystem">CertifySolution</property>
<property name="NextStepIndex">-1</property>
<property name="ExecutionMethod">POST</property>
<property name="freeApprovalAssignmentId" binding="ItemId" type="Int"/>
<property name="addresseeId" binding="Addressee, Format={{'Id':{0}}}" type="Int"/>
<property name="addresseeDeadline" binding="AddresseeDeadline" type="Datetime"/>
<property name="FunctionName">HRProcesses.SendFreeAssignToForward</property>
</flowStep>
</flowSteps>
</facilityFlowScheme>
<closedEventHandler>
<![CDATA[
if (sender.performingAction === 'SUBMIT_FACILITY_PARAMETERS') {
ESS.closeElement(sender.parent);
ESS.widgets.updateWidgets();
}
]]>
</closedEventHandler>
<successMessage>Задание переадресовано</successMessage>
</facility>
Основные моменты по услугам были описаны в прошлой части, поэтому разбор услуги оставим на следующую часть. Хотелось бы обратить внимание на шаги. Изначально я предполагал использовать тот шаг, который сейчас остался в комментарии. В нем просто заполняются свойства и выполняется задание. Но в итоге я решил использовать альтернативный вариант с функцией:
/// <summary>
/// Переадресовывает задание сотруднику с указанным сроком выполнения.
/// </summary>
/// <param name="freeApprovalAssignmentId">Идентификатор задания.</param>
/// <param name="addresseeId">Идентификатор сотрудника для переадресации.</param>
/// <param name="addresseeDeadline">Срок выполнения задания адресатом.</param>
[Public(WebApiRequestType = RequestType.Post)]
public void SendFreeAssignToForward(long freeApprovalAssignmentId, long addresseeId, DateTime addresseeDeadline)
{
try
{
var assignment = Sungero.Docflow.FreeApprovalAssignments.GetAll(a => a.Id == freeApprovalAssignmentId).FirstOrDefault();
if (assignment == null)
{
throw new InvalidOperationException();
}
var addressee = Sungero.Company.Employees.GetAll(e => e.Id == addresseeId).FirstOrDefault();
if (addressee == null)
{
throw new InvalidOperationException();
}
assignment.Addressee = addressee;
assignment.AddresseeDeadline = addresseeDeadline;
assignment.Save();
assignment.Complete(Sungero.Docflow.FreeApprovalAssignment.Result.Forward);
}
catch (Exception)
{
throw new ApplicationException();
}
}
Здесь всё просто, заполняем свойства задания и выполняем его с нужным нам результатом. Для того чтоб у нас случайно не получились пустые ссылки, добавляем исключения, лучше генерировать исключения, чтоб исключить моменты, когда функция выполнилась, но по сути результата не было, услуга успешно завершится, что введет в заблуждение пользователя.
Так будет выглядеть услуга по переадресации задания:
В целом она работает, но ее еще стоит доработать.
Для отображения вложений в карточку необходимо добавить в узел «attachmentsAreas» еще узлы, чтоб это выглядело так.
<attachmentsAreas>
<attachmentsArea title="Документы">
<documentGroups>
<documentGroup>GroupName</documentGroup>
</documentGroups>
</attachmentsArea>
</attachmentsAreas>
Здесь указывается название группы вложений задания из RX. Если этой группы в задании нет, то ничего не отобразится, title можно указывать любой. Документы, у которых нет тела, тоже не будут отображаться. У задания на свободное согласование есть три группы: «ForApprovalGroup», «AddendaGroup», «OtherGroup», их и добавим.
Узлы будут выглядеть так:
<attachmentsAreas>
<attachmentsArea title="На согласование">
<documentGroups>
<documentGroup>ForApprovalGroup</documentGroup>
</documentGroups>
</attachmentsArea>
<attachmentsArea title="Приложения">
<documentGroups>
<documentGroup>AddendaGroup</documentGroup>
</documentGroups>
</attachmentsArea>
<attachmentsArea title="Дополнительно">
<documentGroups>
<documentGroup>OtherGroup</documentGroup>
</documentGroups>
</attachmentsArea>
</attachmentsAreas>
Карточка теперь выглядит так:
Если добавить документы в другие две группы, то они так-же появятся.
Итоговый вид xml карточки:
<?xml version="1.0" encoding="utf-8"?>
<objectCard id="05bb7e05-d17b-455f-a9fa-783667f72c59" name="IFreeApprovalAssignments" header="Задание на согласование" icon="clock">
<authorization>
<allow roles="Employee"/>
</authorization>
<views>
<view deviceIdioms="Desktop" deviceOrientations="Landscape, Portrait">
<fields>
<field caption="Тема" binding="title" />
<field caption="Срок" binding="deadline" type="DateTime" converter="EssBase.convertDeadline"/>
</fields>
<content>
{{instruction}}
</content>
<attachmentsAreas>
<attachmentsArea title="На согласование">
<documentGroups>
<documentGroup>ForApprovalGroup</documentGroup>
</documentGroups>
</attachmentsArea>
<attachmentsArea title="Приложения">
<documentGroups>
<documentGroup>AddendaGroup</documentGroup>
</documentGroups>
</attachmentsArea>
<attachmentsArea title="Дополнительно">
<documentGroups>
<documentGroup>OtherGroup</documentGroup>
</documentGroups>
</attachmentsArea>
</attachmentsAreas>
</view>
</views>
<buttons>
<button name="Approved">
<caption>Согласовать</caption>
<clickEventHandler>
<![CDATA[
ESS.actions.workProcessActions.changeWorkItemState(data, 'Complete', 'Approved').then(function () {
ESS.closeElement(sender);
ESS.toasts.showToast(ESS.L('Документы согласованы'), ESS.enums.ToastType.SUCCESS, true);
ESS.widgets.updateWidgets();
}).catch(function (error) {
ESS.toasts.showErrorToastWithLog(ESS.L('Возникла ошибка'), error.message);
});
]]>
</clickEventHandler>
</button>
<button name="ForRework">
<caption>Отправить на доработку</caption>
<clickEventHandler>
<![CDATA[
ESS.actions.facilityActions.runFacility({id: '670fb3fb-f8a9-4907-8655-e118a7601e6a'}, {parameters: {ItemId: data.id, ItemName: data.name}}, sender);
]]>
</clickEventHandler>
</button>
<button name="Forward">
<caption>Переадресовать</caption>
<clickEventHandler>
<![CDATA[
ESS.actions.facilityActions.runFacility({id: 'eb9fb4e7-da40-4598-9e79-cb7e32856b0f'}, {parameters: {ItemId: data.id, ItemName: data.name}}, sender);
]]>
</clickEventHandler>
</button>
</buttons>
</objectCard>
На этом закончим, в следующей части рассмотрим подробную работу с кодом.
Авторизуйтесь, чтобы написать комментарий