Данная инструкция поможет вам научиться создавать плагины в Platrum.
Для работы с плагинами вам потребуется:
После её прочтения вы:
- Научитесь устанавливать плагины в проект
- Узнаете об устройстве плагинов
- Создадите свой первый плагин
Управление плагинами осуществляется на странице настроек /settings/page/plugins/list.
Для доступа к этой странице в проекте вам необходимо обладать правами Администрирование плагинов (обратитесь к администратору проекта в случае отсутствия доступа).
Во время разработки плагина для проверки промежуточного результата работы неудобно каждый раз обращаться к веб-интерфейсу для загрузки текущей версии плагина.
Вместо интерфейса вы можете воспользоваться скриптом загрузки upload.sh, который располагается в корне этого репозитория.
Для работы скрипта вам потребуется установить следующие переменные окружения:
PLATRUM_API_KEY- апи ключ вашего проектаPLATRUM_API_PROJECT- хост вашего проекта
- Установить wsl 2. Это необходимо, чтобы вы могли удобно работать с linux прямо из windows. Установка происходит легко и быстро. Если ранее вы не имели опыта работа с linux, рекомендуем вам следовать инструкциям по установку с сайта Microsoft Для установки введите в терминале:
wsl --install
- После установки wsl2 и дистрибутива linux последующую работу нужно выполнять из терминала установленного вами дистрибутива.
- Установите архиватор.
sudo apt install zip
-
Далее необходимо установить docker для того, чтобы сборка приложения происходила корректно. Пошагавая инструкция по установке docker находится здесь
-
Склонируйте репозиторий, перейдите в его директорию.
git clone https://github.com/platrum/plugin-example
cd plugin-example
- Войдите в режим суперпользователя. Он необходим далее, чтобы сборка плагина прошла успешно.
sudo bash
- Настройте переменные окружения. В команды ниже вставьте api ключ вашего проетка вместо 'api-key' и хост вашего проекта вместо 'host'.
export PLATRUM_API_KEY='api-key'
export PLATRUM_API_PROJECT='host'
- Запустите сборку приложения из режима суперпользователя (администратора)
./upload.sh example
Плагин можно добавить в модуль, где доступна верхняя панель навигации. Для этого необходимо в файл плагина app.js добавить следующие настройки:
export default {
slots: {
'page.layout.navbar.finance': [
{
name: 'Пример страницы',
route: 'plugin-example.examplePage',
},
],
},
};
Плагин имеет простую структуру: он состоит из файлов конфигурации и файлов фронтенда, которые находятся в корневых папках config и frontend соответственно.
Далее мы познакомимся с файлами конфигурации и узнаем о том, как устроен frontend.
Для гибкой настройки разных аспектов вашего приложения, плагины используют файлы конфигурации. Рассмотрим список доступных файлов:
main.yml- обязательный базовый файл конфигурацииroutes.yml- список маршрутовschema.yml- схема данныхaccess.yml- определение доступовtranslations.yml- переводы
Для того чтобы ваш плагин заработал, необходимо добавить базовый файл конфигурации config/main.yml.
В этом файле определяются системное и человекочитамое имена плагина, краткое описание функциональности, а так же перечисляются зависимости от модулей Platurm:
module: plugin-example
name: Пример плагина
description: Описание плагина
dependencies:
- ui
- ui-collection
- ui-element
- companyГде:
module- системное название плагина (наличиеplugin-префикса обязательно)name- человекочитаемое имя плагинаdescription- краткое описание функциональности плагинаdependencies- перечисление зависимостей плагина от системных модулейPlatrum
Список доступных модулей для dependencies:
ui,ui-collection- модуль, предоставляющие стандартную библиотеку ui компонентовPlatrumui-element- модуль, предоставляющий библиотеку компонентов ui-elementutils- модуль, предоставляющий базовые утилитыlists- модуль для работы со спискамиuser- модуль для работы с профилями пользователейcompany- модуль компанииfinance- модуль финансовfinplan- модуль фин. планированияnotification- модуль уведомленийorgschema- модуль оргсхемыpassword- модуль паролейquality- модуль контроля качестваsettings- модуль настроекstore- модуль складаtasks- модуль задачwiki- модуль базы знанийworkdesk- модуль рабочего стола
В файле config/routes.yml определяются маршруты вашего frontend приложения.
Маршрут - это то, что сопоставляет путь до страницы и frontend компонент страницы.
Без добавления маршрутов в этот файл страницы вашего приложения не будут доступны.
Пример конфигурации frontend маршрутов:
frontend:
examplePage:
pattern: /example
title: Example page
examplePage2:
pattern: /example2
title: Example page 2Где:
frontend- ключевое слово. Объявляемые маршруты должны располагаться на уровень ниже, как в примереexamplePage- id страницы, используемый для сопоставления пути и компонента страницыpattern- путь до страницы, отображаемый в адресной строке браузераtitle- заголовок страницы, отображаемый во вкладке браузера
Для работы с данными вам необходимо описать сущность, с которой вы собираетесь работать, в файле config/schema.yml.
Возможности для манипулирования данными мы рассмотрим позднее.
Ниже приведен пример добавления сущности example_item с набором полей fields:
example_item:
fields:
user_id:
type: string
text:
type: string
date:
type: date
string_field:
type: string
bool_field:
type: bool
int_field:
type: int
map_field:
type: mapДоступные типы полей:
int- целочисленное значениеstring- строковое значениеbool- булево значениеarray- массив значенийmap- коллекция ключ/значениеdate- дата вISOформате
Платформа позволяет проверять пользовательские доступы.
Для работы с доступами вам потребуется описать их в файле config/access.yml:
Пример добавления доступа:
user_creation:
name: Создание пользователей
user_deletion:
name: Удаление пользователейГде:
user_creation- системное название доступа, которому вы будете обращаться при проверке доступаСоздание пользователей- человекочитаемое название доступа
После объявления доступа, он появится на странице доступов /settings/page/common/access.
Платформа позволяет быть вашему плагину мультиязычным. Переводы хранятся в файле config/translations.yml.
Также в этом файле объявляется имя вашего плагина, показываемое пользователям.
Пример:
ru:
module.name: Пример плагина
en:
module.name: Plugin example
Выберите пользователя: Select user
Заполните дату: Fill in the dateГде:
ru- код языка. На данный момент доступно три языка:ru,en,cnmodule.name- имя вашего плагинаВыберите пользователя: Select user- фраза и её перевод
Для перевода фразы, достаточно использовать глобально зарегистрированную функцию t.
Пример использования функции t во vue компоненте:
<template>
<div>{{ t('Выберите пользователя') }}</div>
</template>Для разработки frontend'а используется фреймворк vue версии 2.5.
Так же вам доступна библиотека компонентов element версии 2.9.
Для использования библиотеки вам потребуется добавить модуль ui-element в dependencies в базовом файле конфигурации config/main.yml.
Далее рассмотрим базовые frontend файлы:
В этом файле вы можете определить функцию initHook, которая будет вызываться при каждой загрузке страницы проекта.
Так же этот файл позволяет вам расширять функциональность модулей Platrum, добавляя свои страницы (более подробно мы рассмотрим этот механизм позже):
export default {
initHook($platform) {
console.log('init plugin');
},
slots: {
'company.header': [
{
name: 'Пример страницы',
route: 'plugin-example.examplePage',
},
],
},
};Этот файл служит для экспортирования страниц приложения. Пример экспортирования:
import examplePage from './pages/examplePage';
export default {
examplePage,
};Обратите внимание: экспортируемое название должно совпадать с id маршрута, который должен сопоставляться странице.
Для страницы examplePage правильное определение маршрута выглядит так:
frontend:
examplePage: # id маршрута
pattern: /example
title: Example pageВ данном разделе мы пошагово создадим с вами базовый плагин, который вы сможете использовать для дальнейшей разработки под собственные нужды. Что мы сделаем в рамках данного руководства:
- Добавим пустую страницу в меню
компании - Оживим страницу, добавив в неё верстку
- Сконфигурируем фильтры и колонки таблицы
- Научимся управлять доступами
- Узнаем, как манипулировать данными
На данном шаге мы создадим хоть и пустую, но работающую страницу в проекте.
Для добавления страницы в меню компании нам нужно:
- создать
vueкомпонент страницы - экспортировать страницу в
frontend/pages.js - объявить маршрут в
config/routes.yml - добавить имя модуля в
config/translactions.yml - добавить созданную страницу в меню модуля
компании
Создадим пока что пустую страницу в папке frontend/pages/examplePage/index.vue.
При разработке плагина вы можете придерживаться любой удобной вам структуры файлов, единственное ограничение - все ваши компоненты должны находится в корневой папке плагина frontend.
<template>
<company-layout>
<div>Пустая страница</div>
</company-layout>
</template>Здесь company-layout - это компонент-разметка, необходимый нам для добавления на страницу меню модуля.
Без него на нашей странице отсутствовало бы меню компании.
import examplePage from './pages/examplePage';
export default {
examplePage,
};frontend:
examplePage:
pattern: /example
title: Пример страницыДля того чтобы во вкладке браузера отображалось название нашего модуля, определим имя модуля module.name в config/translactions.yml:
ru:
module.name: Пример плагина
en:
module.name: Plugin exampleМодули платформы, например модуль компании, поддерживают добавление страниц плагинов в своё меню.
Чтобы добавить страницу в меню модуля, нам потребуется объявить свойство slots в экспортируемом по умолчанию объекте в frontend/app.js
export default {
slots: {
'company.header': [
{
name: 'Пример страницы',
route: 'plugin-example.examplePage',
},
],
},
};Где:
company.header- системное название слота менюname- заголовок вкладки менюroute- id маршрута страницы, который мы добавили ранее. Обратите внимание на то, что к id маршрута нужно добавлять имя вашего модуля, указанное вconfig/main.ymlв ключеmodule.
Загружаем наш промежуточный результат, используя скрипт upload.sh:
./upload.sh exampleВ следующих разделах не будет упоминаться использование команды выше, но имейте в виду - для того, чтобы ваши изменения применились, вам необходимо повторно загружать код плагина.
После загрузки плагина, нам станет доступна страница по пути /plugin-example/example:
На прошлом шаге мы создали с вами пусть и открывающуюся, но не очень полезную страницу. В этом шаге мы оживим эту страницу, добавив:
Добавим базовую верстку на ранее созданную страницу frontend/pages/examplePage/index.vue.
На этой странице мы уже добавили компонент-разметку company-layout.
Этот компонент предоставляет следующие слоты для разметки:
- Слот
toolbarпозволяет добавлять разметку в верхнее меню модуля. В него мы добавим кнопку для открытия сайдбара. - Слот
sidebarпозволяет добавлять разметку в левую часть страницы. В этом примере мы добавим в него панель с фильтрами. - Слот по умолчанию, в котором размещается основной контент страницы, в нашем примере это будет таблица и сайдбар с формой для заполнения данных.
В разметке мы будем использовать компоненты предоставляемые библиотеками ui-element и ui-collection:
<template>
<company-layout>
<div slot="toolbar">
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
/>
</div>
<ui-collection-panel-filter
slot="sidebar"
v-model="filter"
:settings="filterSettings"
/>
<ui-collection-panel-table
v-loading="isLoading"
:columns="columns"
:rows="entities"
clickable
empty-text="Пусто!"
selectable
without-settings
/>
</company-layout>
</template>
<script>
export default {
data() {
return {
isLoading: false,
filter: {},
filterSettings: [],
columns: [],
entities: [],
}
},
};
</script>Здесь мы добавили:
el-buttonкнопку в слотtoolbar- панель с фильтрами
ui-collection-panel-filterв слотsidebar - таблицу
ui-collection-panel-tableв слот по умолчанию.
Вот, что у нас поучилось:
Добавим сайдбар с формой для заполнения данных:
<template>
<ui-sidebar
v-model="isSidebarVisible"
title="Добавить сущность"
class="sidebar"
@hide="resetState"
>
<el-form class="content" ref="form" :model="item" label-width="150px">
<el-form-item
label="Пользователь"
prop="user_id"
:rules="[{ required: true, message: 'Выберите пользователя'}]"
>
<orgschema-user-selector v-model="item.user_id" :font-size="14" />
</el-form-item>
<el-form-item label="Текстовое поле" prop="text">
<ui-text-editor v-model="item.text" class="text-editor" bordered />
</el-form-item>
<el-form-item
prop="date"
label="Дата"
:rules="[{ required: true, message: 'Заполните дату'}]"
>
<el-date-picker
v-model="item.date"
type="date"
placeholder="Заполните дату"
size="small"
/>
</el-form-item>
<el-form-item label="Строковое поле" prop="string_field">
<el-input v-model="item.string_field" size="small" />
</el-form-item>
<el-form-item label="Булево поле" prop="bool_field">
<el-checkbox v-model="item.bool_field" />
</el-form-item>
<el-form-item label="Числовое поле" prop="int_field">
<el-input-number v-model="item.int_field" size="small" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="small"
@click="submit"
>
<span>Сохранить</span>
</el-button>
</el-form-item>
</el-form>
</ui-sidebar>
</template>
<script>
export default {
props: {
value: Object,
},
data() {
return {
isSidebarVisible: false,
item: {},
};
},
watch: {
value: {
immediate: true,
handler(val) {
this.item = $utils.object.clone(val);
},
},
},
methods: {
open() {
this.isSidebarVisible = true;
},
async submit() {
try {
await this.$refs.form.validate();
} catch (e) {
this.$uiNotify.error('Заполните необходимые поля');
return;
}
this.$emit('input', this.item);
this.resetState();
},
resetState() {
this.isSidebarVisible = false;
this.$emit('hide');
},
},
};
</script>
<style lang="less" scoped>
.sidebar {
top: 0 !important;
width: 550px;
background: white;
.content {
padding-right: 30px;
.text-editor {
line-height: 20px;
}
}
}
</style>Доработаем страницу frontend/pages/examplePage/index.vue для того, чтобы мы могли открыть сайдбар по нажатию на кнопку +, расположенную в верхнем правом углу страницы:
<template>
<company-layout>
<div slot="toolbar">
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
@click="openSidebar"
/>
</div>
<ui-collection-panel-filter
slot="sidebar"
v-model="filter"
:settings="filterSettings"
/>
<ui-collection-panel-table
v-loading="isLoading"
:columns="columns"
:rows="entities"
clickable
empty-text="Пусто!"
selectable
without-settings
/>
<sidebar
ref="sidebar"
v-model="selectedItem"
@hide="handleSidebarHide"
/>
</company-layout>
</template>
<script>
import Sidebar from './Sidebar';
export default {
components: {
Sidebar,
},
data() {
return {
isLoading: false,
filter: {},
filterSettings: [],
columns: [],
entities: [],
selectedItem: this.createDefaultItem(),
};
},
methods: {
openSidebar() {
this.$refs.sidebar.open();
},
handleSidebarHide() {
this.selectedItem = this.createDefaultItem();
},
createDefaultItem() {
return {
user_id: null,
text: null,
date: null,
string_field: null,
bool_field: null,
int_field: null,
};
},
},
};
</script>В итоге мы получили сайдбар с формой для заполнения данных:
В добавленной ранее таблице сейчас не хватает колонок, а в левой панели - фильтров. В этом шаге мы рассмотрим, как сконфигурировать таблицу и панель с фильтрами.
Используемый нами компонент таблицы ui-collection-panel-table, позволяет гибко настраивать свои колонки.
Добавим описание колонок в data свойство columns компонента frontend/pages/examplePage/index.vue:
columns: [
{
name: '#',
id: 'id',
width: '50px',
format: 'id',
sortable: true,
clickable: true,
sortType: 'number',
},
{
id: 'user_id',
width: '250px',
name: 'Пользователь',
format: 'user',
sortable: true,
},
{
id: 'text',
name: 'Текст',
format: 'text',
},
{
component: 'ui-readable-date-time',
name: 'Дата',
id: 'date',
sortable: true,
sortType: 'date',
getComponentAttrs: row => ({ date: row.date, capitalizeFirstLetter: true }),
},
{
id: 'string_field',
name: 'Строковое поле',
sortable: true,
},
{
id: 'bool_field',
name: 'Булево поле',
width: '100px',
format: 'bool',
align: 'right',
sortable: true,
},
{
id: 'int_field',
name: 'Числовое поле',
width: '100px',
format: 'number',
sortType: 'number',
align: 'center',
sortable: true,
},
]Рассмотрим общую структуру фильтра:
id- системное название поляname- название колонкиwidth- ширина колонкиformat- форматирование значения поля, доступные значения:id,user,image,bool,number,textalign- выравнивание ячейки таблица, возможные значения:left,center,rightsortable- возможность сортировки таблицы по значениям колонкиsortType- тип сортировки, доступные значенияnumber,date,boolean,stringcomponent- имя или объект компонента для рендеринга ячейкиgetComponentAttrs- функция для получения входных параметров компонента
Компонент панели фильтров ui-collection-panel-filter позволяет настраивать своё содержимое.
Добавим фильтры в data свойство filterSettings компонента страницы:
filterSettings: [
{
type: 'in',
component: 'orgschema-user-selector',
label: 'Пользователь',
name: 'user_id',
props: {
multiple: true,
'show-deleted': true,
},
},
{
type: '~=',
label: 'Текстовое поле',
name: 'text',
},
{
type: '=',
label: 'Строковое поле',
name: 'string_field',
},
{
type: 'date',
label: 'Дата',
name: 'date',
},
]Здесь мы добавили:
- фильтр по пользователям
- фильтр по текстовым полям
textиstring_field - фильтр по дате
date
Рассмотрим структуру фильтра:
type- тип фильтра, возможные значения:=,in,~=,datelabel- человекочитаемое название фильтраname- имя фильтруемого поляcomponent- имя или объект компонентаprops- входные параметры для компонента
В этом шаге мы дорабатывали data свойства компонента frontend/pages/examplePage/index.vue.
Полученный итоговый код:
data() {
return {
isLoading: false,
filter: {},
filterSettings: [
{
type: 'in',
component: 'orgschema-user-selector',
label: 'Пользователь',
name: 'user_id',
props: {
multiple: true,
'show-deleted': true,
},
},
{
type: '~=',
label: 'Текстовое поле',
name: 'text',
},
{
type: '~=',
label: 'Строковое поле',
name: 'string_field',
},
{
type: 'date',
label: 'Дата',
name: 'date',
format: 'date',
},
],
columns: [
{
name: '#',
id: 'id',
width: '50px',
format: 'id',
sortable: true,
clickable: true,
sortType: 'number',
},
{
id: 'user_id',
width: '250px',
name: 'Пользователь',
format: 'user',
sortable: true,
},
{
id: 'text',
name: 'Текст',
format: 'text',
is_hidden_by_default: true,
},
{
component: 'ui-readable-date-time',
name: 'Дата',
id: 'date',
sortable: true,
sortType: 'date',
getComponentAttrs: row => ({ date: row.date, capitalizeFirstLetter: true }),
},
{
id: 'string_field',
name: 'Строковое поле',
sortable: true,
},
{
id: 'bool_field',
name: 'Булево поле',
width: '100px',
format: 'bool',
align: 'right',
sortable: true,
},
{
id: 'int_field',
name: 'Числовое поле',
width: '100px',
format: 'number',
sortType: 'number',
align: 'center',
sortable: true,
},
],
entities: [],
selectedItem: this.createDefaultItem(),
};
}Посмотрим на итоговый результат:
В данном шаге мы научимся:
- конфигурировать доступы
- проверять их на фронтенд страницах
- настраивать доступы к отдельным сущностям нашего приложения
Для создания доступа объявите доступ в файле конфигурации config/access.yml:
item_creation:
name: Создание сущностейПосле объявления доступа, он появится на странице доступов /settings/page/common/access.
Подробнее о доступах.
После объявления доступа, мы можем начать с ним работать.
В нашем плагине мы будем показывать кнопку добавления данных только для пользователей с доступом plugin-example.item_creation:
<el-button
v-if="$platform.access.hasAccess('plugin-example.item_creation')"
type="primary"
size="mini"
icon="el-icon-plus"
@click="openSidebar"
/>Для проверки доступов мы использовали сервис $platform.access.hasAccess, предоставляемый платформой.
Этот сервис доступен в любом определяемом вами vue компоненте.
Обратите внимание на то, что полное имя доступа при проверке состоит из системного имени плагина plugin-example и имени доступа item_creation.
Кроме общих доступов, рассмотренных выше, платформа позволяет точечно настраивать доступы к каждой сущности в вашем приложении. На этом шаге мы:
const permission = {
action: 'view',
block_id: 1,
user_id: null,
allow_nested: null,
allow_everyone: null,
allow_managers: null,
}Где:
action- название действия (может быть любым)block_id- ID должностиuser_id- ID пользователяallow_nested- доступ для подчиненныхallow_everyone- доступ для всех пользователейallow_managers- доступ для руководителей
- Доступ на чтение для пользователей с должностью c ID =
2
{
"action": "read",
"block_id": 2
}- Доступ на редактирование пользователю с ID =
1a8642fa90a52d5103c114f5b1a30aae
{
"action": "edit",
"user_id": "1a8642fa90a52d5103c114f5b1a30aae"
}- Доступ на чтение для всех пользователей
{
"action": "read",
"allow_everyone": true
}- Доступ на чтение для всех руководителей пользователя с ID =
1a8642fa90a52d5103c114f5b1a30aae
{
"action": "read",
"user_id": "1a8642fa90a52d5103c114f5b1a30aae",
"allow_managers": true
}- Доступ на редактирование для должности с ID =
2и для всех подчиненных этой должности
{
"action": "read",
"block_id": 2,
"allow_nested": true
}В данном разделе мы:
- узнаем, как сохранять, загружать и удалять данные
- рассмотрим синтаксис фильтров, которые пригодятся при загрузке или удалении данных.
- добавим данные на нашу страницу
Платформа предоставляет api для crud операций над данными.
Апи методы вызываются через сервис this.$modules.plugins.api, который доступен в любом определяемом вами компоненте.
Далее рассмотрим примеры возможных crud функций, которые вы можете определить в компонентах вашего плагина:
Для сохранения одной или нескольких сущностей вы можете использовать методы storeOne или storeMany соответственно:
/**
* @param {object} item
* @return {Promise<object>}
*/
async function storeItem(item) {
return await this.$modules.plugins.api.storeOne('plugin-example.example_item', item);
}
/**
* @param {object[]} items
* @return {Promise<object[]>}
*/
async function storeItems(items) {
return await this.$modules.plugins.api.storeMany('plugin-example.example_item', items);
}Для получения ранее сохраненных данных используйте метод select:
/**
* @param {array} filter
* @return {Promise<object[]>}
*/
async function loadItems(filter) {
return await this.$modules.plugins.api.select('plugin-example.example_item', filter);
}Для удаления данных вы можете использовать метод delete:
/**
* @param {object} item
* @return {Promise<void>}
*/
async function deleteItem(item) {
await this.$modules.plugins.api.delete('plugin-example.example_item', [
['id', '=', item.id],
]);
}Методы select и delete поддерживают работу с фильтрами.
Фильтр это массив, состоящий из трех частей: имени поля, оператора и значения:
['field_name', 'operator', 'value'], где:
field_name- имя поля, описанное вconfig/schema.ymlв массивеfieldsoperator- оператор, используемый при фильтрации. Поддерживаемые значения:=,>,>=,<,<=,in,not_invalue- искомое значение поля
Фильтры могут объединяться в массив, образуя and условие:
this.$modules.plugins.api.select('plugin-example.example_item', [
['user_id', 'in', ['userId1', 'userId2']],
['date', '>=', '2021-08-15T21:00:00Z'],
['date', '<=', '2021-08-16T20:59:59Z'],
['int_field', '=', 100],
]);Если вам требуется найти данные, которые подходят хотя бы под один фильтр, вы можете воспользоваться or оператором:
this.$modules.plugins.api.select('plugin-example.example_item', [
{
or: [
['user_id', 'in', ['userId1', 'userId2']],
['int_field', '=', 100],
],
},
]);В этом шаге мы начнем работать с данными. Для этого нам потребуется:
Создадим сущность example_item в файле config/schema.yml с полями:
example_item:
fields:
user_id:
type: string
text:
type: string
date:
type: date
string_field:
type: string
bool_field:
type: bool
int_field:
type: intНа этом шаге мы начнем сохранять данные.
Перед вызовом метода сохранения, мы обязаны установить доступы сущности в свойстве access_rules.
Заметьте, что мы не определяли свойство access_rules в схеме выше.
Это свойство доступно по умолчанию во всех создаваемых вами сущностях.
Добавим метод setItemAccessRules, в котором мы установим доступ на просмотр для всех пользователей и доступ на редактирование для создавшего запись пользователя.
Для получения user_id текущего пользователя, воспользуемся $modules.user.profile.getCurrent().user_id.
Для сохранения сущности, добавим метод storeItem:
methods: {
setItemAccessRules(item) {
return {
...item,
access_rules: [
{ action: 'view', allow_everyone: true },
{ action: 'edit', user_id: this.$modules.user.profile.getCurrent().user_id },
],
};
},
async storeItem(item) {
try {
// устанавливаем доступы, используя метод setItemAccessRules
const itemWithAccessRules = this.setItemAccessRules(item);
// вызываем апи метод сохранения
const storedItem = await this.$modules.plugins.api.storeOne('plugin-example.example_item', itemWithAccessRules);
// добавляем сохранные данные в состояние компонента
this.entities.push(storedItem);
} catch (e) {
this.$uiNotify.error('Ошибка при сохранении');
throw e;
}
},
}Для загрузки данных, определим метод loadItems в компоненте frontend/pages/examplePage/index.vue:
methods: {
async loadItems({ dbFormatFilter = [] }) {
// добавляем loader на время загрузки данных
this.isLoading = true;
// вызываем апи метод загрузки данных
this.entities = await this.$modules.plugins.api.select(entityName, dbFormatFilter);
// по окончанию загрузки убираем loader
this.isLoading = false;
},
}Так же добавим загрузку данных по нажатию на кнопку фильтровать в панели фильтров:
<ui-collection-panel-filter
slot="sidebar"
v-model="filter"
:settings="filterSettings"
@submit="loadItems"
/>Напоследок добавим возможность удаления созданных данных. Для этого определим метод удаления:
methods: {
async deleteItem(row) {
// вызываем апи метод удаления
await this.$modules.plugins.api.delete('plugin-example.example_item', [
['id', '=', row.id],
]);
// после успешного вызовам апи, удаляем сущность из сотояния компонента
this.entities = this.entities.filter(entity => entity.id !== row.id);
// показываем уведомление об успешном удалении
this.$uiNotify.success('Сущность удалена');
},
}и добавим кнопку в каждую строку таблицы для вызова метода удаления:
<ui-collection-panel-table
v-loading="isLoading"
:columns="columns"
:rows="entities"
clickable
empty-text="Пусто!"
selectable
without-settings
show-actions
>
<div class="actions" slot="actions" slot-scope="{ row }">
<el-button type="danger" icon="el-icon-delete" size="mini" circle @click="deleteItem(row)" />
</div>
</ui-collection-panel-table>Итоговый код страницы frontend/pages/examplePage/index.vue после проделанных шагов:
<template>
<company-layout>
<div slot="toolbar">
<el-button
v-if="$platform.access.hasAccess('plugin-example.user_creation')"
type="primary"
size="mini"
icon="el-icon-plus"
@click="openSidebar"
/>
</div>
<ui-collection-panel-filter
slot="sidebar"
v-model="filter"
:settings="filterSettings"
@submit="loadItems"
/>
<ui-collection-panel-table
v-loading="isLoading"
:columns="columns"
:rows="entities"
clickable
empty-text="Пусто!"
selectable
without-settings
show-actions
>
<div class="actions" slot="actions" slot-scope="{ row }">
<el-button type="danger" icon="el-icon-delete" size="mini" circle @click="deleteItem(row)" />
</div>
</ui-collection-panel-table>
<sidebar
ref="sidebar"
v-model="selectedItem"
@input="storeItem"
@hide="handleSidebarHide"
/>
</company-layout>
</template>
<script>
import Sidebar from './Sidebar';
export default {
components: {
Sidebar,
},
async created() {
await this.loadItems({});
},
data() {
return {
isLoading: false,
filter: {},
filterSettings: [
{
type: 'in',
component: 'orgschema-user-selector',
label: 'Пользователь',
name: 'user_id',
props: {
multiple: true,
'show-deleted': true,
},
},
{
type: '~=',
label: 'Текстовое поле',
name: 'text',
},
{
type: '~=',
label: 'Строковое поле',
name: 'string_field',
},
{
type: 'date',
label: 'Дата',
name: 'date',
format: 'date',
},
],
columns: [
{
name: '#',
id: 'id',
width: '50px',
format: 'id',
sortable: true,
clickable: true,
sortType: 'number',
},
{
id: 'user_id',
width: '250px',
name: 'Пользователь',
format: 'user',
sortable: true,
},
{
id: 'text',
name: 'Текст',
format: 'text',
is_hidden_by_default: true,
},
{
component: 'ui-readable-date-time',
name: 'Дата',
id: 'date',
sortable: true,
sortType: 'date',
getComponentAttrs: row => ({ date: row.date, capitalizeFirstLetter: true }),
},
{
id: 'string_field',
name: 'Строковое поле',
sortable: true,
},
{
id: 'bool_field',
name: 'Булево поле',
width: '100px',
format: 'bool',
align: 'right',
sortable: true,
},
{
id: 'int_field',
name: 'Числовое поле',
width: '100px',
format: 'number',
sortType: 'number',
align: 'center',
sortable: true,
},
],
entities: [],
selectedItem: this.createDefaultItem(),
};
},
methods: {
openSidebar() {
this.$refs.sidebar.open();
},
handleSidebarHide() {
this.selectedItem = this.createDefaultItem();
},
createDefaultItem() {
return {
user_id: null,
text: null,
date: null,
string_field: null,
bool_field: null,
int_field: null,
};
},
setItemAccessRules(item) {
return {
...item,
access_rules: [
{ action: 'view', allow_everyone: true },
{ action: 'edit', user_id: this.$modules.user.profile.getCurrent().user_id },
],
};
},
async storeItem(item) {
try {
const itemWithAccessRules = this.setItemAccessRules(item);
const storedItem = await this.$modules.plugins.api.storeOne('plugin-example.example_item', itemWithAccessRules);
this.entities.push(storedItem);
} catch (e) {
this.$uiNotify.error('Ошибка при сохранении');
throw e;
}
},
async loadItems({ dbFormatFilter = [] }) {
this.isLoading = true;
this.entities = await this.$modules.plugins.api.select('plugin-example.example_item', dbFormatFilter);
this.isLoading = false;
},
async deleteItem(row) {
await this.$modules.plugins.api.delete('plugin-example.example_item', [
['id', '=', row.id],
]);
this.entities = this.entities.filter(entity => entity.id !== row.id);
this.$uiNotify.success('Сущность удалена');
},
},
};
</script>
Итоговый вид нашей страницы:
Данное руководство подошло к концу.
Теперь вы знаете, как:
- загружать плагины в проект
- создавать frontend страницы
- контролировать доступы
- работать с данными
Весь код, который мы рассматривали в выше, доступен в этом репозитории. Вы можете использовать его, как основу для создания ваших плагинов.
Желаем успехов в создании плагинов Platrum!




