@ecosystem/ui-core (0.4.0)
Installation
@ecosystem:registry=npm install @ecosystem/ui-core@0.4.0"@ecosystem/ui-core": "0.4.0"About this package
@ecosystem/ui-core
Общая Vue-библиотека generic entity-UI платформы Ecosystem: готовые страницы «список» и «карточка» поверх entity-API бэкенда (Ecosystem.Core.Web.Entities: /api/entities, /api/viewconfig/{entity}/*, /api/listview/{entity}, /api/crud/{entity}). Выросла из демо-SPA Resources/ (удалено, история — в git).
Форма поставки: пакет-источник
Пакет не собирается сам — exports указывает на src/index.ts, компиляцию .ts/.vue выполняет Vite-сборка приложения-потребителя (штатный подход для внутренних библиотек). Проверка типов пакета: npm run type-check (vue-tsc).
Подключение (из SPA в этом репозитории):
// package.json приложения
"dependencies": {
"@ecosystem/ui-core": "file:../packages/ui-core"
}
Настройка HTTP (обязательно, один раз при старте приложения)
Библиотека не знает, как приложение аутентифицируется, — настройте клиент:
import { configureEntityHttp } from '@ecosystem/ui-core'
// Вариант А: bearer-хост (SPA с OIDC-токенами)
configureEntityHttp({
baseUrl: config.apiBaseUrl, // '' = same-origin
getAccessToken: () => auth.getAccessToken(),
onUnauthorized: () => auth.signIn(location.pathname + location.search)
})
// Вариант Б: куки-хост (SPA IdentityServer)
configureEntityHttp({
credentials: 'include', // кука сессии
onUnauthorized: () => router.push({ name: 'login' })
// xhrHeader включён по умолчанию: X-Requested-With на мутациях — анти-CSRF куки-хостов
})
Маршруты-контракт
Компоненты навигируют по именам маршрутов listview и detailview — приложение обязано объявить их со следующими props:
import { ListViewPage, DetailViewPage } from '@ecosystem/ui-core'
const routes = [
{ path: '/listview/:entity', name: 'listview', component: ListViewPage, props: true },
{ path: '/detailview/:entity/:id', name: 'detailview', component: DetailViewPage, props: true }
]
Открытие строки (и «Создать») в ListViewPage показывает модальную карточку (DetailViewModal) поверх списка; кнопка «развернуть» в её шапке ведёт на отдельную страницу detailview — поэтому маршрут обязателен по-прежнему (плюс он же — deep-link на запись). Страница и модалка — хосты одной формы DetailViewForm (экспортируется отдельно: пропсы entity/id/variant, события config-loaded/created/saved/deleted) — при необходимости встраивайте её в свои сценарии.
Список доступных пользователю сущностей для навигации — entitiesApi.getEntities().
Список (ListViewPage)
Пропсы: entity (обязательный), actions?: ListAction[] (действия этого экземпляра; совпадение ключа замещает глобальные), hiddenActions?: string[] (скрыть действия по ключам).
Тулбар действий. Встроенные действия (ключ/порядок): create/10 (при permission.write), edit/20 (активно ровно при одной выбранной строке), delete/30 (активно при выборе ≥ 1; мягкое удаление по одной записи, ошибки — одним баннером), refresh/90. Колонка чекбоксов появляется автоматически, когда видимо хотя бы одно действие с requiresSelection; клик по строке по-прежнему открывает карточку, клик по чекбоксу — нет.
Колоночные фильтры — строка под шапкой, по одному условию на колонку, сервер объединяет по «И» (вместе с поиском). Операторы по типу компонента:
| Компонент | Операторы |
|---|---|
| String | содержит (по умолчанию), начинается с, равно, не равно |
String с params.kind='guid' |
равно, не равно (значение — валидный guid) |
| Int / Number / Time | =, ≠, >, ≥, <, ≤ |
| DateTime | в этот день, после дня, начиная с дня, до дня, по день («равно» раскладывается в диапазон суток; «не равно» нет — нужен «ИЛИ») |
| Date | =, ≠, после, начиная с, до, по |
| Boolean / Enum | один селект (Да/Нет либо значения enum) |
«Пусто/не пусто» (eq/neq null) добавляются только для колонок с nullable: true — поле появилось в Core-Api 0.4.0; со старым бэкендом этих операторов просто нет, а guid-колонки ведут себя как строки (сервер ответит 400 на «содержит»). Регистронезависимые «содержит»/«начинается с»/поиск — тоже с Core-Api 0.4.0. Текст и числа применяются с дебаунсом 300 мс, остальное — сразу; «Сбросить фильтры» — в тулбаре справа.
Режимы загрузки. «Лента» (по умолчанию): порции догружаются при прокрутке (плюс кнопка «Показать ещё»); «Страницы» — классический пейджер. Размер порции/страницы — 25/50/100/200, по умолчанию 100. Режим и размер — глобальная настройка пользователя, localStorage-ключи ecosystem.listview.mode и ecosystem.listview.pageSize.
Оболочка (AppShell)
AppShell — каркас кабинета: тёмный сайдбар с меню (на мобильных — drawer с топбаром) и светлая область контента. Меню приложение собирает само (модель MenuItem; пункт с children — группа-секция), динамическую группу «по сущностям» даёт useEntityMenu() — пункты-ссылки на listview для всего из GET /api/entities (ответ кэшируется на уровне модуля: сайдбар и главная делят один запрос; reload() сбрасывает кэш).
<script setup lang="ts">
import { AppShell, useEntityMenu, IconHome, IconTable, type MenuItem } from '@ecosystem/ui-core'
const entities = useEntityMenu({ icon: IconTable })
const menu = computed<MenuItem[]>(() => [
{ label: 'Главная', to: { name: 'home' }, icon: IconHome, exact: true },
...(entities.items.value.length ? [{ label: 'Данные', children: entities.items.value }] : [])
])
</script>
<template>
<AppShell :menu="menu">
<template #logo><!-- логотип приложения --></template>
<template #footer><!-- пользователь, кнопка выхода --></template>
<RouterView />
</AppShell>
</template>
Главная страница: по умолчанию монтируйте WelcomePage (простое приветствие, пропсы title/subtitle + слот) на свой home-роут; сервис со своей главной просто ставит на этот роут собственный компонент — ядро механики переопределения не требует. Иконки — встроенные inline-SVG компоненты (IconHome, IconUser, IconKey, IconMail, IconShield, IconTable, IconLogout, IconMenu, IconX, IconExpand, IconPlus, IconPencil, IconTrash, IconRefresh, IconFilter), размер — классом (class="size-4"); подойдёт и любой свой SVG-компонент.
Tailwind
Разметка компонентов — на Tailwind 4. В приложении-потребителе:
- Добавьте исходники пакета в сканирование:
@source "../node_modules/@ecosystem/ui-core/src";(или путь доpackages/ui-core/srcпри file:-установке). - Определите брендовые токены темы, которые использует разметка:
--color-brand-500,--color-brand-600,--color-brand-700(см.@themeTailwind 4).
Расширение
Серверный ViewComponent.Name резолвится через реестр; свои компоненты добавляются точечно:
import { registerCell, registerEditor } from '@ecosystem/ui-core'
registerCell('Image', MyImageCell)
registerEditor('CodeEditor', MyCodeEditor)
Компонент не зарегистрирован — поле не рендерится (deny by default, как на сервере).
Свои действия списка
Реестр действий тулбара глобальный: registerListAction добавляет действие во все списки, повторная регистрация с тем же key замещает прежнее (в т.ч. встроенное), unregisterListAction(key) убирает. Область действия сужается предикатом criteria (сущность, права); порядок в тулбаре — числом order. Контекст ListActionContext даёт entity/config/rows/selectedIds/selectedRows, операции reload/openCreate/openDetail/clearSelection/setError и обёртку runBusy (блокирует тулбар на время работы).
import { registerListAction } from '@ecosystem/ui-core'
// Кнопка только для одной сущности: активна при выборе строк.
registerListAction({
key: 'export-users',
order: 50,
label: 'Выгрузить',
criteria: ctx => ctx.entity === 'user',
requiresSelection: true,
execute: ctx => ctx.runBusy(() => exportUsers(ctx.selectedIds))
})
Действию с собственным диалогом вместо execute задаётся component: при клике он монтируется с пропсом ctx и событием close и сам рисует свой диалог (Teleport в body — образец DetailViewModal). Скрыть встроенное действие на конкретной странице — проп hidden-actions='["delete"]'; заместить глобально — зарегистрировать свой вариант с тем же ключом (например, delete с criteria: ctx => ctx.entity !== 'role').
Dependencies
Development dependencies
| ID | Version |
|---|---|
| @tanstack/vue-table | ^8.20.5 |
| @vue/tsconfig | ^0.7.0 |
| typescript | ~5.8.3 |
| vue | ^3.5.13 |
| vue-router | ^4.5.0 |
| vue-tsc | ^2.1.10 |
Peer dependencies
| ID | Version |
|---|---|
| @tanstack/vue-table | ^8.20.5 |
| vue | ^3.5.13 |
| vue-router | ^4.5.0 |