Turkish language created for Docs. Belgeler için türkçe dil desteği (#1071)
* Turkish language created for Docs. Belgeler için türkçe dil desteği oluşturuldu. * Turkish docs fix
This commit is contained in:
parent
6935b21808
commit
ad4929e48b
@ -3,6 +3,7 @@ export default {
|
||||
{
|
||||
title: 'Welcome',
|
||||
'title.zh-CN': '欢迎',
|
||||
'title.tr-TR': 'Hoşgeldiniz',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/welcome/introduction/index',
|
||||
@ -14,11 +15,13 @@ export default {
|
||||
{
|
||||
title: 'Getting started',
|
||||
'title.zh-CN': '快速开始',
|
||||
'title.tr-TR': 'Başlangıç',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
title: 'Installation',
|
||||
'title.zh-CN': '安装',
|
||||
'title.TR-TR': 'Kurulum',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
'/welcome/getting-started/installation/index',
|
||||
@ -30,6 +33,7 @@ export default {
|
||||
{
|
||||
title: 'Upgrading',
|
||||
'title.zh-CN': '升级',
|
||||
'title.TR-TR': 'Güncelleme',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
'/welcome/getting-started/upgrading/index',
|
||||
@ -43,12 +47,14 @@ export default {
|
||||
{
|
||||
title: 'Release',
|
||||
'title.zh-CN': '版本发布',
|
||||
'title.TR-TR': 'Sürüm',
|
||||
type: 'group',
|
||||
children: ['/welcome/release/index', '/welcome/release/v08-changelog'],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
'title.zh-CN': '社区',
|
||||
'title.TR-TR': 'Topluluk',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/welcome/community/contributing',
|
||||
@ -63,6 +69,7 @@ export default {
|
||||
{
|
||||
title: 'Quick Start',
|
||||
'title.zh-CN': '快速上手',
|
||||
'title.TR-TR': 'Hızlı Başlangıç',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/manual/quick-start/the-first-app',
|
||||
@ -74,6 +81,7 @@ export default {
|
||||
{
|
||||
title: 'Core Concepts',
|
||||
'title.zh-CN': '核心概念',
|
||||
'title.TR-TR': 'Temel Kavramlar',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/manual/core-concepts/a-b-c',
|
||||
@ -87,6 +95,7 @@ export default {
|
||||
{
|
||||
title: 'Blocks Guide',
|
||||
'title.zh-CN': '区块指南',
|
||||
'title.TR-TR': 'Blok Rehberi',
|
||||
type: 'group',
|
||||
children: ['/manual/blocks-guide/charts'],
|
||||
},
|
||||
@ -95,6 +104,7 @@ export default {
|
||||
{
|
||||
title: 'Getting started',
|
||||
'title.zh-CN': '快速开始',
|
||||
'title.TR-TR': 'Başlarken',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/development/index',
|
||||
@ -108,12 +118,14 @@ export default {
|
||||
{
|
||||
title: 'Server',
|
||||
'title.zh-CN': '服务端',
|
||||
'title.TR-TR': 'Sunucu',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/development/server/index',
|
||||
{
|
||||
title: 'Collections & Fields',
|
||||
'title.zh-CN': '数据表和字段',
|
||||
'title.TR-TR': 'Koleksiyonlar & Alanlar',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
'/development/server/collections/index',
|
||||
@ -137,11 +149,13 @@ export default {
|
||||
{
|
||||
title: 'Client',
|
||||
'title.zh-CN': '客户端',
|
||||
'title.TR-TR': 'Ziyaretçi(Client)',
|
||||
type: 'group',
|
||||
children: [
|
||||
'/development/client/index',
|
||||
{
|
||||
title: 'UI 设计器',
|
||||
'title.TR-TR': 'Kullanıcı Arayüz Tasarımcısı',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
// '/development/client/ui-schema-designer/index',
|
||||
@ -228,6 +242,8 @@ export default {
|
||||
{
|
||||
title: 'SchemaDesigner',
|
||||
'title.zh-CN': 'SchemaDesigner',
|
||||
'title.TR-TR': 'Şema Tasarımcısı',
|
||||
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
'/api/client/schema-designer/schema-component',
|
||||
@ -238,6 +254,7 @@ export default {
|
||||
{
|
||||
title: 'Extensions',
|
||||
'title.zh-CN': 'Extensions',
|
||||
'title.TR-TR': 'Eklentiler',
|
||||
type: 'subMenu',
|
||||
children: [
|
||||
// '/api/client/extensions/schema-component',
|
||||
|
57
docs/tr-TR/api/acl/acl-resource.md
Normal file
57
docs/tr-TR/api/acl/acl-resource.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Erişim Kontrol Listesi Kaynağı(ACL)
|
||||
|
||||
Erişim Kontrol Listesi Kaynağı, Erişim Kontrol Listesi sistemindeki kaynak sınıfıdır. Erişim Kontrol Listesi sisteminde, kullanıcılara izinler verildiğinde ilgili kaynaklar otomatik olarak oluşturulur.
|
||||
|
||||
|
||||
## Sınıf yönetimi
|
||||
|
||||
### `constructor()`
|
||||
Yapıcı Metod
|
||||
|
||||
**Kullanım**
|
||||
* `constructor(options: AclResourceOptions)`
|
||||
|
||||
**Tür**
|
||||
```typescript
|
||||
type ResourceActions = { [key: string]: RoleActionParams };
|
||||
|
||||
interface AclResourceOptions {
|
||||
name: string; // Kaynak adı
|
||||
role: ACLRole; // Kaynağın Rolü
|
||||
actions?: ResourceActions;
|
||||
}
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
`RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın
|
||||
|
||||
### `getActions()`
|
||||
|
||||
Kaynağın tüm eylemlerini alın ve sonucu bir `ResourceActions` nesnesi olarak döndürün.
|
||||
|
||||
### `getAction()`
|
||||
Adına göre Action'ın parametre yapılandırmasını döndürür ve döndürülen sonuç bir `RoleActionParams` nesnesidir.
|
||||
|
||||
**Detaylar**
|
||||
|
||||
`RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın
|
||||
|
||||
### `setAction()`
|
||||
|
||||
Kaynağın içinde bir Action parametresi yapılandırması ayarlayın ve sonucu bir `RoleActionParams` nesnesi olarak döndürün.
|
||||
|
||||
**Kullanım**
|
||||
* `setAction(name: string, params: RoleActionParams)`
|
||||
|
||||
**Detaylar**
|
||||
|
||||
* name - ayarlanacak eylem adı
|
||||
* `RoleActionParams` için [`aclRole.grantAction`](./acl-role.md#grantaction) adresine bakın
|
||||
|
||||
### `setActions()`
|
||||
|
||||
**Kullanım**
|
||||
* `setActions(actions: ResourceActions)`
|
||||
|
||||
"setAction" toplu çağrısı için kolaylık yöntemi
|
87
docs/tr-TR/api/acl/acl-role.md
Normal file
87
docs/tr-TR/api/acl/acl-role.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Erişim Kontrol Listesi Rol
|
||||
|
||||
Erişim Kontrol Listesi Rolü, Erişim Kontrol Listesi sistemindeki kullanıcı rolü sınıfıdır. Erişim Kontrol Listesi sisteminde roller genellikle 'acl.define' kullanılarak tanımlanır.
|
||||
|
||||
## Sınıf yönetimi
|
||||
|
||||
### `constructor()`
|
||||
Yapıcı metod
|
||||
|
||||
**Kullanım**
|
||||
* `constructor(public acl: ACL, public name: string)`
|
||||
|
||||
**Detaylar**
|
||||
* acl - ACL Örnek
|
||||
* name - Rol adı
|
||||
|
||||
### `grantAction()`
|
||||
|
||||
Role İşlemler izni ver
|
||||
|
||||
**Kullanım**
|
||||
* `grantAction(path: string, options?: RoleActionParams)`
|
||||
|
||||
**Tip**
|
||||
```typescript
|
||||
interface RoleActionParams {
|
||||
fields?: string[];
|
||||
filter?: any;
|
||||
own?: boolean;
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
* yol - kaynak Eylem yolu. Örneğin "gönderiler" kaynağının "düzenle" Eylemini temsil eden, kaynak adı ile Eylem arasında bir ":" iki nokta üst üste işaretiyle ayrılır. "gönderiler:düzenle" gibi.
|
||||
|
||||
RoleActionParams yetkilendirme olduğunda daha ayrıntılı izin kontrolü elde etmek için ilgili eylemin yapılandırılabilir parametreleri kullanılır.
|
||||
|
||||
* fields - erişilebilir alanlar
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:view': {
|
||||
// yönetici kullanıcılar gönderiler talep edebilir: işlemi görüntüleme, ancak yalnızca alanlar tarafından yapılandırılmış alan izinlerine sahip olma
|
||||
fields: ["id", "title", "content"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
* filter - İzin kaynağı filtreleme yapılandırması
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:view': {
|
||||
// Yönetici kullanıcı posts:view eylemini talep edebilir, ancak listelenen sonuçların filtre tarafından belirlenen koşulları karşılaması gerekir.
|
||||
filter: {
|
||||
createdById: '{{ ctx.state.currentUser.id }}', // Şablon sözdizimini destekleyen izin değerlendirildiğinde değiştirilecek olan değeri ctx cinsinden alabilirsiniz.
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
* Kendi - Yalnızca kendi verilerinize erişip erişemeyeceğinizi
|
||||
```typescript
|
||||
const actionsWithOwn = {
|
||||
'posts:view': {
|
||||
"own": true //
|
||||
}
|
||||
}
|
||||
|
||||
// Eşittir
|
||||
const actionsWithFilter = {
|
||||
'posts:view': {
|
||||
"filter": {
|
||||
"createdById": "{{ ctx.state.currentUser.id }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
* beyaz liste - beyaz liste, yalnızca beyaz listedeki alanlara erişilebilir
|
||||
* kara liste - kara liste, kara listedeki alanlara erişilemez
|
||||
|
232
docs/tr-TR/api/acl/acl.md
Normal file
232
docs/tr-TR/api/acl/acl.md
Normal file
@ -0,0 +1,232 @@
|
||||
# Erişim Kontrol Listesi
|
||||
|
||||
Erişim Kontrol Listesi, Nocobase'deki izin kontrol modülüdür. Erişim Kontrol Listesinde rolleri, kaynakları kaydettikten ve karşılık gelen izinleri yapılandırdıktan sonra, rollerin izinlerini değerlendirebilirsiniz.
|
||||
|
||||
## Kavram Açıklaması
|
||||
|
||||
* rol (`ACLRole`): izin kararının nesnesi
|
||||
* Kaynak (`ACLResource`): Nocobase Erişim Kontrol Listesinde, kaynak genellikle Restful API'deki Resource'a kavramsal olarak benzer olabilen bir veritabanı tablosuna karşılık gelir.
|
||||
* İşlem: "Oluştur", "oku", "güncelle", "sil" vb. gibi kaynaklar üzerindeki işlemler.
|
||||
* Strateji (`ACLAvailableStrategy`): Genellikle her rolün varsayılan kullanıcı izinlerini tanımlayan kendi izin stratejisi vardır.
|
||||
* Grant: `Action`a rol erişimi vermek için `ACLRole` örneğinde `grantAction` işlevini çağırın.
|
||||
* Kimlik Doğrulama: `ACL` örneğinde `can` işlevi çağrılır ve işlev tarafından döndürülen sonuç, kullanıcının kimlik doğrulama sonucudur.
|
||||
|
||||
|
||||
## sınıf yöntemi
|
||||
|
||||
### `constructor()`
|
||||
|
||||
Bir `ACL` örneği oluşturan oluşturucu.
|
||||
|
||||
```typescript
|
||||
import { ACL } from '@nocobase/database';
|
||||
|
||||
const acl = new ACL();
|
||||
```
|
||||
|
||||
### `define()`
|
||||
|
||||
Bir ACL rolü tanımlayın
|
||||
|
||||
**Kullanım**
|
||||
* `define(options: DefineOptions): ACLRole`
|
||||
|
||||
**Tip**
|
||||
|
||||
```typescript
|
||||
interface DefineOptions {
|
||||
role: string;
|
||||
allowConfigure?: boolean;
|
||||
strategy?: string | AvailableStrategyOptions;
|
||||
actions?: ResourceActionsOptions;
|
||||
routes?: any;
|
||||
}
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
* `role` - 角色名称
|
||||
|
||||
```typescript
|
||||
// Yönetici adında bir rol tanımlayın
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
});
|
||||
```
|
||||
|
||||
* `allowConfigure` - Yapılandırma izinlerine izin verilip verilmeyeceği
|
||||
* `strategy` - Rol İzin Politikası
|
||||
* Tanımlanan stratejinin kullanılacağını belirten, kullanılacak stratejinin adı olan bir `string` olabilir.
|
||||
* `AvailableStrategyOptions` için bu rol için yeni bir strateji tanımlanabilir, bkz. [`setAvailableActions()`](#setavailableactions).
|
||||
* `işlemler` - Bir rol tanımlarken, rol için erişilebilir bir `işlemler` nesnesi iletebilirsiniz.
|
||||
Bundan sonra, kaynak izinlerini vermek için sırayla `aclRole.grantAction` çağrılır. Bkz. [`aclRole.grantAction`](./acl-role.md#grantaction)
|
||||
|
||||
```typescript
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:edit': {}
|
||||
},
|
||||
});
|
||||
// Diğer kullanım şekli
|
||||
const role = acl.define({
|
||||
role: 'admin',
|
||||
});
|
||||
|
||||
role.grantAction('posts:edit', {});
|
||||
```
|
||||
|
||||
### `getRole()`
|
||||
|
||||
Rol adına göre kayıtlı rol nesnesini döndürür
|
||||
|
||||
**Kullanım**
|
||||
* `getRole(name: string): ACLRole`
|
||||
|
||||
### `removeRole()`
|
||||
|
||||
Rol adına göre rolleri kaldırın
|
||||
|
||||
**Kullanım**
|
||||
* `removeRole(name: string)`
|
||||
|
||||
### `can()`
|
||||
Kimlik doğrulama işlevi
|
||||
|
||||
**Kullanım**
|
||||
* `can(options: CanArgs): CanResult | null`
|
||||
|
||||
**Tip**
|
||||
|
||||
```typescript
|
||||
interface CanArgs {
|
||||
role: string; // Rol ismi
|
||||
resource: string; // Kaynak Adı
|
||||
action: string; // İşlem adı
|
||||
}
|
||||
|
||||
interface CanResult {
|
||||
role: string; // Rol ismi
|
||||
resource: string; // Kaynak Adı
|
||||
action: string; // İşlem adı
|
||||
params?: any; // İzinler kaydedilirken gönderilen parametreler
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
`can` yöntemi öncelikle rolün kayıt için ilgili `Action` iznine sahip olup olmadığını belirleyecek ve değilse, rolün `strategy` eşleşip eşleşmediğini belirleyecektir.
|
||||
Çağrı `null` döndürdüğünde, rolün izni olmadığını belirtir, aksi takdirde, rolün izne sahip olduğunu belirten bir `CanResult` nesnesi döndürür.
|
||||
|
||||
**Örnek**
|
||||
```typescript
|
||||
// Rolleri tanımlayın, izinleri kaydedin
|
||||
acl.define({
|
||||
role: 'admin',
|
||||
actions: {
|
||||
'posts:edit': {
|
||||
fields: ['title', 'content'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const canResult = acl.can({
|
||||
role: 'admin',
|
||||
resource: 'posts',
|
||||
action: 'edit',
|
||||
});
|
||||
/**
|
||||
* canResult = {
|
||||
* role: 'admin',
|
||||
* resource: 'posts',
|
||||
* action: 'edit',
|
||||
* params: {
|
||||
* fields: ['title', 'content'],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
acl.can({
|
||||
role: 'admin',
|
||||
resource: 'posts',
|
||||
action: 'destroy',
|
||||
}); // null
|
||||
```
|
||||
### `use()`
|
||||
|
||||
**Kullanım**
|
||||
* `use(fn: any)`
|
||||
Ara katman yazılımlarına ara katman işlevleri ekleyin.
|
||||
|
||||
### `middleware()`
|
||||
|
||||
`@nocobase/server` içinde kullanım için bir ara katman işlevi döndürür. Bu `ara yazılımı` kullandıktan sonra, `@nocobase/server`, her istek işlenmeden önce izin kararı verecektir.
|
||||
|
||||
### `allow()`
|
||||
|
||||
Kaynağı herkese açık hale getirin
|
||||
|
||||
**Kullanım**
|
||||
* `allow(resourceName: string, actionNames: string[] | string, condition?: string | ConditionFunc)`
|
||||
|
||||
**Tip**
|
||||
```typescript
|
||||
type ConditionFunc = (ctx: any) => Promise<boolean> | boolean;
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
* resourceName - Kaynak Adı
|
||||
* actionNames - kaynak işlem adı
|
||||
* condition? - Konfigürasyon Etkili Koşullar
|
||||
* `string` iletmek, tanımlanan koşulun kullanıldığını ve kayıt koşulunun `acl.allowManager.registerCondition` yöntemini kullandığını gösterir.
|
||||
```typescript
|
||||
acl.allowManager.registerAllowCondition('superUser', async () => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
|
||||
// superUser durumu için users:list yetkilerini açar
|
||||
acl.allow('users', 'list', 'superUser');
|
||||
```
|
||||
* ConditionFunc'u ctx parametresiyle iletir ve sonucunu `boolean` olarak döndürür.
|
||||
```typescript
|
||||
// Kullanıcı kimliği 1 olduğunda, user:list'e erişebilirsiniz.
|
||||
acl.allow('users', 'list', (ctx) => {
|
||||
return ctx.state.user?.id === 1;
|
||||
});
|
||||
```
|
||||
|
||||
**Örnek**
|
||||
|
||||
```typescript
|
||||
// users:login erişim yetkisi olanlar erişebilir.
|
||||
acl.allow('users', 'login');
|
||||
```
|
||||
|
||||
### `setAvailableActions()`
|
||||
|
||||
**Kullanım**
|
||||
|
||||
* `setAvailableStrategy(name: string, options: AvailableStrategyOptions)`
|
||||
|
||||
Kullanılabilir bir izin politikası kaydedin
|
||||
|
||||
**Tip**
|
||||
|
||||
```typescript
|
||||
interface AvailableStrategyOptions {
|
||||
displayName?: string;
|
||||
actions?: false | string | string[];
|
||||
allowConfigure?: boolean;
|
||||
resource?: '*';
|
||||
}
|
||||
```
|
||||
|
||||
**Detaylar**
|
||||
|
||||
* displayName - Politika Adı
|
||||
* allowConfigure - Bu politikanın **config source** izni olup olmadığına bakılmaksızın, bunu `true` olarak ayarladıktan sonra, `ACL`de bir `configResources` kaynağı olarak kaydolma iznini belirleme isteği bir geçiş döndürür.
|
||||
* actions - Politika içindeki işlemler listesi joker karakteri `*` destekler
|
||||
* resource - Politika içindeki kaynak tanımı joker karakteri `*` destekler
|
||||
|
349
docs/tr-TR/api/actions.md
Normal file
349
docs/tr-TR/api/actions.md
Normal file
@ -0,0 +1,349 @@
|
||||
# Yerleşik ortak kaynak işlemleri
|
||||
|
||||
NocoBase, CRUD gibi yaygın olarak kullanılan veri kaynaklarının işlemleri için yerleşik işlem yöntemlerine sahiptir ve ilgili işlemleri veri tablosu kaynakları aracılığıyla otomatik olarak eşler.
|
||||
|
||||
Tüm işlem yöntemleri, kaynak sağlayıcı örneğine kaydedilir ve ayrıca standart Koa uyumlu ara yazılım işlevleridir (`(ctx, next) => Promise<void>`). Eylemin parametreleri, rota tarafından ayrıştırılır ve 'ctx.action' nesnesine eklenir ve sonraki parametre açıklamaları bu nesneyi temel alır.
|
||||
|
||||
Genellikle, yerleşik eylem yöntemini doğrudan çağırmaya gerek yoktur.Varsayılan eylem davranışını genişletmeniz gerektiğinde, özel eylem yönteminde varsayılan yöntemi çağırabilirsiniz.
|
||||
|
||||
## Paket yapısı
|
||||
|
||||
İlişkili varlıklar aşağıdaki şekillerde tanıtılabilir:
|
||||
|
||||
```ts
|
||||
import actions from '@nocobase/actions';
|
||||
```
|
||||
|
||||
## Tek Veri Kaynağı İşlemi
|
||||
|
||||
### `list()`
|
||||
|
||||
Bir veri listesi alınması işlemine karşılık gelen URL `GET /api/<resource>:list` şeklindedir.
|
||||
|
||||
**参数**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
| --- | --- | --- | --- |
|
||||
| `filter` | `Filter` | - | filtre parametresi |
|
||||
| `fields` | `string[]` | - | Çekilecek alanlar |
|
||||
| `except` | `string[]` | - | Çekilmeyecek alanlar |
|
||||
| `appends` | `string[]` | - | İlişkisel alanlar |
|
||||
| `sort` | `string[]` | - | Sıralama |
|
||||
| `page` | `number` | 1 | Sayfalandırma |
|
||||
| `pageSize` | `number` | 20 | Sayfa başına veri sayısı |
|
||||
|
||||
**Örnek**
|
||||
|
||||
Bir veri listesini sorgulamak için bir arabirim sağlamanız gerektiğinde fakat varsayılan olarak JSON biçiminde çıktı almadığınızda, onu yerleşik varsayılan yönteme göre genişletebilirsiniz:
|
||||
|
||||
```ts
|
||||
import actions from '@nocobase/actions';
|
||||
|
||||
app.actions({
|
||||
async ['books:list'](ctx, next) {
|
||||
ctx.action.mergeParams({
|
||||
except: ['content']
|
||||
});
|
||||
|
||||
await actions.list(ctx, async () => {
|
||||
const { rows } = ctx.body;
|
||||
// JSON'u CSV çıktısına dönüştürün
|
||||
ctx.body = rows.map(row => Object.keys(row).map(key => row[key]).join(',')).join('\n');
|
||||
ctx.type = 'text/csv';
|
||||
|
||||
await next();
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
İstek örneği, CSV biçimindeki dosyanın dönüşünü alacaktır:
|
||||
|
||||
```shell
|
||||
curl -X GET http://localhost:13000/api/books:list
|
||||
```
|
||||
|
||||
### `get()`
|
||||
|
||||
Tek bir veri parçası almak için işlem URL'si `GET /api/<resource>:get` şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
| --- | --- | --- | --- |
|
||||
| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele |
|
||||
| `filter` | `Filter` | - | filtre parametresi |
|
||||
| `fields` | `string[]` | - | Çekilecek alanlar |
|
||||
| `except` | `string[]` | - | Çekilmeyecek alanlar |
|
||||
| `appends` | `string[]` | - | İlişkisel alanlar |
|
||||
| `sort` | `string[]` | - | Sıralama |
|
||||
| `page` | `number` | 1 | Sayfalandırma |
|
||||
| `pageSize` | `number` | 20 | Sayfa başı veri sayısı |
|
||||
|
||||
**Örnek**
|
||||
|
||||
NocoBase'in yerleşik dosya yönetimi eklentisine dayanarak, istemci kaynak tanımlayıcılı bir dosya indirmeyi istediğinde dosya akışını döndürecek şekilde genişletilebilir:
|
||||
|
||||
```ts
|
||||
import path from 'path';
|
||||
import actions from '@nocobase/actions';
|
||||
import { STORAGE_TYPE_LOCAL } from '@nocobase/plugin-file-manager';
|
||||
|
||||
app.actions({
|
||||
async ['attachments:get'](ctx, next) {
|
||||
ctx.action.mergeParams({
|
||||
appends: ['storage'],
|
||||
});
|
||||
|
||||
await actions.get(ctx, async () => {
|
||||
if (ctx.accepts('json', 'application/octet-stream') === 'json') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const { body: attachment } = ctx;
|
||||
const { storage } = attachment;
|
||||
|
||||
if (storage.type !== STORAGE_TYPE_LOCAL) {
|
||||
return ctx.redirect(attachment.url);
|
||||
}
|
||||
|
||||
ctx.body = fs.createReadStream(path.resolve(storage.options.documentRoot?, storage.path));
|
||||
ctx.attachment(attachment.filename);
|
||||
ctx.type = 'application/octet-stream';
|
||||
|
||||
await next();
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
İstek örneği, dosya akışının dönüşünü alacak:
|
||||
|
||||
```shell
|
||||
curl -X GET -H "Accept: application/octet-stream" http://localhost:13000/api/attachments:get?filterByTk=1
|
||||
```
|
||||
|
||||
### `create()`
|
||||
|
||||
Tek bir veri parçası oluşturma işleminin URL'si "POST /api/<resource>:create" şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
| --- | --- | --- | --- |
|
||||
| `values` | `Object` | - | oluşturulacak veriler |
|
||||
|
||||
**Örnek**
|
||||
|
||||
Dosya yönetimi eklentisine benzer şekilde, yüklenen dosyaya ek olarak ikili içerikli veri oluşturma:
|
||||
|
||||
```ts
|
||||
import multer from '@koa/multer';
|
||||
import actions from '@nocobase/actions';
|
||||
|
||||
app.actions({
|
||||
async ['files:create'](ctx, next) {
|
||||
if (ctx.request.type === 'application/json') {
|
||||
return actions.create(ctx, next);
|
||||
}
|
||||
|
||||
if (ctx.request.type !== 'multipart/form-data') {
|
||||
return ctx.throw(406);
|
||||
}
|
||||
|
||||
// Dosya kaydetme işlemi yalnızca örnek olarak multer() kullanır ve tam mantığı temsil etmez
|
||||
multer().single('file')(ctx, async () => {
|
||||
const { file, body } = ctx.request;
|
||||
const { filename, mimetype, size, path } = file;
|
||||
|
||||
ctx.action.mergeParams({
|
||||
values: {
|
||||
filename,
|
||||
mimetype,
|
||||
size,
|
||||
path: file.path,
|
||||
meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {};
|
||||
}
|
||||
});
|
||||
|
||||
await actions.create(ctx, next);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
İstek örneği; dosya tablosunda sıradan veriler oluşturabilir veya eklerle birlikte gönderebilirsiniz:
|
||||
|
||||
```shell
|
||||
# Yalnızca normal veriler oluşturun
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:create"
|
||||
|
||||
# Eklerle birlikte gönder
|
||||
curl -X POST -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:create"
|
||||
```
|
||||
|
||||
### `update()`
|
||||
|
||||
Bir veya daha fazla veri parçasını güncellemek için kullanılan URL, "PUT /api/<resource>:update" şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
| --- | --- | --- | --- |
|
||||
| `filter` | `Filter` | - | filtre parametresi |
|
||||
| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele |
|
||||
| `values` | `Object` | - | güncellenecek veri değeri |
|
||||
|
||||
Not: parametrelerdeki `filter` ve `filterByTk` en az bir öğe sağlar.
|
||||
|
||||
**Örnek**
|
||||
|
||||
"create()" örneğine benzer şekilde, güncellenmiş dosya olarak ikili içerik verilerini taşımak için de kullanılabilir.
|
||||
|
||||
```ts
|
||||
import multer from '@koa/multer';
|
||||
import actions from '@nocobase/actions';
|
||||
|
||||
app.actions({
|
||||
async ['files:update'](ctx, next) {
|
||||
if (ctx.request.type === 'application/json') {
|
||||
return actions.update(ctx, next);
|
||||
}
|
||||
|
||||
if (ctx.request.type !== 'multipart/form-data') {
|
||||
return ctx.throw(406);
|
||||
}
|
||||
|
||||
// Dosya kaydetme işlemi yalnızca örnek olarak multer() kullanır ve tam mantığı temsil etmez
|
||||
multer().single('file')(ctx, async () => {
|
||||
const { file, body } = ctx.request;
|
||||
const { filename, mimetype, size, path } = file;
|
||||
|
||||
ctx.action.mergeParams({
|
||||
values: {
|
||||
filename,
|
||||
mimetype,
|
||||
size,
|
||||
path: file.path,
|
||||
meta: typeof body.meta === 'string' ? JSON.parse(body.meta) : {};
|
||||
}
|
||||
});
|
||||
|
||||
await actions.update(ctx, next);
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
İstek örneği, dosya tablosunda sıradan veriler oluşturabilir veya eklerle birlikte gönderebilirsiniz:
|
||||
|
||||
```shell
|
||||
# Yalnızca normal veriler oluşturun
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"filename": "some-file.txt", "mimetype": "text/plain", "size": 5, "url": "https://cdn.yourdomain.com/some-file.txt"}' "http://localhost:13000/api/files:update"
|
||||
|
||||
# Eklerle birlikte gönderme
|
||||
curl -X PUT -F "file=@/path/to/some-file.txt" -F 'meta={"length": 100}' "http://localhost:13000/api/files:update"
|
||||
```
|
||||
|
||||
### `destroy()`
|
||||
|
||||
Bir veya daha fazla veri parçasını silin. Karşılık gelen URL "DELETE /api/<resource>:destroy" şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
| --- | --- | --- | --- |
|
||||
| `filter` | `Filter` | - | filtre parametresi |
|
||||
| `filterByTk` | `number \| string` | - | birincil anahtarı filtrele |
|
||||
|
||||
Not: parametrelerdeki `filter` ve `filterByTk` en az bir öğe sağlar.
|
||||
|
||||
**Örnek**
|
||||
|
||||
Dosya verilerini silmek için bir dosya yönetimi eklentisinin uzantısına benzer şekilde dosyayı da silmek gerekir:
|
||||
|
||||
```ts
|
||||
import actions from '@nocobase/actions';
|
||||
|
||||
app.actions({
|
||||
async ['files:destroy'](ctx, next) {
|
||||
// const repository = getRepositoryFromParams(ctx);
|
||||
|
||||
// const { filterByTk, filter } = ctx.action.params;
|
||||
|
||||
// const items = await repository.find({
|
||||
// fields: [repository.collection.filterTargetKey],
|
||||
// appends: ['storage'],
|
||||
// filter,
|
||||
// filterByTk,
|
||||
// context: ctx,
|
||||
// });
|
||||
|
||||
// await items.reduce((promise, item) => promise.then(async () => {
|
||||
// await item.removeFromStorage();
|
||||
// await item.destroy();
|
||||
// }), Promise.resolve());
|
||||
|
||||
await actions.destroy(ctx, async () => {
|
||||
// yapılacak işler
|
||||
await next();
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `move()`
|
||||
Karşılık gelen URL 'POST /api/<resource>:move' şeklindedir.
|
||||
|
||||
Bu yöntem, verileri taşımak ve verilerin sırasını ayarlamak için kullanılır. Örneğin, bir sayfada bir öğeyi başka bir öğenin üstüne veya altına sürükleyip bırakın, sırayı ayarlamak için de bu yöntemi kullanabilirsiniz.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
|----------|-------------| -- |---------------|
|
||||
| `sourceId` | `targetKey` | - | Kaynak öğe kimliği |
|
||||
| `targetId` | `targetKey` | - | Hedef öğe kimliği |
|
||||
| `sortField` | `string` | `sort` | Depolanan alan adını sırala |
|
||||
| `targetScope` | `string` | - | Sıralanmış kapsam, bir kaynak farklı kapsamlara göre sıralanabilir |
|
||||
| `sticky` | `boolean` | - | Hareketli elemanın üstte olup olmadığı |
|
||||
| `method` | `insertAfter` \| `prepend` | - | Ekleme türü, hedef öğenin eklenmesinden önce veya sonra |
|
||||
|
||||
## İlişkisel Kaynak Kaynak İşlemleri
|
||||
|
||||
### `add()`
|
||||
|
||||
Bir nesneyle ilişki eklemek için, karşılık gelen URL `POST /api/<resource.assocition>:add` şeklindedir. "hasMany" ve "belongsToMany" ilişkilendirmeleri için geçerlidir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
|----------|-------------| --- | --- |
|
||||
| `values` | `TargetKey \| TargetKey[]` | - | Eklenen ilişkili nesne kimliği |
|
||||
|
||||
### `remove()`
|
||||
Bir nesneyle olan ilişkilendirmeyi kaldırmak için karşılık gelen URL 'POST /api/<resource.assocition>:remove' şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
|----------|-------------| --- | --- |
|
||||
| `values` | `TargetKey \| TargetKey[]` | - | Kaldırılan ilişkili nesne kimliği |
|
||||
|
||||
### `set()`
|
||||
İlişkili ilişkilendirme nesnesini ayarlamaya karşılık gelen URL 'POST /api/<resource.assocition>:set' şeklindedir.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
|----------|-------------| --- | --- |
|
||||
| `values` | `TargetKey \| TargetKey[]` | - | İlişkili nesnenin kimliğini ayarlayın |
|
||||
|
||||
### `toggle()`
|
||||
|
||||
İlişkili ilişkilendirme nesnesini değiştirmeye karşılık gelen URL 'POST /api/<resource.assocition>:toggle' şeklindedir. "toggle", ilişkili nesnenin zaten var olup olmadığını dahili olarak belirler, varsa onu kaldırır ve yoksa ekler.
|
||||
|
||||
**Parametre**
|
||||
|
||||
| parametre adı | tür | varsayılan değer | açıklama |
|
||||
|----------|-------------| -- | --- |
|
||||
| `values` | `TargetKey` | - | Anahtarın ilişkili nesnesinin kimliği |
|
350
docs/tr-TR/api/cli.md
Normal file
350
docs/tr-TR/api/cli.md
Normal file
@ -0,0 +1,350 @@
|
||||
# NocoBase CLI
|
||||
|
||||
NocoBase CLI, NocoBase uygulamalarını geliştirmenize, oluşturmanıza ve dağıtmanıza yardımcı olmak için tasarlanmıştır.
|
||||
|
||||
<Alert>
|
||||
|
||||
NocoBase CLI, ts-node ve node çalışma modlarını destekler.
|
||||
|
||||
- ts-node modu (varsayılan): geliştirme ortamı için gerçek zamanlı derlemeyi destekler, ancak yavaş yanıt verir
|
||||
- node modu: üretim ortamında kullanılır, yanıt hızlıdır, ancak tüm kaynak kodunu derlemek için "yarn nocobase build"i çalıştırmanız gerekir
|
||||
|
||||
</Alert>
|
||||
|
||||
## Kullanım talimatları
|
||||
|
||||
```bash
|
||||
$ yarn nocobase -h
|
||||
|
||||
Kullanım: nocobase [komut] [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-h, --help
|
||||
|
||||
Komutlar:
|
||||
console
|
||||
db:auth Veritabanı bağlantısının başarılı olduğunu doğrulayın
|
||||
db:sync Koleksiyon yapılandırması aracılığıyla ilgili veri tabloları ve alanları oluşturun
|
||||
install Kurulum
|
||||
start Uygulamayı üretim ortamında başlatır
|
||||
build Derleme ve paketleme yapar
|
||||
clean Derlenmiş dosyaları siler
|
||||
dev Geliştirme ortamı için uygulamayı başlatın ve tam zamanında derlemeyi destekleyin
|
||||
doc Belge geliştirme
|
||||
test Test
|
||||
umi
|
||||
upgrade Sürüm yükseltme
|
||||
migrator Veri aktarma
|
||||
pm Eklenti yöneticisi
|
||||
help
|
||||
```
|
||||
|
||||
## Scaffold Uygulama
|
||||
|
||||
Scaffold Uygulama `package.json` \ `scripts` aşağıdaki gibi:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "nocobase dev",
|
||||
"start": "nocobase start",
|
||||
"clean": "nocobase clean",
|
||||
"build": "nocobase build",
|
||||
"test": "nocobase test",
|
||||
"pm": "nocobase pm",
|
||||
"postinstall": "nocobase postinstall"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Komut satırı uzantısı
|
||||
|
||||
NocoBase CLI, [Komut Satırı](https://github.com/tj/commander.js) üzerine kurulmuştur, komutu özgürce genişletebilirsiniz, genişletilmiş komut 'app/server/index.ts' içinde yazılabilir:
|
||||
|
||||
```ts
|
||||
const app = new Application(config);
|
||||
|
||||
app.command('hello').action(() => {});
|
||||
```
|
||||
|
||||
Veya bir eklenti için:
|
||||
|
||||
```ts
|
||||
class MyPlugin extends Plugin {
|
||||
beforeLoad() {
|
||||
this.app.command('hello').action(() => {});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Terminal çalışması
|
||||
|
||||
```bash
|
||||
$ yarn nocobase hello
|
||||
```
|
||||
|
||||
## Yerleşik komut satırı
|
||||
|
||||
Kullanım sıklığına göre sırala
|
||||
|
||||
### `dev`
|
||||
|
||||
Geliştirme ortamında uygulamayı başlatın ve kod gerçek zamanlı olarak derlenir.
|
||||
|
||||
<Alert>
|
||||
NocoBase kurulmadığında, otomatik olarak kurulacaktır (kurulum komutuna bakın)
|
||||
</Alert>
|
||||
|
||||
```bash
|
||||
Kullanım: nocobase dev [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-p, --port [port]
|
||||
--client
|
||||
--server
|
||||
-h, --help
|
||||
```
|
||||
|
||||
Örnek
|
||||
|
||||
```bash
|
||||
# Uygulamayı başlatın, geliştirme ortamı için tam zamanında derleme
|
||||
yarn nocobase dev
|
||||
# Sadece sunucuyu başlat
|
||||
yarn nocobase dev --server
|
||||
# sadece istemciyi başlat
|
||||
yarn nocobase dev --client
|
||||
```
|
||||
|
||||
### `start`
|
||||
|
||||
Üretim ortamında, uygulamayı başlatmak için kodun yarn build ile derlenmesi gereklidir.
|
||||
|
||||
<Alert>
|
||||
|
||||
- NocoBase kurulmadığında, otomatik olarak kurulacaktır (kurulum komutuna bakın)
|
||||
- Kaynak kodu değiştirildiğinde, yeniden paketlenmesi gerekir (build komutuna bakın)
|
||||
|
||||
</Alert>
|
||||
|
||||
```bash
|
||||
$ yarn nocobase start -h
|
||||
|
||||
Kullanım: nocobase start [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-p, --port
|
||||
-s, --silent
|
||||
-h, --help
|
||||
```
|
||||
|
||||
Örnek
|
||||
|
||||
```bash
|
||||
# derlenen kodun kullanımı için sistemi başlatın,
|
||||
yarn nocobase start
|
||||
```
|
||||
|
||||
### `install`
|
||||
|
||||
Kurulum
|
||||
|
||||
```bash
|
||||
$ yarn nocobase install -h
|
||||
|
||||
Kullanım: nocobase install [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-f, --force
|
||||
-c, --clean
|
||||
-s, --silent
|
||||
-l, --lang [lang]
|
||||
-e, --root-email <rootEmail>
|
||||
-p, --root-password <rootPassword>
|
||||
-n, --root-nickname [rootNickname]
|
||||
-h, --help
|
||||
```
|
||||
|
||||
Örnek
|
||||
|
||||
```bash
|
||||
# İlk kurulum
|
||||
yarn nocobase install -l tr-TR -e admin@nocobase.com -p admin123
|
||||
# NocoBase için tüm veri sayfalarını kaldırın ve yeniden yükleyin
|
||||
yarn nocobase install -f -l tr-TR -e admin@nocobase.com -p admin123
|
||||
# Veritabanını boşaltın ve yeniden yükleyin
|
||||
yarn nocobase install -c -l tr-TR -e admin@nocobase.com -p admin123
|
||||
```
|
||||
|
||||
<Alert>
|
||||
|
||||
`-f/--force` ve `-c/--clean`
|
||||
- `-f/--force` NocoBase verileri silin
|
||||
- `-c/--clean` Veritabanını boşaltın, tüm veri tabloları silinecek
|
||||
|
||||
</Alert>
|
||||
|
||||
### `upgrade`
|
||||
|
||||
Sürüm yükseltme
|
||||
|
||||
```bash
|
||||
yarn nocobase upgrade
|
||||
```
|
||||
|
||||
### `test`
|
||||
|
||||
jest testi, "-c, --db-clean" desteğini genişletmenin yanı sıra tüm [jest-cli](https://jestjs.io/docs/cli) seçeneklerini destekler.
|
||||
|
||||
```bash
|
||||
$ yarn nocobase test -h
|
||||
|
||||
Usage: nocobase test [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-c, --db-clean Tüm testleri çalıştırmadan önce veritabanını temizleyin
|
||||
-h, --help
|
||||
```
|
||||
|
||||
Örnek
|
||||
|
||||
```bash
|
||||
# tüm test dosyalarını çalıştır
|
||||
yarn nocobase test
|
||||
# Belirtilen klasördeki tüm test dosyalarını çalıştırın
|
||||
yarn nocobase test packages/core/server
|
||||
# belirtilen dosyadaki tüm testleri çalıştır
|
||||
yarn nocobase test packages/core/database/src/__tests__/database.test.ts
|
||||
|
||||
# Testi çalıştırmadan önce veritabanını temizleyin
|
||||
yarn nocobase test -c
|
||||
yarn nocobase test packages/core/server -c
|
||||
```
|
||||
|
||||
### `build`
|
||||
|
||||
Kod üretim ortamına dağıtılmadan önce kaynak kodun derlenmesi ve paketlenmesi gerekir.Kod değiştirilirse yeniden oluşturulması gerekir.
|
||||
|
||||
```bash
|
||||
# Tüm paketler
|
||||
yarn nocobase build
|
||||
# Belirtilen paket(ler)
|
||||
yarn nocobase build app/server app/client
|
||||
```
|
||||
|
||||
### `clean`
|
||||
|
||||
derlenmiş dosyaları sil
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
# Aynı işi aşağıdaki kod ile de yapabilirsiniz.
|
||||
yarn rimraf -rf packages/*/*/{lib,esm,es,dist}
|
||||
```
|
||||
|
||||
### `doc`
|
||||
|
||||
Döküman geliştirme
|
||||
|
||||
```bash
|
||||
# Belge geliştirmeyi başlar
|
||||
yarn doc --lang=tr-TR # yarn doc dev ile aynı işi yapar
|
||||
# Dokümantasyon oluşturun, varsayılan olarak ./docs/dist/ dizinine çıktı alın
|
||||
yarn doc build
|
||||
# Dist ile belge çıktısının son etkisini görüntüleyin
|
||||
yarn doc serve --lang=tr-TR
|
||||
```
|
||||
|
||||
### `db:auth`
|
||||
|
||||
Veritabanı bağlantısının başarılı olduğunu doğrulayın
|
||||
|
||||
```bash
|
||||
$ yarn nocobase db:auth -h
|
||||
|
||||
Kullanım: nocobase db:auth [seçenekler]
|
||||
|
||||
Seçenekler:
|
||||
-r, --retry [TekrarSayısı] TekrarSayısı yazan yere bir rakam yazın.
|
||||
-h, --help
|
||||
```
|
||||
|
||||
### `db:sync`
|
||||
|
||||
Koleksiyon yapılandırması aracılığıyla veri tabloları ve alanlar oluşturun
|
||||
|
||||
```bash
|
||||
$ yarn nocobase db:sync -h
|
||||
|
||||
Kullanım: nocobase db:sync [seçenek]
|
||||
|
||||
Seçenekler:
|
||||
-f, --force
|
||||
-h, --help yardım içeriğini görüntüler
|
||||
```
|
||||
|
||||
### `migrator`
|
||||
|
||||
Veri aktarma
|
||||
|
||||
```bash
|
||||
$ yarn nocobase migrator
|
||||
|
||||
Konumsal argümanlar:
|
||||
<komut>
|
||||
up Bekleyen taşımaları uygular
|
||||
down Taşıma işlemlerini geri al
|
||||
pending Bekleyen taşımaları listeler
|
||||
executed Yürütülen taşımaları listeler
|
||||
create Taşıma dosyası oluşturun
|
||||
```
|
||||
|
||||
### `pm`
|
||||
|
||||
Eklenti yöneticisi
|
||||
|
||||
```bash
|
||||
# Eklenti oluştur
|
||||
yarn pm create hello
|
||||
# Eklentiyi kaydet
|
||||
yarn pm add hello
|
||||
# Eklentiyi etkinleştir
|
||||
yarn pm enable hello
|
||||
# Eklentiyi devre dışı bırak
|
||||
yarn pm disable hello
|
||||
# Eklentiyi kaldır
|
||||
yarn pm remove hello
|
||||
```
|
||||
|
||||
Tamamlanmayanlar
|
||||
|
||||
```bash
|
||||
# Eklenti sürüm yükseltme
|
||||
yarn pm upgrade hello
|
||||
# Eklentiyi yayınla
|
||||
yarn pm publish hello
|
||||
```
|
||||
|
||||
### `umi`
|
||||
|
||||
"app/client", [umi](https://umijs.org/) tabanlıdır ve ilgili diğer komutları "nocobase umi" aracılığıyla yürütebilir.
|
||||
|
||||
```bash
|
||||
# Geliştirme ortamı için gereken .umi önbelleğini oluşturun
|
||||
yarn nocobase umi generate tmp
|
||||
```
|
||||
|
||||
### `help`
|
||||
|
||||
Yardım komutu, ayrıca `-h` ve `--help` seçenek parametresiyle de kullanılabilir
|
||||
|
||||
```bash
|
||||
# Tüm yardım içeriğini göster
|
||||
yarn nocobase help
|
||||
# Aynı içeriğe aşağıdaki komut ile de ulaşabilirsiniz.
|
||||
yarn nocobase -h
|
||||
# TAynı içeriğe aşağıdaki komut ile de ulaşabilirsiniz.
|
||||
yarn nocobase --help
|
||||
# db:sync komutunun seçeneklerini görüntüleyin
|
||||
yarn nocobase db:sync -h
|
||||
```
|
62
docs/tr-TR/api/client/application.md
Normal file
62
docs/tr-TR/api/client/application.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Application
|
||||
|
||||
## 构造函数
|
||||
|
||||
### `constructor()`
|
||||
|
||||
创建一个应用实例。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: ApplicationOptions)`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const app = new Application({
|
||||
apiClient: {
|
||||
baseURL: process.env.API_BASE_URL,
|
||||
},
|
||||
dynamicImport: (name: string) => {
|
||||
return import(`../plugins/${name}`);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 方法
|
||||
|
||||
### use()
|
||||
|
||||
添加 Providers,内置 Providers 有:
|
||||
|
||||
- APIClientProvider
|
||||
- I18nextProvider
|
||||
- AntdConfigProvider
|
||||
- RemoteRouteSwitchProvider
|
||||
- SystemSettingsProvider
|
||||
- PluginManagerProvider
|
||||
- SchemaComponentProvider
|
||||
- SchemaInitializerProvider
|
||||
- BlockSchemaComponentProvider
|
||||
- AntdSchemaComponentProvider
|
||||
- ACLProvider
|
||||
- RemoteDocumentTitleProvider
|
||||
|
||||
### render()
|
||||
|
||||
渲染 App 组件
|
||||
|
||||
```ts
|
||||
import { Application } from '@nocobase/client';
|
||||
|
||||
export const app = new Application({
|
||||
apiClient: {
|
||||
baseURL: process.env.API_BASE_URL,
|
||||
},
|
||||
dynamicImport: (name: string) => {
|
||||
return import(`../plugins/${name}`);
|
||||
},
|
||||
});
|
||||
|
||||
export default app.render();
|
||||
```
|
23
docs/tr-TR/api/client/extensions/acl.md
Normal file
23
docs/tr-TR/api/client/extensions/acl.md
Normal file
@ -0,0 +1,23 @@
|
||||
# ACL
|
||||
|
||||
## Components
|
||||
|
||||
### `<ACLProvider />`
|
||||
|
||||
### `<ACLRolesCheckProvider />`
|
||||
|
||||
### `<ACLCollectionProvider />`
|
||||
|
||||
### `<ACLActionProvider />`
|
||||
|
||||
### `<ACLCollectionFieldProvider />`
|
||||
|
||||
### `<ACLMenuItemProvider />`
|
||||
|
||||
## Hooks
|
||||
|
||||
### `useACLContext()`
|
||||
|
||||
### `useACLRoleContext()`
|
||||
|
||||
### `useRoleRecheck()`
|
25
docs/tr-TR/api/client/extensions/block-provider.md
Normal file
25
docs/tr-TR/api/client/extensions/block-provider.md
Normal file
@ -0,0 +1,25 @@
|
||||
# BlockProvider
|
||||
|
||||
## 内核方法
|
||||
|
||||
### `<BlockProvider />`
|
||||
|
||||
### `useBlockRequestContext()`
|
||||
|
||||
## 内置 BlockProvider 组件
|
||||
|
||||
### `<CalendarBlockProvider />`
|
||||
|
||||
### `<TableFieldProvider />`
|
||||
|
||||
### `<TableBlockProvider />`
|
||||
|
||||
### `<TableSelectorProvider />`
|
||||
|
||||
### `<FormBlockProvider />`
|
||||
|
||||
### `<FormFieldProvider />`
|
||||
|
||||
### `<DetailsBlockProvider />`
|
||||
|
||||
### `<KanbanBlockProvider />`
|
267
docs/tr-TR/api/client/extensions/collection-manager.md
Normal file
267
docs/tr-TR/api/client/extensions/collection-manager.md
Normal file
@ -0,0 +1,267 @@
|
||||
# CollectionManager
|
||||
|
||||
## Components
|
||||
|
||||
### CollectionManagerProvider
|
||||
|
||||
```jsx | pure
|
||||
<CollectionManagerProvider interfaces={{}} collections={[]}></CollectionManagerProvider>
|
||||
```
|
||||
|
||||
### CollectionProvider
|
||||
|
||||
```jsx | pure
|
||||
const collection = {
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
<CollectionProvider collection={collection}></CollectionProvider>
|
||||
```
|
||||
|
||||
如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。
|
||||
|
||||
```jsx | pure
|
||||
const collections = [
|
||||
{
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
<CollectionManagerProvider collections={collections}>
|
||||
<CollectionProvider name={'tests'}></CollectionProvider>
|
||||
</CollectionManagerProvider>
|
||||
```
|
||||
|
||||
### CollectionFieldProvider
|
||||
|
||||
```jsx | pure
|
||||
const field = {
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
};
|
||||
<CollectionFieldProvider field={field}></CollectionFieldProvider>
|
||||
```
|
||||
|
||||
如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。
|
||||
|
||||
```jsx | pure
|
||||
const collection = {
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
<CollectionProvider collection={collection}>
|
||||
<CollectionFieldProvider name={'title'}></CollectionFieldProvider>
|
||||
</CollectionProvider>
|
||||
```
|
||||
|
||||
### CollectionField
|
||||
|
||||
万能字段组件,需要与 `<CollectionProvider/>` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'title',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'CollectionField',
|
||||
'x-component-props': {},
|
||||
properties: {},
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### useCollectionManager()
|
||||
|
||||
与 `<CollectionManagerProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { collections, get } = useCollectionManager();
|
||||
```
|
||||
|
||||
### useCollection()
|
||||
|
||||
与 `<CollectionProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { name, fields, getField, findField, resource } = useCollection();
|
||||
```
|
||||
|
||||
### useCollectionField()
|
||||
|
||||
与 `<CollectionFieldProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { name, uiSchema, resource } = useCollectionField();
|
||||
```
|
||||
|
||||
resource 需要与 `<RecordProvider/>` 搭配使用,用于提供当前数据表行记录的上下文。如:
|
||||
|
||||
# CollectionManager
|
||||
|
||||
## Components
|
||||
|
||||
### CollectionManagerProvider
|
||||
|
||||
```jsx | pure
|
||||
<CollectionManagerProvider interfaces={{}} collections={[]}></CollectionManagerProvider>
|
||||
```
|
||||
|
||||
### CollectionProvider
|
||||
|
||||
```jsx | pure
|
||||
const collection = {
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
<CollectionProvider collection={collection}></CollectionProvider>
|
||||
```
|
||||
|
||||
如果没有传 collection 参数,从 CollectionManagerProvider 里取对应 name 的 collection。
|
||||
|
||||
```jsx | pure
|
||||
const collections = [
|
||||
{
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
<CollectionManagerProvider collections={collections}>
|
||||
<CollectionProvider name={'tests'}></CollectionProvider>
|
||||
</CollectionManagerProvider>
|
||||
```
|
||||
|
||||
### CollectionFieldProvider
|
||||
|
||||
```jsx | pure
|
||||
const field = {
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
};
|
||||
<CollectionFieldProvider field={field}></CollectionFieldProvider>
|
||||
```
|
||||
|
||||
如果没有传 field 参数,从 CollectionProvider 里取对应 name 的 field。
|
||||
|
||||
```jsx | pure
|
||||
const collection = {
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
type: 'string',
|
||||
'x-component': 'Input'
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
<CollectionProvider collection={collection}>
|
||||
<CollectionFieldProvider name={'title'}></CollectionFieldProvider>
|
||||
</CollectionProvider>
|
||||
```
|
||||
|
||||
### CollectionField
|
||||
|
||||
万能字段组件,需要与 `<CollectionProvider/>` 搭配使用,仅限于在 Schema 场景使用。从 CollectionProvider 里取对应 name 的 field schema。可通过 CollectionField 所在的 schema 扩展配置。
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'title',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {},
|
||||
'x-component': 'CollectionField',
|
||||
'x-component-props': {},
|
||||
properties: {},
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
### useCollectionManager()
|
||||
|
||||
与 `<CollectionManagerProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { collections, get } = useCollectionManager();
|
||||
```
|
||||
|
||||
### useCollection()
|
||||
|
||||
与 `<CollectionProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { name, fields, getField, findField, resource } = useCollection();
|
||||
```
|
||||
|
||||
### useCollectionField()
|
||||
|
||||
与 `<CollectionFieldProvider/>` 搭配使用
|
||||
|
||||
```jsx | pure
|
||||
const { name, uiSchema, resource } = useCollectionField();
|
||||
```
|
||||
|
||||
resource 需要与 `<RecordProvider/>` 搭配使用,用于提供当前数据表行记录的上下文。
|
14
docs/tr-TR/api/client/extensions/schema-component.md
Normal file
14
docs/tr-TR/api/client/extensions/schema-component.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 适配的 Schema 组件
|
||||
|
||||
## Common
|
||||
|
||||
- DndContext
|
||||
- SortableItem
|
||||
|
||||
## And Design
|
||||
|
||||
- Action
|
||||
- BlockItem
|
||||
- Calendar
|
||||
- CardItem
|
||||
- Cascader
|
3
docs/tr-TR/api/client/index.md
Normal file
3
docs/tr-TR/api/client/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Overview
|
||||
|
||||
test
|
79
docs/tr-TR/api/client/route-switch.md
Normal file
79
docs/tr-TR/api/client/route-switch.md
Normal file
@ -0,0 +1,79 @@
|
||||
# RouteSwitch
|
||||
|
||||
## `<RouteSwitchProvider />`
|
||||
|
||||
```ts
|
||||
interface RouteSwitchProviderProps {
|
||||
components?: ReactComponent;
|
||||
routes?: RouteRedirectProps[];
|
||||
}
|
||||
```
|
||||
|
||||
## `<RouteSwitch />`
|
||||
|
||||
```ts
|
||||
interface RouteSwitchProps {
|
||||
routes?: RouteRedirectProps[];
|
||||
components?: ReactComponent;
|
||||
}
|
||||
|
||||
type RouteRedirectProps = RedirectProps | RouteProps;
|
||||
|
||||
interface RedirectProps {
|
||||
type: 'redirect';
|
||||
to: any;
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
push?: boolean;
|
||||
from?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface RouteProps {
|
||||
type: 'route';
|
||||
path?: string | string[];
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
sensitive?: boolean;
|
||||
component?: any;
|
||||
routes?: RouteProps[];
|
||||
[key: string]: any;
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { Link, MemoryRouter as Router } from 'react-router-dom';
|
||||
import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client';
|
||||
|
||||
const Home = () => <h1>Home</h1>;
|
||||
const About = () => <h1>About</h1>;
|
||||
|
||||
const routes: RouteRedirectProps[] = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/about',
|
||||
component: 'About',
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<RouteSwitchProvider components={{ Home, About }}>
|
||||
<Router initialEntries={['/']}>
|
||||
<Link to={'/'}>Home</Link>, <Link to={'/about'}>About</Link>
|
||||
<RouteSwitch routes={routes} />
|
||||
</Router>
|
||||
</RouteSwitchProvider>
|
||||
);
|
||||
};
|
||||
```
|
13
docs/tr-TR/api/client/schema-designer/schema-component.md
Normal file
13
docs/tr-TR/api/client/schema-designer/schema-component.md
Normal file
@ -0,0 +1,13 @@
|
||||
# SchemaComponent
|
||||
|
||||
## 核心组件
|
||||
|
||||
### `<SchemaComponentProvider />`
|
||||
### `<SchemaComponentOptions>`
|
||||
### `<SchemaComponent>`
|
||||
|
||||
## 核心方法
|
||||
|
||||
### `createDesignable()`
|
||||
### `useDesignable()`
|
||||
### `useCompile()`
|
27
docs/tr-TR/api/client/schema-designer/schema-initializer.md
Normal file
27
docs/tr-TR/api/client/schema-designer/schema-initializer.md
Normal file
@ -0,0 +1,27 @@
|
||||
# SchemaInitializer
|
||||
|
||||
用于各种 schema 的初始化。新增的 schema 可以插入到某个已有 schema 节点的任意位置,包括:
|
||||
|
||||
```ts
|
||||
{
|
||||
properties: {
|
||||
// beforeBegin 在当前节点的前面插入
|
||||
node1: {
|
||||
properties: {
|
||||
// afterBegin 在当前节点的第一个子节点前面插入
|
||||
// ...
|
||||
// beforeEnd 在当前节点的最后一个子节点后面
|
||||
},
|
||||
},
|
||||
// afterEnd 在当前节点的后面
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
SchemaInitializer 的核心包括 `<SchemaInitializer.Button />` 和 `<SchemaInitializer.Item />` 两个组件。`<SchemaInitializer.Button />` 用于创建 Schema 的下拉菜单按钮,下拉菜单的菜单项为 `<SchemaInitializer.Item/>`。
|
||||
|
||||
### `<SchemaInitializerProvider />`
|
||||
|
||||
### `<SchemaInitializer.Button />`
|
||||
|
||||
### `<SchemaInitializer.Item/>`
|
25
docs/tr-TR/api/client/schema-designer/schema-settings.md
Normal file
25
docs/tr-TR/api/client/schema-designer/schema-settings.md
Normal file
@ -0,0 +1,25 @@
|
||||
# SchemaSettings
|
||||
|
||||
### `<SchemaSettings />`
|
||||
|
||||
### `<SchemaSettings.Item />`
|
||||
|
||||
### `<SchemaSettings.ItemGroup />`
|
||||
|
||||
### `<SchemaSettings.SubMenu />`
|
||||
|
||||
### `<SchemaSettings.Divider />`
|
||||
|
||||
### `<SchemaSettings.Remove />`
|
||||
|
||||
### `<SchemaSettings.SelectItem />`
|
||||
|
||||
### `<SchemaSettings.SwitchItem />`
|
||||
|
||||
### `<SchemaSettings.ModalItem />`
|
||||
|
||||
### `<SchemaSettings.ActionModalItem />`
|
||||
|
||||
### `<SchemaSettings.Template />`
|
||||
|
||||
### `<SchemaSettings.BlockTitleItem />`
|
484
docs/tr-TR/api/database/collection.md
Normal file
484
docs/tr-TR/api/database/collection.md
Normal file
@ -0,0 +1,484 @@
|
||||
# Collection
|
||||
|
||||
数据表结构管理类。
|
||||
|
||||
大部分接口通常不会直接由开发者调用,除非进行较底层的扩展开发。
|
||||
|
||||
## 构造函数
|
||||
|
||||
通常不会直接使用,主要通过 `Database` 实例的 `collection` 方法作为代理入口调用。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: CollectionOptions, context: CollectionContext)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.name` | `string` | - | collection 标识 |
|
||||
| `options.tableName?` | `string` | - | 数据库表名,如不传则使用 `options.name` 的值 |
|
||||
| `options.fields?` | `FieldOptions[]` | - | 字段定义,详见 [Field](./field) |
|
||||
| `options.model?` | `string \| ModelCtor<Model>` | - | Sequelize 的 Model 类型,如果使用的是 `string`,则需要调用之前在 db 上注册过该模型名称 |
|
||||
| `options.repository?` | `string \| RepositoryType` | - | 数据仓库类型,如果使用 `string`,则需要调用之前在 db 上注册过该仓库类型 |
|
||||
| `options.sortable?` | `string \| boolean \| { name?: string; scopeKey?: string }` | - | 数据可排序字段配置,默认不排序 |
|
||||
| `options.autoGenId?` | `boolean` | `true` | 是否自动生成唯一主键,默认为 `true` |
|
||||
| `context.database` | `Database` | - | 所在的上下文环境数据库 |
|
||||
|
||||
**示例**
|
||||
|
||||
创建一张文章表:
|
||||
|
||||
```ts
|
||||
const posts = new Collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
},
|
||||
{
|
||||
type: 'double',
|
||||
name: 'price',
|
||||
}
|
||||
]
|
||||
}, {
|
||||
// 已存在的数据库实例
|
||||
database: db
|
||||
});
|
||||
```
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `options`
|
||||
|
||||
数据表配置初始参数。与构造函数的 `options` 参数一致。
|
||||
|
||||
### `context`
|
||||
|
||||
当前数据表所属的上下文环境,目前主要是数据库实例。
|
||||
|
||||
### `name`
|
||||
|
||||
数据表名称。
|
||||
|
||||
### `db`
|
||||
|
||||
所属数据库实例。
|
||||
|
||||
### `filterTargetKey`
|
||||
|
||||
作为主键的字段名。
|
||||
|
||||
### `isThrough`
|
||||
|
||||
是否为中间表。
|
||||
|
||||
### `model`
|
||||
|
||||
匹配 Sequelize 的 Model 类型。
|
||||
|
||||
### `repository`
|
||||
|
||||
数据仓库实例。
|
||||
|
||||
## 字段配置方法
|
||||
|
||||
### `getField()`
|
||||
|
||||
获取数据表已定义对应名称的字段对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `getField(name: string): Field`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 字段名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const field = posts.getField('title');
|
||||
```
|
||||
|
||||
### `setField()`
|
||||
|
||||
对数据表设置字段。
|
||||
|
||||
**签名**
|
||||
|
||||
* `setField(name: string, options: FieldOptions): Field`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 字段名称 |
|
||||
| `options` | `FieldOptions` | - | 字段配置,详见 [Field](./field) |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({ name: 'posts' });
|
||||
|
||||
posts.setField('title', { type: 'string' });
|
||||
```
|
||||
|
||||
### `setFields()`
|
||||
|
||||
对数据表批量设置多个字段。
|
||||
|
||||
**签名**
|
||||
|
||||
* `setFields(fields: FieldOptions[], resetFields = true): Field[]`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `fields` | `FieldOptions[]` | - | 字段配置,详见 [Field](./field) |
|
||||
| `resetFields` | `boolean` | `true` | 是否重置已存在的字段 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({ name: 'posts' });
|
||||
|
||||
posts.setFields([
|
||||
{ type: 'string', name: 'title' },
|
||||
{ type: 'double', name: 'price' }
|
||||
]);
|
||||
```
|
||||
|
||||
### `removeField()`
|
||||
|
||||
移除数据表已定义对应名称的字段对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `removeField(name: string): void | Field`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 字段名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.removeField('title');
|
||||
```
|
||||
|
||||
### `resetFields()`
|
||||
|
||||
重置(清空)数据表的字段。
|
||||
|
||||
**签名**
|
||||
|
||||
* `resetFields(): void`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.resetFields();
|
||||
```
|
||||
|
||||
### `hasField()`
|
||||
|
||||
判断数据表是否已定义对应名称的字段对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `hasField(name: string): boolean`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 字段名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.hasField('title'); // true
|
||||
```
|
||||
|
||||
### `findField()`
|
||||
|
||||
查找数据表中符合条件的字段对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `findField(predicate: (field: Field) => boolean): Field | undefined`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `predicate` | `(field: Field) => boolean` | - | 查找条件 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.findField(field => field.name === 'title');
|
||||
```
|
||||
|
||||
### `forEachField()`
|
||||
|
||||
遍历数据表中的字段对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `forEachField(callback: (field: Field) => void): void`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `callback` | `(field: Field) => void` | - | 回调函数 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.forEachField(field => console.log(field.name));
|
||||
```
|
||||
|
||||
## 索引配置方法
|
||||
|
||||
### `addIndex()`
|
||||
|
||||
添加数据表索引。
|
||||
|
||||
**签名**
|
||||
|
||||
* `addIndex(index: string | string[] | { fields: string[], unique?: boolean,[key: string]: any })`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `index` | `string \| string[]` | - | 需要配置索引的字段名 |
|
||||
| `index` | `{ fields: string[], unique?: boolean, [key: string]: any }` | - | 完整配置 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.addIndex({
|
||||
fields: ['title'],
|
||||
unique: true
|
||||
});
|
||||
```
|
||||
|
||||
### `removeIndex()`
|
||||
|
||||
移除数据表索引。
|
||||
|
||||
**签名**
|
||||
|
||||
* `removeIndex(fields: string[])`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `fields` | `string[]` | - | 需要移除索引的字段名组合 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
],
|
||||
indexes: [
|
||||
{
|
||||
fields: ['title'],
|
||||
unique: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.removeIndex(['title']);
|
||||
```
|
||||
|
||||
## 表配置方法
|
||||
|
||||
### `remove()`
|
||||
|
||||
删除数据表。
|
||||
|
||||
**签名**
|
||||
|
||||
* `remove(): void`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
posts.remove();
|
||||
```
|
||||
|
||||
## 数据库操作方法
|
||||
|
||||
### `sync()`
|
||||
|
||||
同步数据表定义到数据库。除了 Sequelize 中默认的 `Model.sync` 的逻辑,还会一并处理关系字段对应的数据表。
|
||||
|
||||
**签名**
|
||||
|
||||
* `sync(): Promise<void>`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await posts.sync();
|
||||
```
|
||||
|
||||
### `existsInDb()`
|
||||
|
||||
判断数据表是否存在于数据库中。
|
||||
|
||||
**签名**
|
||||
|
||||
* `existsInDb(options?: Transactionable): Promise<boolean>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options?.transaction` | `Transaction` | - | 事务实例 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const existed = await posts.existsInDb();
|
||||
|
||||
console.log(existed); // false
|
||||
```
|
||||
|
||||
### `removeFromDb()`
|
||||
|
||||
**签名**
|
||||
|
||||
* `removeFromDb(): Promise<void>`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const books = db.collection({
|
||||
name: 'books'
|
||||
});
|
||||
|
||||
// 同步书籍表到数据库
|
||||
await db.sync();
|
||||
|
||||
// 删除数据库中的书籍表
|
||||
await books.removeFromDb();
|
||||
```
|
554
docs/tr-TR/api/database/field.md
Normal file
554
docs/tr-TR/api/database/field.md
Normal file
@ -0,0 +1,554 @@
|
||||
# Field
|
||||
|
||||
数据表字段管理类(抽象类)。同时是所有字段类型的基类,其他任意字段类型均通过继承该类来实现。
|
||||
|
||||
## 构造函数
|
||||
|
||||
通常不会直接由开发者调用,主要通过 `db.collection({ fields: [] })` 方法作为代理入口调用。
|
||||
|
||||
在扩展字段时主要通过继承 `Field` 抽象类,再注册到 Database 实例中来实现。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: FieldOptions, context: FieldContext)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `FieldOptions` | - | 字段配置对象 |
|
||||
| `options.name` | `string` | - | 字段名称 |
|
||||
| `options.type` | `string` | - | 字段类型,对应在 db 中注册的字段类型名称 |
|
||||
| `context` | `FieldContext` | - | 字段上下文对象 |
|
||||
| `context.database` | `Database` | - | 数据库实例 |
|
||||
| `context.collection` | `Collection` | - | 数据表实例 |
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `name`
|
||||
|
||||
字段名称。
|
||||
|
||||
### `type`
|
||||
|
||||
字段类型。
|
||||
|
||||
### `dataType`
|
||||
|
||||
字段数据库存储类型。
|
||||
|
||||
### `options`
|
||||
|
||||
字段初始化配置参数。
|
||||
|
||||
### `context`
|
||||
|
||||
字段上下文对象。
|
||||
|
||||
## 配置方法
|
||||
|
||||
### `on()`
|
||||
|
||||
基于数据表事件的快捷定义方式。相当于 `db.on(this.collection.name + '.' + eventName, listener)`。
|
||||
|
||||
继承时通常无需覆盖此方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `on(eventName: string, listener: (...args: any[]) => void)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `eventName` | `string` | - | 事件名称 |
|
||||
| `listener` | `(...args: any[]) => void` | - | 事件监听器 |
|
||||
|
||||
### `off()`
|
||||
|
||||
基于数据表事件的快捷移除方式。相当于 `db.off(this.collection.name + '.' + eventName, listener)`。
|
||||
|
||||
继承时通常无需覆盖此方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `off(eventName: string, listener: (...args: any[]) => void)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `eventName` | `string` | - | 事件名称 |
|
||||
| `listener` | `(...args: any[]) => void` | - | 事件监听器 |
|
||||
|
||||
### `bind()`
|
||||
|
||||
当字段被添加到数据表时触发的执行内容。通常用于添加数据表事件监听器和其他处理。
|
||||
|
||||
继承时需要先调用对应的 `super.bind()` 方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `bind()`
|
||||
|
||||
### `unbind()`
|
||||
|
||||
当字段从数据表中移除时触发的执行内容。通常用于移除数据表事件监听器和其他处理。
|
||||
|
||||
继承时需要先调用对应的 `super.unbind()` 方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `unbind()`
|
||||
|
||||
### `get()`
|
||||
|
||||
获取字段的配置项的值。
|
||||
|
||||
**签名**
|
||||
|
||||
* `get(key: string): any`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `key` | `string` | - | 配置项名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const field = db.collection('users').getField('name');
|
||||
|
||||
// 获取字段名称配置项的值,返回 'name'
|
||||
console.log(field.get('name'));
|
||||
```
|
||||
|
||||
### `merge()`
|
||||
|
||||
合并字段的配置项的值。
|
||||
|
||||
**签名**
|
||||
|
||||
* `merge(options: { [key: string]: any }): void`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `{ [key: string]: any }` | - | 要合并的配置项对象 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const field = db.collection('users').getField('name');
|
||||
|
||||
field.merge({
|
||||
// 添加一个索引配置
|
||||
index: true
|
||||
});
|
||||
```
|
||||
|
||||
### `remove()`
|
||||
|
||||
从数据表中移除字段(仅从内存中移除)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const books = db.getCollections('books');
|
||||
|
||||
books.getField('isbn').remove();
|
||||
|
||||
// really remove from db
|
||||
await books.sync();
|
||||
```
|
||||
|
||||
## 数据库方法
|
||||
|
||||
### `removeFromDb()`
|
||||
|
||||
从数据库中移除字段。
|
||||
|
||||
**签名**
|
||||
|
||||
* `removeFromDb(options?: Transactionable): Promise<void>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.transaction?` | `Transaction` | - | 事务实例 |
|
||||
|
||||
### `existsInDb()`
|
||||
|
||||
判断字段是否存在于数据库中。
|
||||
|
||||
**签名**
|
||||
|
||||
* `existsInDb(options?: Transactionable): Promise<boolean>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.transaction?` | `Transaction` | - | 事务实例 |
|
||||
|
||||
## 内置字段类型列表
|
||||
|
||||
NocoBase 内置了一些常用的字段类型,可以直接在定义数据表的字段是使用对应的 type 名称来指定类型。不同类型的字段参数配置不同,具体可参考下面的列表。
|
||||
|
||||
所有字段类型的配置项除了以下额外介绍的以外,都会透传至 Sequelize,所以所有 Sequelize 支持的字段配置项都可以在这里使用(如 `allowNull`、`defaultValue` 等)。
|
||||
|
||||
另外 server 端的字段类型主要解决数据库存储和部分算法的问题,与前端的字段展示类型和使用组件基本无关。前端字段类型可以参考教程对应说明。
|
||||
|
||||
### `'boolean'`
|
||||
|
||||
逻辑值类型。
|
||||
|
||||
**示例**
|
||||
|
||||
```js
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'published'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'integer'`
|
||||
|
||||
整型(32 位)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'pages'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'bigInt'`
|
||||
|
||||
长整型(64 位)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'bigInt',
|
||||
name: 'words'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'double'`
|
||||
|
||||
双精度浮点型(64 位)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'double',
|
||||
name: 'price'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'real'`
|
||||
|
||||
实数类型(仅 PG 适用)。
|
||||
|
||||
### `'decimal'`
|
||||
|
||||
十进制小数类型。
|
||||
|
||||
### `'string'`
|
||||
|
||||
字符串类型。相当于大部分数据库的 `VARCHAR` 类型。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'text'`
|
||||
|
||||
文本类型。相当于大部分数据库的 `TEXT` 类型。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'password'`
|
||||
|
||||
密码类型(NocoBase 扩展)。基于 Node.js 原生的 crypto 包的 `scrypt` 方法进行密码加密。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
length: 64, // 长度,默认 64
|
||||
randomBytesSize: 8 // 随机字节长度,默认 8
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `length` | `number` | 64 | 字符长度 |
|
||||
| `randomBytesSize` | `number` | 8 | 随机字节大小 |
|
||||
|
||||
### `'date'`
|
||||
|
||||
日期类型。
|
||||
|
||||
### `'time'`
|
||||
|
||||
时间类型。
|
||||
|
||||
### `'array'`
|
||||
|
||||
数组类型(仅 PG 适用)。
|
||||
|
||||
### `'json'`
|
||||
|
||||
JSON 类型。
|
||||
|
||||
### `'jsonb'`
|
||||
|
||||
JSONB 类型(仅 PG 适用,其他会被兼容为 `'json'` 类型)。
|
||||
|
||||
### `'uuid'`
|
||||
|
||||
UUID 类型。
|
||||
|
||||
### `'uid'`
|
||||
|
||||
UID 类型(NocoBase 扩展)。短随机字符串标识符类型。
|
||||
|
||||
### `'formula'`
|
||||
|
||||
公式类型(NocoBase 扩展)。可配置基于 [mathjs](https://www.npmjs.com/package/mathjs) 的数学公式计算,公式中可以引用同一条记录中其他列的数值参与计算。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'orders',
|
||||
fields: [
|
||||
{
|
||||
type: 'double',
|
||||
name: 'price'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'quantity'
|
||||
},
|
||||
{
|
||||
type: 'formula',
|
||||
name: 'total',
|
||||
expression: 'price * quantity'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'radio'`
|
||||
|
||||
单选类型(NocoBase 扩展)。全表最多有一行数据的该字段值为 `true`,其他都为 `false` 或 `null`。
|
||||
|
||||
**示例**
|
||||
|
||||
整个系统只有一个被标记为 root 的用户,任意另一个用户的 root 值被改为 `true` 之后,其他所有 root 为 `true` 的记录均会被修改为 `false`:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'radio',
|
||||
name: 'root',
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'sort'`
|
||||
|
||||
排序类型(NocoBase 扩展)。基于整型数字进行排序,为新记录自动生成新序号,当移动数据时进行序号重排。
|
||||
|
||||
数据表如果定义了 `sortable` 选项,也会自动生成对应字段。
|
||||
|
||||
**示例**
|
||||
|
||||
文章基于所属用户可排序:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
},
|
||||
{
|
||||
type: 'sort',
|
||||
name: 'priority',
|
||||
scopeKey: 'userId' // 以 userId 相同值分组的数据进行排序
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'virtual'`
|
||||
|
||||
虚拟类型。不实际储存数据,仅用于特殊 getter/setter 定义时使用。
|
||||
|
||||
### `'belongsTo'`
|
||||
|
||||
多对一关联类型。外键储存在自身表,与 hasOne/hasMany 相对。
|
||||
|
||||
**示例**
|
||||
|
||||
任意文章属于某个作者:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'author',
|
||||
target: 'users', // 不配置默认为 name 复数名称的表名
|
||||
foreignKey: 'authorId', // 不配置默认为 <name> + Id 的格式
|
||||
sourceKey: 'id' // 不配置默认为 target 表的 id
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'hasOne'`
|
||||
|
||||
一对一关联类型。外键储存在关联表,与 belongsTo 相对。
|
||||
|
||||
**示例**
|
||||
|
||||
任意用户都有一份个人资料:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasOne',
|
||||
name: 'profile',
|
||||
target: 'profiles', // 可省略
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### `'hasMany'`
|
||||
|
||||
一对多关联类型。外键储存在关联表,与 belongsTo 相对。
|
||||
|
||||
**示例**
|
||||
|
||||
任意用户可以拥有多篇文章:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'posts',
|
||||
foreignKey: 'authorId',
|
||||
sourceKey: 'id'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### `'belongsToMany'`
|
||||
|
||||
多对多关联类型。使用中间表储存双方外键,如不指定已存在的表为中间表的话,将会自动创建中间表。
|
||||
|
||||
**示例**
|
||||
|
||||
任意文章可以加任意多个标签,任意标签也可以被任意多篇文章添加:
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'tags',
|
||||
target: 'tags', // 同名可省略
|
||||
through: 'postsTags', // 中间表不配置将自动生成
|
||||
foreignKey: 'postId', // 自身表在中间表的外键
|
||||
sourceKey: 'id', // 自身表的主键
|
||||
otherKey: 'tagId' // 关联表在中间表的外键
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'tags',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'posts',
|
||||
through: 'postsTags', // 同一组关系指向同一张中间表
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
1130
docs/tr-TR/api/database/index.md
Normal file
1130
docs/tr-TR/api/database/index.md
Normal file
File diff suppressed because it is too large
Load Diff
816
docs/tr-TR/api/database/operators.md
Normal file
816
docs/tr-TR/api/database/operators.md
Normal file
@ -0,0 +1,816 @@
|
||||
# Filter Operators
|
||||
|
||||
用于 Repository 的 find、findOne、findAndCount、count 等 API 的 filter 参数里。如:
|
||||
|
||||
```ts
|
||||
const repository = db.getRepository('books');
|
||||
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$eq: '春秋',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
相当于 Sequelize Where 查询的 [Op](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators) 对象。
|
||||
|
||||
为了支持 JSON 化,NocoBase 中将查询运算符转换为以 $ 为前缀的字符串标识。
|
||||
|
||||
另外,NocoBase 也提供了扩展运算符的 API,详见 [`db.registerOperators()`](../database#registeroperators)。
|
||||
|
||||
## 通用运算符
|
||||
|
||||
### `$eq`
|
||||
|
||||
判断字段值是否相等于指定值。相当于 SQL 的 `=`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$eq: '春秋',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
等同于 `title: '春秋'`。
|
||||
|
||||
### `$ne`
|
||||
|
||||
判断字段值是否不等于指定值。相当于 SQL 的 `!=`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$ne: '春秋',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$is`
|
||||
|
||||
判断字段值是否为指定值。相当于 SQL 的 `IS`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$is: null,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$not`
|
||||
|
||||
判断字段值是否不为指定值。相当于 SQL 的 `IS NOT`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$not: null,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$col`
|
||||
|
||||
判断字段值是否等于另一个字段的值。相当于 SQL 的 `=`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$col: 'name',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$in`
|
||||
|
||||
判断字段值是否在指定数组中。相当于 SQL 的 `IN`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$in: ['春秋', '战国'],
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notIn`
|
||||
|
||||
判断字段值是否不在指定数组中。相当于 SQL 的 `NOT IN`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notIn: ['春秋', '战国'],
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$empty`
|
||||
|
||||
判断一般字段是否为空,如果是字符串字段,判断是否为空串,如果是数组字段,判断是否为空数组。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$empty: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notEmpty`
|
||||
|
||||
判断一般字段是否不为空,如果是字符串字段,判断是否不为空串,如果是数组字段,判断是否不为空数组。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notEmpty: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 逻辑运算符
|
||||
|
||||
### `$and`
|
||||
|
||||
逻辑 AND。相当于 SQL 的 `AND`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
$and: [
|
||||
{ title: '诗经' },
|
||||
{ isbn: '1234567890' },
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$or`
|
||||
|
||||
逻辑 OR。相当于 SQL 的 `OR`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
$or: [
|
||||
{ title: '诗经' },
|
||||
{ publishedAt: { $lt: '0000-00-00T00:00:00Z' } },
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 布尔类型字段运算符
|
||||
|
||||
用于布尔类型字段 `type: 'boolean'`
|
||||
|
||||
### `$isFalsy`
|
||||
|
||||
判断布尔类型字段值是否为假。布尔字段值为 `false`、`0` 和 `NULL` 的情况都会被判断为 `$isFalsy: true`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
isPublished: {
|
||||
$isFalsy: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$isTruly`
|
||||
|
||||
判断布尔类型字段值是否为真。布尔字段值为 `true` 和 `1` 的情况都会被判断为 `$isTruly: true`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
isPublished: {
|
||||
$isTruly: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 数字类型字段运算符
|
||||
|
||||
用于数字类型字段,包括:
|
||||
|
||||
- `type: 'integer'`
|
||||
- `type: 'float'`
|
||||
- `type: 'double'`
|
||||
- `type: 'real'`
|
||||
- `type: 'decimal'`
|
||||
|
||||
### `$gt`
|
||||
|
||||
判断字段值是否大于指定值。相当于 SQL 的 `>`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$gt: 100,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$gte`
|
||||
|
||||
判断字段值是否大于等于指定值。相当于 SQL 的 `>=`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$gte: 100,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$lt`
|
||||
|
||||
判断字段值是否小于指定值。相当于 SQL 的 `<`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$lt: 100,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$lte`
|
||||
|
||||
判断字段值是否小于等于指定值。相当于 SQL 的 `<=`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$lte: 100,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$between`
|
||||
|
||||
判断字段值是否在指定的两个值之间。相当于 SQL 的 `BETWEEN`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$between: [100, 200],
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notBetween`
|
||||
|
||||
判断字段值是否不在指定的两个值之间。相当于 SQL 的 `NOT BETWEEN`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
price: {
|
||||
$notBetween: [100, 200],
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 字符串类型字段运算符
|
||||
|
||||
用于字符串类型字段,包括 `string`
|
||||
|
||||
### `$includes`
|
||||
|
||||
判断字符串字段是否包含指定子串。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$includes: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$notIncludes`
|
||||
|
||||
判断字符串字段是否不包含指定子串。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notIncludes: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$startsWith`
|
||||
|
||||
判断字符串字段是否以指定子串开头。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$startsWith: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$notStatsWith`
|
||||
|
||||
判断字符串字段是否不以指定子串开头。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notStatsWith: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$endsWith`
|
||||
|
||||
判断字符串字段是否以指定子串结尾。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$endsWith: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$notEndsWith`
|
||||
|
||||
判断字符串字段是否不以指定子串结尾。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notEndsWith: '三字经',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$like`
|
||||
|
||||
判断字段值是否包含指定的字符串。相当于 SQL 的 `LIKE`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$like: '计算机',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notLike`
|
||||
|
||||
判断字段值是否不包含指定的字符串。相当于 SQL 的 `NOT LIKE`。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notLike: '计算机',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$iLike`
|
||||
|
||||
判断字段值是否包含指定的字符串,忽略大小写。相当于 SQL 的 `ILIKE`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$iLike: 'Computer',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notILike`
|
||||
|
||||
判断字段值是否不包含指定的字符串,忽略大小写。相当于 SQL 的 `NOT ILIKE`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notILike: 'Computer',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$regexp`
|
||||
|
||||
判断字段值是否匹配指定的正则表达式。相当于 SQL 的 `REGEXP`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$regexp: '^计算机',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notRegexp`
|
||||
|
||||
判断字段值是否不匹配指定的正则表达式。相当于 SQL 的 `NOT REGEXP`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notRegexp: '^计算机',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$iRegexp`
|
||||
|
||||
判断字段值是否匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `~*`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$iRegexp: '^COMPUTER',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notIRegexp`
|
||||
|
||||
判断字段值是否不匹配指定的正则表达式,忽略大小写。相当于 SQL 的 `!~*`(仅 PG 适用)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
title: {
|
||||
$notIRegexp: '^COMPUTER',
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 日期类型字段运算符
|
||||
|
||||
用于日期类型字段 `type: 'date'`
|
||||
|
||||
### `$dateOn`
|
||||
|
||||
判断日期字段是否在某天内。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateOn: '2021-01-01',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$dateNotOn`
|
||||
|
||||
判断日期字段是否不在某天内。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateNotOn: '2021-01-01',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$dateBefore`
|
||||
|
||||
判断日期字段是否在某个值之前。相当于小于传入的日期值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateBefore: '2021-01-01T00:00:00.000Z',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$dateNotBefore`
|
||||
|
||||
判断日期字段是否不在某个值之前。相当于大于等于传入的日期值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateNotBefore: '2021-01-01T00:00:00.000Z',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$dateAfter`
|
||||
|
||||
判断日期字段是否在某个值之后。相当于大于传入的日期值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateAfter: '2021-01-01T00:00:00.000Z',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$dateNotAfter`
|
||||
|
||||
判断日期字段是否不在某个值之后。相当于小于等于传入的日期值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$dateNotAfter: '2021-01-01T00:00:00.000Z',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 数组类型字段运算符
|
||||
|
||||
用于数组类型字段 `type: 'array'`
|
||||
|
||||
### `$match`
|
||||
|
||||
判断数组字段的值是否匹配指定数组中的值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$match: ['文学', '历史'],
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$notMatch`
|
||||
|
||||
判断数组字段的值是否不匹配指定数组中的值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$notMatch: ['文学', '历史'],
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$anyOf`
|
||||
|
||||
判断数组字段的值是否包含指定数组中的任意值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$anyOf: ['文学', '历史'],
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$noneOf`
|
||||
|
||||
判断数组字段的值是否不包含指定数组中的任意值。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$noneOf: ['文学', '历史'],
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `$arrayEmpty`
|
||||
|
||||
判断数组字段是否为空。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$arrayEmpty: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$arrayNotEmpty`
|
||||
|
||||
判断数组字段是否不为空。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
tags: {
|
||||
$arrayNotEmpty: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 关系字段类型运算符
|
||||
|
||||
用于判断关系是否存在,字段类型包括:
|
||||
|
||||
- `type: 'hasOne'`
|
||||
- `type: 'hasMany'`
|
||||
- `type: 'belongsTo'`
|
||||
- `type: 'belongsToMany'`
|
||||
|
||||
### `$exists`
|
||||
|
||||
有关系数据
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
author: {
|
||||
$exists: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `$notExists`
|
||||
|
||||
无关系数据
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
repository.find({
|
||||
filter: {
|
||||
author: {
|
||||
$notExists: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
@ -0,0 +1,181 @@
|
||||
# BelongsToManyRepository
|
||||
`BelongsToManyRepository` 是用于处理 `BelongsToMany` 关系的 `Relation Repository`。
|
||||
|
||||
不同于其他关系类型,`BelongsToMany` 类型的关系需要通过中间表来记录。
|
||||
在 `Nocobase` 中定义关联关系,可自动创建中间表,也可以明确指定中间表。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `find()`
|
||||
|
||||
查找关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: FindOptions): Promise<M[]>`
|
||||
|
||||
**详细信息**
|
||||
|
||||
查询参数与 [`Repository.find()`](../repository.md#find) 一致。
|
||||
|
||||
### `findOne()`
|
||||
|
||||
查找关联对象,仅返回一条记录
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findOne(options?: FindOneOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/find-one.md"></embed>
|
||||
|
||||
|
||||
### `count()`
|
||||
|
||||
返回符合查询条件的记录数
|
||||
|
||||
**签名**
|
||||
|
||||
* `async count(options?: CountOptions)`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
|
||||
filter?: Filter;
|
||||
}
|
||||
```
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
从数据库查询特定条件的数据集和结果数。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
type FindAndCountOptions = CommonFindOptions
|
||||
```
|
||||
|
||||
### `create()`
|
||||
|
||||
创建关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create(options?: CreateOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/create-options.md"></embed>
|
||||
|
||||
### `update()`
|
||||
|
||||
更新符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update(options?: UpdateOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/update-options.md"></embed>
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destroy(options?: TargetKey | TargetKey[] | DestroyOptions): Promise<Boolean>`
|
||||
|
||||
<embed src="../shared/destroy-options.md"></embed>
|
||||
|
||||
### `add()`
|
||||
|
||||
添加新的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async add(
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions
|
||||
): Promise<void>`
|
||||
|
||||
**类型**
|
||||
|
||||
```typescript
|
||||
type PrimaryKeyWithThroughValues = [TargetKey, Values];
|
||||
|
||||
interface AssociatedOptions extends Transactionable {
|
||||
tk?: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[];
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
可以直接传入关联对象的 `targetKey`,也可将 `targetKey` 与中间表的字段值一并传入。
|
||||
|
||||
**示例**
|
||||
```typescript
|
||||
const t1 = await Tag.repository.create({
|
||||
values: { name: 't1' },
|
||||
});
|
||||
|
||||
const t2 = await Tag.repository.create({
|
||||
values: { name: 't2' },
|
||||
});
|
||||
|
||||
const p1 = await Post.repository.create({
|
||||
values: { title: 'p1' },
|
||||
});
|
||||
|
||||
const PostTagRepository = new BelongsToManyRepository(Post, 'tags', p1.id);
|
||||
|
||||
// 传入 targetKey
|
||||
PostTagRepository.add([
|
||||
t1.id, t2.id
|
||||
]);
|
||||
|
||||
// 传入中间表字段
|
||||
PostTagRepository.add([
|
||||
[t1.id, { tagged_at: '123' }],
|
||||
[t2.id, { tagged_at: '456' }],
|
||||
]);
|
||||
```
|
||||
|
||||
### `set()`
|
||||
|
||||
设置关联对象
|
||||
|
||||
**签名**
|
||||
* async set(
|
||||
options: TargetKey | TargetKey[] | PrimaryKeyWithThroughValues | PrimaryKeyWithThroughValues[] | AssociatedOptions,
|
||||
): Promise<void>
|
||||
|
||||
**详细信息**
|
||||
|
||||
参数同 [add()](#add)
|
||||
|
||||
### `remove()`
|
||||
|
||||
移除与给定对象之间的关联关系
|
||||
|
||||
**签名**
|
||||
* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface AssociatedOptions extends Transactionable {
|
||||
tk?: TargetKey | TargetKey[];
|
||||
}
|
||||
```
|
||||
|
||||
### `toggle()`
|
||||
|
||||
切换关联对象。
|
||||
|
||||
在一些业务场景中,经常需要切换关联对象,比如用户收藏商品,用户可以取消收藏,也可以再次收藏。使用 `toggle` 方法可以快速实现类似功能。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async toggle(options: TargetKey | { tk?: TargetKey; transaction?: Transaction }): Promise<void>`
|
||||
|
||||
**详细信息**
|
||||
|
||||
`toggle` 方法会自动判断关联对象是否已经存在,如果存在则移除,如果不存在则添加。
|
@ -0,0 +1,3 @@
|
||||
## BelongsToRepository
|
||||
|
||||
`BelongsToRepository` 是用于处理 `BelongsTo` 关系的 `Repository`,它提供了一些便捷的方法来处理 `BelongsTo` 关系。其接口与 [HasOneRepository](./has-one-repository.md) 一致。
|
@ -0,0 +1,132 @@
|
||||
|
||||
# HasManyRepository
|
||||
|
||||
`HasManyRepository` 是用于处理 `HasMany` 关系的 `Relation Repository`。
|
||||
|
||||
## 类方法
|
||||
|
||||
### `find()`
|
||||
|
||||
查找关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: FindOptions): Promise<M[]>`
|
||||
|
||||
**详细信息**
|
||||
|
||||
查询参数与 [`Repository.find()`](../repository.md#find) 一致。
|
||||
|
||||
### `findOne()`
|
||||
|
||||
查找关联对象,仅返回一条记录
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findOne(options?: FindOneOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/find-one.md"></embed>
|
||||
|
||||
|
||||
### `count()`
|
||||
|
||||
返回符合查询条件的记录数
|
||||
|
||||
**签名**
|
||||
|
||||
* `async count(options?: CountOptions)`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
|
||||
filter?: Filter;
|
||||
}
|
||||
```
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
从数据库查询特定条件的数据集和结果数。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findAndCount(options?: FindAndCountOptions): Promise<[any[], number]>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
type FindAndCountOptions = CommonFindOptions
|
||||
```
|
||||
|
||||
|
||||
### `create()`
|
||||
|
||||
创建关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create(options?: CreateOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/create-options.md"></embed>
|
||||
|
||||
### `update()`
|
||||
|
||||
更新符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update(options?: UpdateOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/update-options.md"></embed>
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除符合条件的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destroy(options?: TK | DestroyOptions): Promise<M>`
|
||||
|
||||
<embed src="../shared/destroy-options.md"></embed>
|
||||
|
||||
### `add()`
|
||||
|
||||
添加对象关联关系
|
||||
|
||||
**签名**
|
||||
* `async add(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface AssociatedOptions extends Transactionable {
|
||||
tk?: TargetKey | TargetKey[];
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `tk` - 关联对象的 targetKey 值,可以是单个值,也可以是数组。
|
||||
<embed src="../shared/transaction.md"></embed>
|
||||
|
||||
### `remove()`
|
||||
|
||||
移除与给定对象之间的关联关系
|
||||
|
||||
**签名**
|
||||
* `async remove(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**详细信息**
|
||||
|
||||
参数同 [`add()`](#add) 方法。
|
||||
|
||||
### `set()`
|
||||
|
||||
设置当前关系的关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async set(options: TargetKey | TargetKey[] | AssociatedOptions)`
|
||||
|
||||
**详细信息**
|
||||
|
||||
参数同 [`add()`](#add) 方法。
|
||||
|
||||
|
@ -0,0 +1,181 @@
|
||||
# HasOneRepository
|
||||
`HasOneRepository` 为 `HasOne` 类型的关联 Repository。
|
||||
|
||||
```typescript
|
||||
const User = db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'hasOne', name: 'profile' },
|
||||
{ type: 'string', name: 'name' },
|
||||
],
|
||||
});
|
||||
|
||||
const Profile = db.collection({
|
||||
name: 'profiles',
|
||||
fields: [{ type: 'string', name: 'avatar' }],
|
||||
});
|
||||
|
||||
const user = await User.repository.create({
|
||||
values: { name: 'u1' },
|
||||
});
|
||||
|
||||
// 创建 HasOneRepository 实例
|
||||
const userProfileRepository = new HasOneRepository(User, 'profile', user.get('id'));
|
||||
|
||||
```
|
||||
|
||||
## 类方法
|
||||
|
||||
### `find()`
|
||||
|
||||
查找关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: SingleRelationFindOption): Promise<Model<any> | null>`
|
||||
|
||||
**类型**
|
||||
|
||||
```typescript
|
||||
interface SingleRelationFindOption extends Transactionable {
|
||||
fields?: Fields;
|
||||
except?: Except;
|
||||
appends?: Appends;
|
||||
filter?: Filter;
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
查询参数与 [`Repository.find()`](../repository.md#find) 一致。
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.find();
|
||||
// 关联对象不存在时,返回 null
|
||||
```
|
||||
|
||||
### `create()`
|
||||
创建关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create(options?: CreateOptions): Promise<Model>`
|
||||
|
||||
<embed src="../shared/create-options.md"></embed>
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.create({
|
||||
values: { avatar: 'avatar1' },
|
||||
});
|
||||
|
||||
console.log(profile.toJSON());
|
||||
/*
|
||||
{
|
||||
id: 1,
|
||||
avatar: 'avatar1',
|
||||
userId: 1,
|
||||
updatedAt: 2022-09-24T13:59:40.025Z,
|
||||
createdAt: 2022-09-24T13:59:40.025Z
|
||||
}
|
||||
*/
|
||||
|
||||
```
|
||||
|
||||
### `update()`
|
||||
|
||||
更新关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update(options: UpdateOptions): Promise<Model>`
|
||||
|
||||
<embed src="../shared/update-options.md"></embed>
|
||||
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const profile = await UserProfileRepository.update({
|
||||
values: { avatar: 'avatar2' },
|
||||
});
|
||||
|
||||
profile.get('avatar'); // 'avatar2'
|
||||
```
|
||||
|
||||
### `remove()`
|
||||
|
||||
移除关联对象,仅解除关联关系,不删除关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async remove(options?: Transactionable): Promise<void>`
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
await UserProfileRepository.remove();
|
||||
await UserProfileRepository.find() == null; // true
|
||||
|
||||
await Profile.repository.count() === 1; // true
|
||||
```
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destroy(options?: Transactionable): Promise<Boolean>`
|
||||
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
await UserProfileRepository.destroy();
|
||||
await UserProfileRepository.find() == null; // true
|
||||
await Profile.repository.count() === 0; // true
|
||||
```
|
||||
|
||||
### `set()`
|
||||
|
||||
设置关联对象
|
||||
|
||||
**签名**
|
||||
|
||||
* `async set(options: TargetKey | SetOption): Promise<void>`
|
||||
|
||||
**类型**
|
||||
|
||||
```typescript
|
||||
interface SetOption extends Transactionable {
|
||||
tk?: TargetKey;
|
||||
}
|
||||
````
|
||||
**详细信息**
|
||||
|
||||
* tk: 设置关联对象的 targetKey
|
||||
* transaction: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
||||
|
||||
**示例**
|
||||
|
||||
```typescript
|
||||
const newProfile = await Profile.repository.create({
|
||||
values: { avatar: 'avatar2' },
|
||||
});
|
||||
|
||||
await UserProfileRepository.set(newProfile.get('id'));
|
||||
|
||||
(await UserProfileRepository.find()).get('id') === newProfile.get('id'); // true
|
||||
```
|
45
docs/tr-TR/api/database/relation-repository/index.md
Normal file
45
docs/tr-TR/api/database/relation-repository/index.md
Normal file
@ -0,0 +1,45 @@
|
||||
# RelationRepository
|
||||
|
||||
`RelationRepository` 是关系类型的 `Repository` 对象,`RelationRepository` 可以实现在不加载关联的情况下对关联数据进行操作。基于 `RelationRepository`,每种关联都派生出对应的实现,分别为
|
||||
|
||||
* [`HasOneRepository`](#has-one-repository)
|
||||
* `HasManyRepository`
|
||||
* `BelongsToRepository`
|
||||
* `BelongsToManyRepository`
|
||||
|
||||
|
||||
## 构造函数
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(sourceCollection: Collection, association: string, sourceKeyValue: string | number)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `sourceCollection` | `Collection` | - | 关联中的参照关系(referencing relation)对应的 Collection |
|
||||
| `association` | `string` | - | 关联名称 |
|
||||
| `sourceKeyValue` | `string \| number` | - | 参照关系中对应的 key 值 |
|
||||
|
||||
|
||||
## 基类属性
|
||||
|
||||
### `db: Database`
|
||||
|
||||
数据库对象
|
||||
|
||||
### `sourceCollection`
|
||||
关联中的参照关系(referencing relation)对应的 Collection
|
||||
|
||||
### `targetCollection`
|
||||
关联中被参照关系(referenced relation)对应的 Collection
|
||||
|
||||
### `association`
|
||||
sequelize 中的与当前关联对应的 association 对象
|
||||
|
||||
### `associationField`
|
||||
collection 中的与当前关联对应的字段
|
||||
|
||||
### `sourceKeyValue`
|
||||
参照关系中对应的 key 值
|
352
docs/tr-TR/api/database/repository.md
Normal file
352
docs/tr-TR/api/database/repository.md
Normal file
@ -0,0 +1,352 @@
|
||||
# Repository
|
||||
|
||||
数据表数据仓库管理类。大部分基于数据表的数据存取等操作均通过该类实现。
|
||||
|
||||
## 构造函数
|
||||
|
||||
通常不会直接由开发者调用,主要通过 `db.registerRepositories()` 注册类型以后,在 `db.colletion()` 的参数中指定对应已注册的仓库类型,并完成实例化。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(collection: Collection)`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
import { Repository } from '@nocobase/database';
|
||||
|
||||
class MyRepository extends Repository {
|
||||
async myQuery(sql) {
|
||||
return this.database.sequelize.query(sql);
|
||||
}
|
||||
}
|
||||
|
||||
db.registerRepositories({
|
||||
books: MyRepository
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'books',
|
||||
// here link to the registered repository
|
||||
repository: 'books'
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
|
||||
const books = db.getRepository('books') as MyRepository;
|
||||
await books.myQuery('SELECT * FROM books;');
|
||||
```
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `database`
|
||||
|
||||
上下文所在的数据库管理实例。
|
||||
|
||||
### `collection`
|
||||
|
||||
对应的数据表管理实例。
|
||||
|
||||
### `model`
|
||||
|
||||
对应的数据模型类。
|
||||
|
||||
## 实例方法
|
||||
|
||||
|
||||
### `find()`
|
||||
|
||||
从数据库查询特定条件的数据集。相当于 Sequelize 中的 `Model.findAll()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async find(options?: FindOptions): Promise<Model[]>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
type Filter = FilterWithOperator | FilterWithValue | FilterAnd | FilterOr;
|
||||
type Appends = string[];
|
||||
type Except = string[];
|
||||
type Fields = string[];
|
||||
type Sort = string[] | string;
|
||||
|
||||
interface SequelizeFindOptions {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
interface FilterByTk {
|
||||
filterByTk?: TargetKey;
|
||||
}
|
||||
|
||||
interface CommonFindOptions extends Transactionable {
|
||||
filter?: Filter;
|
||||
fields?: Fields;
|
||||
appends?: Appends;
|
||||
except?: Except;
|
||||
sort?: Sort;
|
||||
}
|
||||
|
||||
type FindOptions = SequelizeFindOptions & CommonFindOptions & FilterByTk;
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
#### `filter: Filter`
|
||||
查询条件,用于过滤数据结果。传入的查询参数中,`key` 为查询的字段名,`value` 可传要查询的值,
|
||||
也可配合使用操作符进行其他条件的数据筛选。
|
||||
|
||||
```typescript
|
||||
// 查询 name 为 foo,并且 age 大于 18 的记录
|
||||
repository.find({
|
||||
filter: {
|
||||
name: "foo",
|
||||
age: {
|
||||
$gt: 18,
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
更多操作符请参考 [查询操作符](./operators.md)。
|
||||
|
||||
#### `filterByTk: TargetKey`
|
||||
通过 `TargetKey` 查询数据,为 `filter` 参数的便捷方法。`TargetKey` 具体是哪一个字段,
|
||||
可在 `Collection` 中进行[配置](./collection.md#filtertargetkey),默认为 `primaryKey`。
|
||||
|
||||
```typescript
|
||||
|
||||
// 默认情况下,查找 id 为 1 的记录
|
||||
repository.find({
|
||||
filterByTk: 1,
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
#### `fields: string[]`
|
||||
查询列,用户控制数据字段结果。传入此参数之后,只会返回指定的字段。
|
||||
|
||||
#### `except: string[]`
|
||||
排除列,用于控制数据字段结果。传入此参数之后,传入的字段将不会输出。
|
||||
|
||||
#### `appends: string[]`
|
||||
追加列,用于加载关联数据。传入此参数之后,指定的关联字段将一并输出。
|
||||
|
||||
#### `sort: string[] | string`
|
||||
指定查询结果排序方式,传入参数为字段名称,默认按照升序 `asc` 排序,若需按降序 `desc` 排序,
|
||||
可在字段名称前加上 `-` 符号,如:`['-id', 'name']`,表示按 `id desc, name asc` 排序。
|
||||
|
||||
#### `limit: number`
|
||||
限制结果数量,同 `SQL` 中的 `limit`
|
||||
|
||||
#### `offset: number`
|
||||
查询偏移量,同 `SQL` 中的 `offset`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.getRepository('posts');
|
||||
|
||||
const results = await posts.find({
|
||||
filter: {
|
||||
createdAt: {
|
||||
$gt: '2022-01-01T00:00:00.000Z',
|
||||
}
|
||||
},
|
||||
fields: ['title'],
|
||||
appends: ['user'],
|
||||
});
|
||||
```
|
||||
|
||||
### `findOne()`
|
||||
|
||||
从数据库查询特定条件的单条数据。相当于 Sequelize 中的 `Model.findOne()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findOne(options?: FindOneOptions): Promise<Model | null>`
|
||||
|
||||
<embed src="./shared/find-one.md"></embed>
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.getRepository('posts');
|
||||
|
||||
const result = await posts.findOne({
|
||||
filterByTk: 1,
|
||||
});
|
||||
```
|
||||
|
||||
### `count()`
|
||||
|
||||
从数据库查询特定条件的数据总数。相当于 Sequelize 中的 `Model.count()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `count(options?: CountOptions): Promise<number>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
|
||||
filter?: Filter;
|
||||
}
|
||||
```
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const books = db.getRepository('books');
|
||||
|
||||
const count = await books.count({
|
||||
filter: {
|
||||
title: '三字经'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### `findAndCount()`
|
||||
|
||||
从数据库查询特定条件的数据集和结果数。相当于 Sequelize 中的 `Model.findAndCountAll()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async findAndCount(options?: FindAndCountOptions): Promise<[Model[], number]>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
type FindAndCountOptions = Omit<SequelizeAndCountOptions, 'where' | 'include' | 'order'> & CommonFindOptions;
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
查询参数与 `find()` 相同。返回值为一个数组,第一个元素为查询结果,第二个元素为结果总数。
|
||||
|
||||
### `create()`
|
||||
|
||||
向数据表插入一条新创建的数据。相当于 Sequelize 中的 `Model.create()`。当要创建的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async create<M extends Model>(options: CreateOptions): Promise<M>`
|
||||
|
||||
<embed src="./shared/create-options.md"></embed>
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.getRepository('posts');
|
||||
|
||||
const result = await posts.create({
|
||||
values: {
|
||||
title: 'NocoBase 1.0 发布日志',
|
||||
tags: [
|
||||
// 有关系表主键值时为更新该条数据
|
||||
{ id: 1 },
|
||||
// 没有主键值时为创建新数据
|
||||
{ name: 'NocoBase' },
|
||||
]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `createMany()`
|
||||
|
||||
向数据表插入多条新创建的数据。相当于多次调用 `create()` 方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `createMany(options: CreateManyOptions): Promise<Model[]>`
|
||||
|
||||
**类型**
|
||||
```typescript
|
||||
interface CreateManyOptions extends BulkCreateOptions {
|
||||
records: Values[];
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `records`:要创建的记录的数据对象数组。
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.getRepository('posts');
|
||||
|
||||
const results = await posts.createMany({
|
||||
records: [
|
||||
{
|
||||
title: 'NocoBase 1.0 发布日志',
|
||||
tags: [
|
||||
// 有关系表主键值时为更新该条数据
|
||||
{ id: 1 },
|
||||
// 没有主键值时为创建新数据
|
||||
{ name: 'NocoBase' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'NocoBase 1.1 发布日志',
|
||||
tags: [
|
||||
{ id: 1 }
|
||||
]
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### `update()`
|
||||
|
||||
更新数据表中的数据。相当于 Sequelize 中的 `Model.update()`。当要更新的数据对象携带关系字段的信息时,会一并创建或更新相应的关系数据记录。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async update<M extends Model>(options: UpdateOptions): Promise<M>`
|
||||
|
||||
<embed src="./shared/update-options.md"></embed>
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const posts = db.getRepository('posts');
|
||||
|
||||
const result = await posts.update({
|
||||
filterByTk: 1,
|
||||
values: {
|
||||
title: 'NocoBase 1.0 发布日志',
|
||||
tags: [
|
||||
// 有关系表主键值时为更新该条数据
|
||||
{ id: 1 },
|
||||
// 没有主键值时为创建新数据
|
||||
{ name: 'NocoBase' },
|
||||
]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `destory()`
|
||||
|
||||
删除数据表中的数据。相当于 Sequelize 中的 `Model.destroy()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async destory(options?: TargetKey | TargetKey[] | DestoryOptions): Promise<number>`
|
||||
|
||||
**类型**
|
||||
|
||||
```typescript
|
||||
interface DestroyOptions extends SequelizeDestroyOptions {
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey | TargetKey[];
|
||||
truncate?: boolean;
|
||||
context?: any;
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。
|
||||
* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。
|
||||
* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
8
docs/tr-TR/api/database/shared.md
Normal file
8
docs/tr-TR/api/database/shared.md
Normal file
@ -0,0 +1,8 @@
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.values` | `M` | `{}` | 插入的数据对象 |
|
||||
| `options.whitelist?` | `string[]` | - | `values` 字段的白名单,只有名单内的字段会被存储 |
|
||||
| `options.blacklist?` | `string[]` | - | `values` 字段的黑名单,名单内的字段不会被存储 |
|
||||
| `options.transaction?` | `Transaction` | - | 事务 |
|
21
docs/tr-TR/api/database/shared/create-options.md
Normal file
21
docs/tr-TR/api/database/shared/create-options.md
Normal file
@ -0,0 +1,21 @@
|
||||
**类型**
|
||||
```typescript
|
||||
type WhiteList = string[];
|
||||
type BlackList = string[];
|
||||
type AssociationKeysToBeUpdate = string[];
|
||||
|
||||
interface CreateOptions extends SequelizeCreateOptions {
|
||||
values?: Values;
|
||||
whitelist?: WhiteList;
|
||||
blacklist?: BlackList;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
context?: any;
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `values`:要创建的记录的数据对象。
|
||||
* `whitelist`:指定要创建的记录的数据对象中,哪些字段**可以被写入**。若不传入此参数,则默认允许所有字段写入。
|
||||
* `blacklist`:指定要创建的记录的数据对象中,哪些字段**不允许被写入**。若不传入此参数,则默认允许所有字段写入。
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
17
docs/tr-TR/api/database/shared/destroy-options.md
Normal file
17
docs/tr-TR/api/database/shared/destroy-options.md
Normal file
@ -0,0 +1,17 @@
|
||||
**类型**
|
||||
|
||||
```typescript
|
||||
interface DestroyOptions extends SequelizeDestroyOptions {
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey | TargetKey[];
|
||||
truncate?: boolean;
|
||||
context?: any;
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
* `filter`:指定要删除的记录的过滤条件。Filter 详细用法可参考 [`find()`](#find) 方法。
|
||||
* `filterByTk`:按 TargetKey 指定要删除的记录的过滤条件。
|
||||
* `truncate`: 是否清空表数据,在没有传入 `filter` 或 `filterByTk` 参数时有效。
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
8
docs/tr-TR/api/database/shared/find-one.md
Normal file
8
docs/tr-TR/api/database/shared/find-one.md
Normal file
@ -0,0 +1,8 @@
|
||||
**类型**
|
||||
```typescript
|
||||
type FindOneOptions = Omit<FindOptions, 'limit'>;
|
||||
```
|
||||
|
||||
**参数**
|
||||
|
||||
大部分参数与 `find()` 相同,不同之处在于 `findOne()` 只返回单条数据,所以不需要 `limit` 参数,且查询时始终为 `1`。
|
0
docs/tr-TR/api/database/shared/find-options.md
Normal file
0
docs/tr-TR/api/database/shared/find-options.md
Normal file
1
docs/tr-TR/api/database/shared/transaction.md
Normal file
1
docs/tr-TR/api/database/shared/transaction.md
Normal file
@ -0,0 +1 @@
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
24
docs/tr-TR/api/database/shared/update-options.md
Normal file
24
docs/tr-TR/api/database/shared/update-options.md
Normal file
@ -0,0 +1,24 @@
|
||||
**类型**
|
||||
```typescript
|
||||
interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
|
||||
values: Values;
|
||||
filter?: Filter;
|
||||
filterByTk?: TargetKey;
|
||||
whitelist?: WhiteList;
|
||||
blacklist?: BlackList;
|
||||
updateAssociationValues?: AssociationKeysToBeUpdate;
|
||||
context?: any;
|
||||
}
|
||||
```
|
||||
|
||||
**详细信息**
|
||||
|
||||
|
||||
* `values`:要更新的记录的数据对象。
|
||||
* `filter`:指定要更新的记录的过滤条件, Filter 详细用法可参考 [`find()`](#find) 方法。
|
||||
* `filterByTk`:按 TargetKey 指定要更新的记录的过滤条件。
|
||||
* `whitelist`: `values` 字段的白名单,只有名单内的字段会被写入。
|
||||
* `blacklist`: `values` 字段的黑名单,名单内的字段不会被写入。
|
||||
* `transaction`: 事务对象。如果没有传入事务参数,该方法会自动创建一个内部事务。
|
||||
|
||||
`filterByTk` 与 `filter` 至少要传其一。
|
225
docs/tr-TR/api/env.md
Normal file
225
docs/tr-TR/api/env.md
Normal file
@ -0,0 +1,225 @@
|
||||
# 环境变量
|
||||
|
||||
## 全局环境变量
|
||||
|
||||
保存在 `.env` 文件里
|
||||
|
||||
### APP_ENV
|
||||
|
||||
应用环境,默认值 `development`,可选项包括:
|
||||
|
||||
- `production` 生产环境
|
||||
- `development` 开发环境
|
||||
|
||||
```bash
|
||||
APP_ENV=production
|
||||
```
|
||||
|
||||
### APP_HOST
|
||||
|
||||
应用主机,默认值 `0.0.0.0`
|
||||
|
||||
```bash
|
||||
APP_HOST=192.168.3.154
|
||||
```
|
||||
|
||||
### APP_PORT
|
||||
|
||||
应用端口,默认值 `13000`
|
||||
|
||||
```bash
|
||||
APP_PORT=13000
|
||||
```
|
||||
|
||||
### APP_KEY
|
||||
|
||||
秘钥,用于 jwt 等场景
|
||||
|
||||
```bash
|
||||
APP_KEY=app-key-test
|
||||
```
|
||||
|
||||
### API_BASE_PATH
|
||||
|
||||
NocoBase API 地址前缀,默认值 `/api/`
|
||||
|
||||
```bash
|
||||
API_BASE_PATH=/api/
|
||||
```
|
||||
|
||||
### PLUGIN_PACKAGE_PREFIX
|
||||
|
||||
插件包前缀,默认值 `@nocobase/plugin-,@nocobase/preset-`
|
||||
|
||||
例如,有一名为 `my-nocobase-app` 的项目,新增了 `hello` 插件,包名为 `@my-nocobase-app/plugin-hello`。
|
||||
|
||||
PLUGIN_PACKAGE_PREFIX 配置如下:
|
||||
|
||||
```bash
|
||||
PLUGIN_PACKAGE_PREFIX=@nocobase/plugin-,@nocobase/preset-,@my-nocobase-app/plugin-
|
||||
```
|
||||
|
||||
插件名和包名的对应关系为:
|
||||
|
||||
- `users` 插件包名为 `@nocobase/plugin-users`
|
||||
- `nocobase` 插件包名为 `@nocobase/preset-nocobase`
|
||||
- `hello` 插件包名为 `@my-nocobase-app/plugin-hello`
|
||||
|
||||
### DB_DIALECT
|
||||
|
||||
数据库类型,默认值 `sqlite`,可选项包括:
|
||||
|
||||
- `sqlite`
|
||||
- `mysql`
|
||||
- `postgres`
|
||||
|
||||
```bash
|
||||
DB_DIALECT=mysql
|
||||
```
|
||||
|
||||
### DB_STORAGE
|
||||
|
||||
数据库文件路径(使用 SQLite 数据库时配置)
|
||||
|
||||
```bash
|
||||
# 相对路径
|
||||
DB_HOST=storage/db/nocobase.db
|
||||
# 绝对路径
|
||||
DB_HOST=/your/path/nocobase.db
|
||||
```
|
||||
|
||||
### DB_HOST
|
||||
|
||||
数据库主机(使用 mysql 或 postgres 数据库时需要配置)
|
||||
|
||||
默认值 `localhost`
|
||||
|
||||
```bash
|
||||
DB_HOST=localhost
|
||||
```
|
||||
|
||||
### DB_PORT
|
||||
|
||||
数据库端口(使用 mysql 或 postgres 数据库时需要配置)
|
||||
|
||||
- MySQL 默认端口 3306
|
||||
- PostgreSQL 默认端口 5432
|
||||
|
||||
```bash
|
||||
DB_PORT=3306
|
||||
```
|
||||
|
||||
### DB_DATABASE
|
||||
|
||||
数据库名(使用 mysql 或 postgres 数据库时需要配置)
|
||||
|
||||
```bash
|
||||
DB_DATABASE=nocobase
|
||||
```
|
||||
|
||||
### DB_USER
|
||||
|
||||
数据库用户(使用 mysql 或 postgres 数据库时需要配置)
|
||||
|
||||
```bash
|
||||
DB_USER=nocobase
|
||||
```
|
||||
|
||||
### DB_PASSWORD
|
||||
|
||||
数据库密码(使用 mysql 或 postgres 数据库时需要配置)
|
||||
|
||||
```bash
|
||||
DB_PASSWORD=nocobase
|
||||
```
|
||||
|
||||
### DB_TABLE_PREFIX
|
||||
|
||||
数据表前缀
|
||||
|
||||
```bash
|
||||
DB_TABLE_PREFIX=nocobase_
|
||||
```
|
||||
|
||||
### DB_LOGGING
|
||||
|
||||
数据库日志开关,默认值 `off`,可选项包括:
|
||||
|
||||
- `on` 打开
|
||||
- `off` 关闭
|
||||
|
||||
```bash
|
||||
DB_LOGGING=on
|
||||
```
|
||||
|
||||
## 临时环境变量
|
||||
|
||||
安装 NocoBase 时,可以通过设置临时的环境变量来辅助安装,如:
|
||||
|
||||
```bash
|
||||
yarn cross-env \
|
||||
INIT_APP_LANG=zh-CN \
|
||||
INIT_ROOT_EMAIL=demo@nocobase.com \
|
||||
INIT_ROOT_PASSWORD=admin123 \
|
||||
INIT_ROOT_NICKNAME="Super Admin" \
|
||||
nocobase install
|
||||
|
||||
# 等同于
|
||||
yarn nocobase install \
|
||||
--lang=zh-CN \
|
||||
--root-email=demo@nocobase.com \
|
||||
--root-password=admin123 \
|
||||
--root-nickname="Super Admin"
|
||||
|
||||
# 等同于
|
||||
yarn nocobase install -l zh-CN -e demo@nocobase.com -p admin123 -n "Super Admin"
|
||||
```
|
||||
|
||||
### INIT_APP_LANG
|
||||
|
||||
安装时的语言,默认值 `en-US`,可选项包括:
|
||||
|
||||
- `en-US`
|
||||
- `zh-CN`
|
||||
|
||||
```bash
|
||||
yarn cross-env \
|
||||
INIT_APP_LANG=zh-CN \
|
||||
nocobase install
|
||||
```
|
||||
|
||||
### INIT_ROOT_EMAIL
|
||||
|
||||
Root 用户邮箱
|
||||
|
||||
```bash
|
||||
yarn cross-env \
|
||||
INIT_APP_LANG=zh-CN \
|
||||
INIT_ROOT_EMAIL=demo@nocobase.com \
|
||||
nocobase install
|
||||
```
|
||||
|
||||
### INIT_ROOT_PASSWORD
|
||||
|
||||
Root 用户密码
|
||||
|
||||
```bash
|
||||
yarn cross-env \
|
||||
INIT_APP_LANG=zh-CN \
|
||||
INIT_ROOT_EMAIL=demo@nocobase.com \
|
||||
INIT_ROOT_PASSWORD=admin123 \
|
||||
nocobase install
|
||||
```
|
||||
|
||||
### INIT_ROOT_NICKNAME
|
||||
|
||||
Root 用户昵称
|
||||
|
||||
```bash
|
||||
yarn cross-env \
|
||||
INIT_APP_LANG=zh-CN \
|
||||
INIT_ROOT_EMAIL=demo@nocobase.com \
|
||||
INIT_ROOT_PASSWORD=admin123 \
|
||||
INIT_ROOT_NICKNAME="Super Admin" \
|
||||
nocobase install
|
||||
```
|
142
docs/tr-TR/api/http/action-api.md
Normal file
142
docs/tr-TR/api/http/action-api.md
Normal file
@ -0,0 +1,142 @@
|
||||
# Action API
|
||||
|
||||
## Common
|
||||
|
||||
---
|
||||
|
||||
Collection and Association resources are common.
|
||||
|
||||
### `create`
|
||||
|
||||
```bash
|
||||
POST /api/users:create?whitelist=a,b&blacklist=c,d
|
||||
|
||||
{} # Request Body
|
||||
```
|
||||
|
||||
- Parameters
|
||||
- whitelist White list
|
||||
- blacklist Black list
|
||||
- Request body: JSON data to be inserted
|
||||
- Response body data: Created data JSON
|
||||
|
||||
#### Add a User
|
||||
|
||||
```bash
|
||||
POST /api/users:create
|
||||
|
||||
Request Body
|
||||
{
|
||||
"email": "demo@nocobase.com",
|
||||
"name": "Admin"
|
||||
}
|
||||
|
||||
Response 200 (application/json)
|
||||
{
|
||||
"data": {},
|
||||
}
|
||||
```
|
||||
|
||||
#### Add a user's article
|
||||
|
||||
```bash
|
||||
POST /api/users/1/posts:create
|
||||
|
||||
Request Body
|
||||
{
|
||||
"title": "My first post"
|
||||
}
|
||||
|
||||
Response 200 (application/json)
|
||||
{
|
||||
"data": {},
|
||||
}
|
||||
```
|
||||
|
||||
#### Association in Request Body
|
||||
|
||||
```bash
|
||||
POST /api/posts:create
|
||||
|
||||
Request Body
|
||||
{
|
||||
"title": "My first post",
|
||||
"user": 1
|
||||
}
|
||||
|
||||
Response 200 (application/json)
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"title": "My first post",
|
||||
"userId": 1,
|
||||
"user": {
|
||||
"id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `update`
|
||||
|
||||
```bash
|
||||
POST /api/users:create?filterByTk=1&whitelist=a,b&blacklist=c,d
|
||||
|
||||
{} # Request Body
|
||||
```
|
||||
|
||||
- Parameters
|
||||
- whitelist White list
|
||||
- blacklist Black list
|
||||
- filterByTk Filter by tk field, by default tk is the primary key of the data table
|
||||
- filter Filter,support json string
|
||||
- Request body: JSON data to be updated
|
||||
|
||||
#### Association in Request Body
|
||||
|
||||
```bash
|
||||
POST /api/posts:update/1
|
||||
|
||||
Request Body
|
||||
{
|
||||
"title": "My first post 2",
|
||||
"user": 2
|
||||
}
|
||||
|
||||
Response 200 (application/json)
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "My first post 2",
|
||||
"userId": 2,
|
||||
"user": {
|
||||
"id": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `list`
|
||||
|
||||
### `get`
|
||||
|
||||
### `destroy`
|
||||
|
||||
### `move`
|
||||
|
||||
## Association
|
||||
|
||||
---
|
||||
|
||||
### `add`
|
||||
|
||||
### `set`
|
||||
|
||||
### `remove`
|
||||
|
||||
### `toggle`
|
||||
|
||||
|
||||
|
59
docs/tr-TR/api/http/filter-operators.md
Normal file
59
docs/tr-TR/api/http/filter-operators.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Filter operators
|
||||
|
||||
## Common
|
||||
|
||||
- $eq
|
||||
- $ne
|
||||
- $gte
|
||||
- $gt
|
||||
- $lte
|
||||
- $lt
|
||||
- $not
|
||||
- $is
|
||||
- $in
|
||||
- $notIn
|
||||
- $like
|
||||
- $notLike
|
||||
- $iLike
|
||||
- $notILike
|
||||
- $and
|
||||
- $or
|
||||
- $empty
|
||||
- $notEmpty
|
||||
|
||||
## array
|
||||
|
||||
- $match
|
||||
- $notMatch
|
||||
- $anyOf
|
||||
- $noneOf
|
||||
- $arrayEmpty
|
||||
- $arrayNotEmpty
|
||||
|
||||
## association
|
||||
|
||||
- $exists
|
||||
- $notExists
|
||||
|
||||
## boolean
|
||||
|
||||
- $isTruthy
|
||||
- $isFalsy
|
||||
|
||||
## date
|
||||
|
||||
- $dateOn
|
||||
- $dateNotOn
|
||||
- $dateBefore
|
||||
- $dateNotBefore
|
||||
- $dateAfter
|
||||
- $dateNotAfter
|
||||
|
||||
## string
|
||||
|
||||
- $includes
|
||||
- $notIncludes
|
||||
- $startsWith
|
||||
- $notStartsWith
|
||||
- $endWith
|
||||
- $notEndWith
|
301
docs/tr-TR/api/http/index.md
Normal file
301
docs/tr-TR/api/http/index.md
Normal file
@ -0,0 +1,301 @@
|
||||
# Overview
|
||||
|
||||
NocoBase HTTP API is designed based on Resource & Action, it is a superset of REST API. The operation is not limited to add, delete, change, and check, Resource Action can be extended arbitrarily in NocoBase.
|
||||
|
||||
## Resource
|
||||
|
||||
Resource has two expressions in NocoBase.
|
||||
|
||||
- `<collection>`
|
||||
- `<collection>.<association>`
|
||||
|
||||
<Alert>
|
||||
|
||||
- collection is the set of all abstract data
|
||||
- association is the association data for the collection
|
||||
- resource includes both collection and collection.association
|
||||
|
||||
</Alert>
|
||||
|
||||
### Example
|
||||
|
||||
- `posts` Post
|
||||
- `posts.user` Post user
|
||||
- `posts.tags` Post tags
|
||||
|
||||
## Action
|
||||
|
||||
Representing resource operations as `:<action>`
|
||||
|
||||
- `<collection>:<action>`
|
||||
- `<collection>.<association>:<action>`
|
||||
|
||||
Built-in global operations for collection or association
|
||||
|
||||
- `create`
|
||||
- `get`
|
||||
- `list`
|
||||
- `update`
|
||||
- `destroy`
|
||||
- `move`
|
||||
|
||||
Built-in association operation for association only
|
||||
|
||||
- `set`
|
||||
- `add`
|
||||
- `remove`
|
||||
- `toggle`
|
||||
|
||||
### Example
|
||||
|
||||
- `posts:create` Create posts
|
||||
- `posts.user:get` View posts user
|
||||
- `posts.tags:add` Attach post tags (associate existing tags with post)
|
||||
|
||||
## Request URL
|
||||
|
||||
```bash
|
||||
<GET|POST> /api/<collection>:<action>
|
||||
<GET|POST> /api/<collection>:<action>/<collectionIndex>
|
||||
<GET|POST> /api/<collection>/<collectionIndex>/<association>:<action>
|
||||
<GET|POST> /api/<collection>/<collectionIndex>/<association>:<action>/<associationIndex>
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
posts resource
|
||||
|
||||
```bash
|
||||
POST /api/posts:create
|
||||
GET /api/posts:list
|
||||
GET /api/posts:get/1
|
||||
POST /api/posts:update/1
|
||||
POST /api/posts:destroy/1
|
||||
```
|
||||
|
||||
posts.comments resource
|
||||
|
||||
```bash
|
||||
POST /api/posts/1/comments:create
|
||||
GET /api/posts/1/comments:list
|
||||
GET /api/posts/1/comments:get/1
|
||||
POST /api/posts/1/comments:update/1
|
||||
POST /api/posts/1/comments:destroy/1
|
||||
```
|
||||
|
||||
posts.tags resource
|
||||
|
||||
```bash
|
||||
POST /api/posts/1/tags:create
|
||||
GET /api/posts/1/tags:get
|
||||
GET /api/posts/1/tags:list
|
||||
POST /api/posts/1/tags:update
|
||||
POST /api/posts/1/tags:destroy
|
||||
POST /api/posts/1/tags:add
|
||||
GET /api/posts/1/tags:remove
|
||||
```
|
||||
|
||||
## Resource location
|
||||
|
||||
- collection resource, locates the data to be processed by `collectionIndex`, `collectionIndex` must be unique
|
||||
- association resource, locates the data to be processed by `collectionIndex` and `associationIndex` jointly, `associationIndex` may not be unique, but `collectionIndex` and `associationIndex`'s association indexes must be unique
|
||||
|
||||
When viewing association resource details, the requested URL needs to provide both `<collectionIndex>` and `<associationIndex>`, `<collectionIndex>` is not redundant because `<associationIndex>` may not be unique.
|
||||
|
||||
For example, `tables.fields` indicates the fields of a data table
|
||||
|
||||
```bash
|
||||
GET /api/tables/table1/fields/title
|
||||
GET /api/tables/table2/fields/title
|
||||
```
|
||||
|
||||
Both table1 and table2 have a title field. The title is unique in table1, but other tables may also have a title field
|
||||
|
||||
## Request parameters
|
||||
|
||||
Request parameters can be placed in the request's headers, parameters (query string), and body (GET requests do not have a body).
|
||||
|
||||
A few special request parameters
|
||||
|
||||
- `filter` Data filtering, used in query-related operations.
|
||||
- `filterByTk` filter by tk field, used in operations that specify details of the data.
|
||||
- `sort` Sorting, used in query-related operations.
|
||||
- `fields` which data to output, for use in query-related operations.
|
||||
- `appends` additional relationship fields for use in query-related operations.
|
||||
- `except` which fields to exclude (no output), used in query-related operations.
|
||||
- `whitelist` fields whitelist, used in data creation and update related operations.
|
||||
- `blacklist` fields blacklist, used in data creation and update related operations.
|
||||
|
||||
### filter
|
||||
|
||||
Data filter
|
||||
|
||||
```bash
|
||||
# simple
|
||||
GET /api/posts?filter[status]=publish
|
||||
# Recommend using the json string format, which requires encodeURIComponent encoding
|
||||
GET /api/posts?filter={"status":"published"}
|
||||
|
||||
# filter operators
|
||||
GET /api/posts?filter[status.$eq]=publish
|
||||
GET /api/posts?filter={"status.$eq":"published"}
|
||||
|
||||
# $and
|
||||
GET /api/posts?filter={"$and": [{"status.$eq":"published"}, {"title.$includes":"a"}]}
|
||||
# $or
|
||||
GET /api/posts?filter={"$or": [{"status.$eq":"pending"}, {"status.$eq":"draft"}]}
|
||||
|
||||
# association field
|
||||
GET /api/posts?filter[user.email.$includes]=gmail
|
||||
GET /api/posts?filter={"user.email.$includes":"gmail"}
|
||||
```
|
||||
|
||||
[Click here for more information about filter operators](http-api/filter-operators)
|
||||
|
||||
### filterByTk
|
||||
|
||||
Filter by tk field. By default
|
||||
|
||||
- collection resource, tk is the primary key of the data table.
|
||||
- association resource, tk is the targetKey field of the association.
|
||||
|
||||
```bash
|
||||
GET /api/posts:get?filterByTk=1&fields=name,title&appends=tags
|
||||
```
|
||||
|
||||
### sort
|
||||
|
||||
Sorting. When sorting in descending order, the fields are preceded by the minus sign `-`.
|
||||
|
||||
```bash
|
||||
# createAt field in ascending order
|
||||
GET /api/posts:get?sort=createdAt
|
||||
# createAt field descending
|
||||
GET /api/posts:get?sort=-createdAt
|
||||
# Multiple fields sorted jointly, createAt field descending, title A-Z ascending
|
||||
GET /api/posts:get?sort=-createdAt,title
|
||||
```
|
||||
|
||||
### fields
|
||||
|
||||
Which fields to output
|
||||
|
||||
```bash
|
||||
GET /api/posts:list?fields=name,title
|
||||
|
||||
Response 200 (application/json)
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"name": "",
|
||||
"title": ""
|
||||
}
|
||||
],
|
||||
"meta": {}
|
||||
}
|
||||
```
|
||||
|
||||
### appends
|
||||
|
||||
Appends a relationship field
|
||||
|
||||
### except
|
||||
|
||||
Which fields to exclude (not output) for use in query-related operations.
|
||||
|
||||
### whitelist
|
||||
|
||||
Whitelist
|
||||
|
||||
```bash
|
||||
POST /api/posts:create?whitelist=title
|
||||
|
||||
{
|
||||
"title": "My first post",
|
||||
"date": "2022-05-19" # The date field will be filtered out and will not be written to the database
|
||||
}
|
||||
```
|
||||
|
||||
### blacklist
|
||||
|
||||
Blacklist
|
||||
|
||||
```bash
|
||||
POST /api/posts:create?blacklist=date
|
||||
|
||||
{
|
||||
"title": "My first post",
|
||||
"date": "2022-05-19" # The date field will be filtered out and will not be written to the database
|
||||
}
|
||||
```
|
||||
|
||||
## Request Response
|
||||
|
||||
Format of the response
|
||||
|
||||
```ts
|
||||
type ResponseResult = {
|
||||
data?: any; // Master data
|
||||
meta?: any; // Additional Data
|
||||
errors?: ResponseError[]; // Errors
|
||||
};
|
||||
|
||||
type ResponseError = {
|
||||
code?: string;
|
||||
message: string;
|
||||
};
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
View list
|
||||
|
||||
```bash
|
||||
GET /api/posts:list
|
||||
|
||||
Response 200 (application/json)
|
||||
|
||||
{
|
||||
data: [
|
||||
{
|
||||
id: 1
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
count: 1
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
totalPage: 1
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
View details
|
||||
|
||||
```bash
|
||||
GET /api/posts:get/1
|
||||
|
||||
Response 200 (application/json)
|
||||
|
||||
{
|
||||
data: {
|
||||
id: 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Error
|
||||
|
||||
```bash
|
||||
POST /api/posts:create
|
||||
|
||||
Response 400 (application/json)
|
||||
|
||||
{
|
||||
errors: [
|
||||
{
|
||||
message: 'name must be required',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
281
docs/tr-TR/api/http/javascript-sdk.md
Normal file
281
docs/tr-TR/api/http/javascript-sdk.md
Normal file
@ -0,0 +1,281 @@
|
||||
# JavaScript SDK
|
||||
|
||||
## APIClient
|
||||
|
||||
```ts
|
||||
class APIClient {
|
||||
// axios instance
|
||||
axios: AxiosInstance;
|
||||
// constructors
|
||||
constructor(instance?: AxiosInstance | AxiosRequestConfig);
|
||||
// Client-side requests, support for AxiosRequestConfig and ResourceActionOptions
|
||||
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>;
|
||||
// Get Resources
|
||||
resource<R = IResource>(name: string, of?: any): R;
|
||||
}
|
||||
```
|
||||
|
||||
Initialize instance
|
||||
|
||||
```ts
|
||||
import axios from 'axios';
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
|
||||
// Provide AxiosRequestConfig configuration parameters
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
|
||||
// Provide AxiosInstance
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
const api = new APIClient(instance);
|
||||
```
|
||||
|
||||
## Mock
|
||||
|
||||
```ts
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
|
||||
const mock = new MockAdapter(api.axios);
|
||||
|
||||
mock.onGet('users:get').reply(200, {
|
||||
data: { id: 1, name: 'John Smith' },
|
||||
});
|
||||
|
||||
await api.request({ url: 'users:get' });
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
APIClient uses localStorage by default, you can also custom storage.
|
||||
|
||||
```ts
|
||||
import { Storage } from '@nocobase/sdk';
|
||||
|
||||
class MemoryStorage extends Storage {
|
||||
items = new Map();
|
||||
|
||||
clear() {
|
||||
this.items.clear();
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.items.get(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
return this.items.set(key, value);
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
return this.items.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
storageClass: CustomStorage,
|
||||
});
|
||||
```
|
||||
|
||||
## Auth
|
||||
|
||||
```ts
|
||||
// sign in and remember the current token
|
||||
api.auth.signIn({ email, password });
|
||||
// sign out and delete the token
|
||||
api.auth.signOut();
|
||||
// set the token
|
||||
api.auth.setToken('123');
|
||||
// set the role (multiple roles)
|
||||
api.auth.setRole('admin');
|
||||
// set the locale (multiple languages)
|
||||
api.auth.setLocale('zh-CN');
|
||||
```
|
||||
|
||||
Custom Auth
|
||||
|
||||
```ts
|
||||
import { Auth } from '@nocobase/sdk';
|
||||
|
||||
class CustomAuth extends Auth {
|
||||
|
||||
}
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
authClass: CustomAuth,
|
||||
});
|
||||
```
|
||||
|
||||
## Request
|
||||
|
||||
```ts
|
||||
// url
|
||||
await api.request({
|
||||
url: 'users:list',
|
||||
// request params
|
||||
params: {
|
||||
filter: {
|
||||
'email.$includes': 'noco',
|
||||
},
|
||||
},
|
||||
// request body
|
||||
data,
|
||||
});
|
||||
|
||||
// resource & action
|
||||
await api.request({
|
||||
resource: 'users',
|
||||
action: 'list',
|
||||
// action params
|
||||
params: {
|
||||
filter: {
|
||||
'email.$includes': 'noco',
|
||||
},
|
||||
page: 1,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Resource action
|
||||
|
||||
```ts
|
||||
await api.resource('collection')[action]();
|
||||
await api.resource('collection.association', collectionId)[action]();
|
||||
```
|
||||
|
||||
## Action API
|
||||
|
||||
```ts
|
||||
await api.resource('collection').create();
|
||||
await api.resource('collection').get();
|
||||
await api.resource('collection').list();
|
||||
await api.resource('collection').update();
|
||||
await api.resource('collection').destroy();
|
||||
await api.resource('collection.association', collectionId).create();
|
||||
await api.resource('collection.association', collectionId).get();
|
||||
await api.resource('collection.association', collectionId).list();
|
||||
await api.resource('collection.association', collectionId).update();
|
||||
await api.resource('collection.association', collectionId).destroy();
|
||||
```
|
||||
|
||||
### `get`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
get: (options?: GetActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface GetActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
fields?: string || string[];
|
||||
appends?: string || string[];
|
||||
expect?: string || string[];
|
||||
sort?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
### `list`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
list: (options?: ListActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface ListActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
fields?: string || string[];
|
||||
appends?: string || string[];
|
||||
expect?: string || string[];
|
||||
sort?: string[];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
paginate?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `create`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
create: (options?: CreateActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface CreateActionOptions {
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
values?: {[key: sting]: any};
|
||||
}
|
||||
```
|
||||
|
||||
### `update`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
update: (options?: UpdateActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface UpdateActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
values?: {[key: sting]: any};
|
||||
}
|
||||
```
|
||||
|
||||
### `destroy`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
destroy: (options?: DestroyActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface DestroyActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### `move`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
move: (options?: MoveActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface MoveActionOptions {
|
||||
sourceId: any;
|
||||
targetId?: any;
|
||||
/** @default 'sort' */
|
||||
sortField?: any;
|
||||
targetScope?: {[key: string]: any};
|
||||
sticky?: boolean;
|
||||
method?: 'insertAfter' | 'prepend';
|
||||
}
|
||||
```
|
||||
|
||||
### `<custom>`
|
||||
|
||||
```ts
|
||||
interface AttachmentResource {
|
||||
|
||||
}
|
||||
|
||||
interface UploadActionOptions {
|
||||
|
||||
}
|
||||
|
||||
api.resource<AttachmentResource>('attachments').upload();
|
||||
api.resource('attachments').upload<UploadActionOptions>();
|
||||
```
|
184
docs/tr-TR/api/http/rest-api.md
Normal file
184
docs/tr-TR/api/http/rest-api.md
Normal file
@ -0,0 +1,184 @@
|
||||
# REST API
|
||||
|
||||
NocoBase's HTTP API is a superset of the REST API, and the standard CRUD API also supports the RESTful style.
|
||||
|
||||
## Collection resources
|
||||
|
||||
---
|
||||
|
||||
### Create collection
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>:create
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
### List collection
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>:list
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>
|
||||
```
|
||||
|
||||
### View collection details
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>:get?filterByTk=<collectionIndex>
|
||||
GET /api/<collection>:get/<collectionIndex>
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>/<collectionIndex>
|
||||
```
|
||||
|
||||
### Update collection
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>:update?filterByTk=<collectionIndex>
|
||||
|
||||
{} # JSON body
|
||||
|
||||
# Or
|
||||
POST /api/<collection>:update/<collectionIndex>
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
PUT /api/<collection>/<collectionIndex>
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
### Delete collection
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>:destroy?filterByTk=<collectionIndex>
|
||||
# Or
|
||||
POST /api/<collection>:destroy/<collectionIndex>
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
DELETE /api/<collection>/<collectionIndex>
|
||||
```
|
||||
|
||||
## Association resources
|
||||
|
||||
---
|
||||
|
||||
### Create Association
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>/<collectionIndex>/<association>:create
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>/<collectionIndex>/<association>
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
### List Association
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>/<collectionIndex>/<association>:list
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>/<collectionIndex>/<association>
|
||||
```
|
||||
|
||||
### View Association details
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>/<collectionIndex>/<association>:get?filterByTk=<associationIndex>
|
||||
# Or
|
||||
GET /api/<collection>/<collectionIndex>/<association>:get/<associationIndex>
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
GET /api/<collection>/<collectionIndex>/<association>:get/<associationIndex>
|
||||
```
|
||||
|
||||
### Update Association
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>/<collectionIndex>/<association>:update?filterByTk=<associationIndex>
|
||||
|
||||
{} # JSON body
|
||||
|
||||
# Or
|
||||
POST /api/<collection>/<collectionIndex>/<association>:update/<associationIndex>
|
||||
|
||||
{} # JSON body
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
PUT /api/<collection>/<collectionIndex>/<association>:update/<associationIndex>
|
||||
|
||||
{} # JSON
|
||||
```
|
||||
|
||||
### Delete Association
|
||||
|
||||
HTTP API
|
||||
|
||||
```bash
|
||||
POST /api/<collection>/<collectionIndex>/<association>:destroy?filterByTk=<associationIndex>
|
||||
# Or
|
||||
POST /api/<collection>/<collectionIndex>/<association>:destroy/<associationIndex>
|
||||
```
|
||||
|
||||
REST API
|
||||
|
||||
```bash
|
||||
DELETE /api/<collection>/<collectionIndex>/<association>/<associationIndex>
|
||||
```
|
12
docs/tr-TR/api/index.md
Normal file
12
docs/tr-TR/api/index.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 概览
|
||||
|
||||
| 模块 | 包名 | 描述 |
|
||||
| --------------------------------- | --------------------- | ------------------- |
|
||||
| [Server](/api/server) | `@nocobase/server` | 服务端应用 |
|
||||
| [Database](/api/database) | `@nocobase/database` | 数据库访问层 |
|
||||
| [Resourcer](/api/resourcer) | `@nocobase/resourcer` | 资源与路由映射 |
|
||||
| [ACL](/api/acl) | `@nocobase/acl` | 访问控制表 |
|
||||
| [Client](/api/client/application) | `@nocobase/client` | 客户端应用 |
|
||||
| [CLI](/api/cli) | `@nocobase/cli` | NocoBase 命令行工具 |
|
||||
| [SDK](/api/sdk) | `@nocobase/sdk` | NocoBase SDK |
|
||||
| [Actions](/api/actions) | `@nocobase/actions` | 内置常用资源操作 |
|
148
docs/tr-TR/api/resourcer/action.md
Normal file
148
docs/tr-TR/api/resourcer/action.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Action
|
||||
|
||||
Action 是对资源的操作过程的描述,通常包含数据库处理等,类似其他框架中的 service 层,最简化的实现可以是一个 Koa 的中间件函数。在资源管理器里,针对特定资源定义的普通操作函数会被包装成 Action 类型的实例,当请求匹配对应资源的操作时,执行对应的操作过程。
|
||||
|
||||
## 构造函数
|
||||
|
||||
通常不需要直接实例化 Action,而是由资源管理器自动调用 `Action` 的静态方法 `toInstanceMap()` 进行实例化。
|
||||
|
||||
### `constructor(options: ActionOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `handler` | `Function` | - | 操作函数 |
|
||||
| `middlewares?` | `Middleware \| Middleware[]` | - | 针对操作的中间件 |
|
||||
| `values?` | `Object` | - | 默认的操作数据 |
|
||||
| `fields?` | `string[]` | - | 默认针对的字段组 |
|
||||
| `appends?` | `string[]` | - | 默认附加的关联字段组 |
|
||||
| `except?` | `string[]` | - | 默认排除的字段组 |
|
||||
| `filter` | `FilterOptions` | - | 默认的过滤参数 |
|
||||
| `sort` | `string[]` | - | 默认的排序参数 |
|
||||
| `page` | `number` | - | 默认的页码 |
|
||||
| `pageSize` | `number` | - | 默认的每页数量 |
|
||||
| `maxPageSize` | `number` | - | 默认最大每页数量 |
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `actionName`
|
||||
|
||||
被实例化后对应的操作名称。在实例化时从请求中解析获取。
|
||||
|
||||
### `resourceName`
|
||||
|
||||
被实例化后对应的资源名称。在实例化时从请求中解析获取。
|
||||
|
||||
### `resourceOf`
|
||||
|
||||
被实例化后对应的关系资源的主键值。在实例化时从请求中解析获取。
|
||||
|
||||
### `readonly middlewares`
|
||||
|
||||
针对操作的中间件列表。
|
||||
|
||||
### `params`
|
||||
|
||||
操作参数。包含对应操作的所有相关参数,实例化时根据定义的 action 参数初始化,之后请求中解析前端传入的参数并根据对应参数的合并策略合并。如果有其他中间件的处理,也会有类似的合并过程。直到 handler 处理时,访问 params 得到的是经过多次合并的最终参数。
|
||||
|
||||
参数的合并过程提供了针对操作处理的可扩展性,可以通过自定义中间件的方式按业务需求进行参数的前置解析和处理,例如表单提交的参数验证就可以在此环节实现。
|
||||
|
||||
预设的参数可以参考 [/api/actions] 中不同操作的参数。
|
||||
|
||||
参数中还包含请求资源路由的描述部分,具体如下:
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `resourceName` | `string` | - | 资源名称 |
|
||||
| `resourceIndex` | `string \| number` | - | 资源的主键值 |
|
||||
| `associatedName` | `string` | - | 所属关系资源的名称 |
|
||||
| `associatedIndex` | `string \| number` | - | 所属关系资源的主键值 |
|
||||
| `associated` | `Object` | - | 所属关系资源的实例 |
|
||||
| `actionName` | `string` | - | 操作名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.define('books', {
|
||||
actions: {
|
||||
publish(ctx, next) {
|
||||
ctx.body = ctx.action.params.values;
|
||||
// {
|
||||
// id: 1234567890
|
||||
// publishedAt: '2019-01-01',
|
||||
// }
|
||||
}
|
||||
},
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
ctx.action.mergeParams({
|
||||
values: {
|
||||
id: Math.random().toString(36).substr(2, 10),
|
||||
publishedAt: new Date(),
|
||||
}
|
||||
});
|
||||
await next();
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `mergeParams()`
|
||||
|
||||
将额外的参数合并至当前参数集,且可以根据不同的策略进行合并。
|
||||
|
||||
**签名**
|
||||
|
||||
* `mergeParams(params: ActionParams, strategies: MergeStrategies = {})`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `params` | `ActionParams` | - | 额外的参数集 |
|
||||
| `strategies` | `MergeStrategies` | - | 针对每个参数的合并策略 |
|
||||
|
||||
内置操作的默认合并策略如下表:
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 合并策略 | 描述 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `filterByTk` | `number \| string` | - | SQL `and` | 查询主键值 |
|
||||
| `filter` | `FilterOptions` | - | SQL `and` | 查询过滤参数 |
|
||||
| `fields` | `string[]` | - | 取并集 | 字段组 |
|
||||
| `appends` | `string[]` | `[]` | 取并集 | 附加的关联字段组 |
|
||||
| `except` | `string[]` | `[]` | 取并集 | 排除的字段组 |
|
||||
| `whitelist` | `string[]` | `[]` | 取交集 | 可处理字段的白名单 |
|
||||
| `blacklist` | `string[]` | `[]` | 取并集 | 可处理字段的黑名单 |
|
||||
| `sort` | `string[]` | - | SQL `order by` | 查询排序参数 |
|
||||
| `page` | `number` | - | 覆盖 | 页码 |
|
||||
| `pageSize` | `number` | - | 覆盖 | 每页数量 |
|
||||
| `values` | `Object` | - | 深度合并 | 操作提交的数据 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
ctx.action.mergeParams({
|
||||
filter: {
|
||||
name: 'foo',
|
||||
},
|
||||
fields: ['id', 'name'],
|
||||
except: ['name'],
|
||||
sort: ['id'],
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
values: {
|
||||
name: 'foo',
|
||||
},
|
||||
}, {
|
||||
filter: 'and',
|
||||
fields: 'union',
|
||||
except: 'union',
|
||||
sort: 'overwrite',
|
||||
page: 'overwrite',
|
||||
pageSize: 'overwrite',
|
||||
values: 'deepMerge',
|
||||
});
|
||||
```
|
252
docs/tr-TR/api/resourcer/index.md
Normal file
252
docs/tr-TR/api/resourcer/index.md
Normal file
@ -0,0 +1,252 @@
|
||||
# Resourcer
|
||||
|
||||
Resourcer 主要用于管理 API 资源与路由,也是 NocoBase 的内置模块,app 默认会自动创建一个 Resourcer 实例,大部分情况你可以通过 `app.resourcer` 访问。
|
||||
|
||||
资源路由管理器主要通过 [资源](/api/server/resourcer/resource) + [操作](/api/server/resourcer/action) 的概念定义服务端 API 接口,与 RESTful 的概念相似。大部分资源通过映射数据库表生成,包含常规的 CRUD 操作,以覆盖常见场景。但如果有额外需求,也可以在此基础上扩展更多的资源类型和操作类型。
|
||||
|
||||
## 包结构
|
||||
|
||||
可通过以下方式引入相关实体:
|
||||
|
||||
```ts
|
||||
import Resourcer, {
|
||||
Resource,
|
||||
Action,
|
||||
Middleware,
|
||||
branch
|
||||
} from '@nocobase/resourcer';
|
||||
```
|
||||
|
||||
## 构造函数
|
||||
|
||||
用于创建 Resourcer 管理器实例。由于 app 默认创建一个内置实例,所以通常不会直接使用构造函数。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: ResourcerOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `prefix` | `string` | - | 路由路径前缀 |
|
||||
| `accessors` | `Object` | _以下成员值_ | 默认操作方法名称标识 |
|
||||
| `accessors.list` | `string` | `'list'` | 列举操作方法名称标识 |
|
||||
| `accessors.get` | `string` | `'get'` | 获取操作方法名称标识 |
|
||||
| `accessors.create` | `string` | `'create'` | 创建操作方法名称标识 |
|
||||
| `accessors.update` | `string` | `'update'` | 更新操作方法名称标识 |
|
||||
| `accessors.delete` | `string` | `'destroy'` | 删除操作方法名称标识 |
|
||||
| `accessors.add` | `string` | `'add'` | 增加关联操作方法名称标识 |
|
||||
| `accessors.remove` | `string` | `'remove'` | 移除关联操作方法名称标识 |
|
||||
| `accessors.set` | `string` | `'set'` | 全量设置关联操作方法名称标识 |
|
||||
|
||||
**示例**
|
||||
|
||||
在创建 app 时件时,可以通过 `resourcer` 选项传入:
|
||||
|
||||
```ts
|
||||
const app = new Application({
|
||||
// 对应默认 resourcer 实例的配置项
|
||||
resourcer: {
|
||||
prefix: process.env.API_BASE_PATH
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `define()`
|
||||
|
||||
定义并向资源管理器注册一个资源对象。通常代替 `Resource` 类的构造函数使用。
|
||||
|
||||
**签名**
|
||||
|
||||
* `define(options: ResourceOptions): Resource`
|
||||
|
||||
**参数**
|
||||
|
||||
详见 [Resource 构造函数](/api/server/resourcer/resource#构造函数)。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.define({
|
||||
name: 'books',
|
||||
actions: {
|
||||
// 扩展的 action
|
||||
publish(ctx, next) {
|
||||
ctx.body = 'ok';
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `isDefined()`
|
||||
|
||||
检查对应名称的资源是否已被注册。
|
||||
|
||||
**签名**
|
||||
|
||||
* `isDefined(name: string): boolean`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 资源名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.isDefined('books'); // true
|
||||
```
|
||||
|
||||
### `registerAction()`
|
||||
|
||||
向资源管理器注册一个操作,可以指定针对特定的资源,如不指定资源名称,则认为是针对全局所有资源都可访问的操作。
|
||||
|
||||
**签名**
|
||||
|
||||
* `registerAction(name: string, handler: HandlerType): void`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 操作名称 |
|
||||
| `handler` | `HandlerType` | - | 操作处理函数 |
|
||||
|
||||
`name` 的值如果以 `<resourceName>:` 开头则代表仅针对 `<resourceName>` 资源可访问,否则认为是全局操作。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
// 注册后任意资源都可以进行 upload 操作
|
||||
app.resourcer.registerAction('upload', async (ctx, next) => {
|
||||
ctx.body = 'ok';
|
||||
});
|
||||
|
||||
// 仅针对 attachments 资源注册 upload 操作
|
||||
app.resourcer.registerAction('attachments:upload', async (ctx, next) => {
|
||||
ctx.body = 'ok';
|
||||
});
|
||||
```
|
||||
|
||||
### `registerActions()`
|
||||
|
||||
向资源管理器注册多个操作的集合方法。
|
||||
|
||||
**签名**
|
||||
|
||||
* `registerActions(actions: { [name: string]: HandlerType }): void`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `actions` | `{ [name: string]: HandlerType }` | - | 操作集合 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.registerActions({
|
||||
upload: async (ctx, next) => {
|
||||
ctx.body = 'ok';
|
||||
},
|
||||
'attachments:upload': async (ctx, next) => {
|
||||
ctx.body = 'ok';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### `getResource()`
|
||||
|
||||
获取对应名称的资源对象。
|
||||
|
||||
**签名**
|
||||
|
||||
* `getResource(name: string): Resource`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 资源名称 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.getResource('books');
|
||||
```
|
||||
|
||||
### `getAction()`
|
||||
|
||||
获取对应名称的操作处理函数。
|
||||
|
||||
**签名**
|
||||
|
||||
* `getAction(name: string): HandlerType`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 操作名称 |
|
||||
|
||||
`name` 的值如果以 `<resourceName>:` 开头则代表仅针对 `<resourceName>` 资源的操作,否则认为是全局操作。
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.getAction('upload');
|
||||
app.resourcer.getAction('attachments:upload');
|
||||
```
|
||||
|
||||
### `use()`
|
||||
|
||||
以 Koa 的形式注册一个中间件,中间件形成一个队列,并排在所有资源的操作处理函数之前执行。
|
||||
|
||||
**签名**
|
||||
|
||||
* `use(middleware: Middleware | Middleware[]): void`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `middleware` | `Middleware \| Middleware[]` | - | 中间件 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
console.log(ctx.req.url);
|
||||
await next();
|
||||
});
|
||||
```
|
||||
|
||||
### `middleware()`
|
||||
|
||||
生成一个兼容 Koa 的中间件,用于将资源的路由处理注入到应用中。
|
||||
|
||||
**签名**
|
||||
|
||||
* `middleware(options: KoaMiddlewareOptions): KoaMiddleware`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.prefix?` | `string` | `''` | 路径前缀。 |
|
||||
| `options.accessors?` | `Object` | `{}` | 常用方法的名称映射,与构造函数的 `accessors` 参数结构相同。 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const koa = new Koa();
|
||||
|
||||
const resourcer = new Resourcer();
|
||||
|
||||
// 生成兼容 Koa 的中间件
|
||||
koa.use(resourcer.middleware());
|
||||
```
|
168
docs/tr-TR/api/resourcer/middleware.md
Normal file
168
docs/tr-TR/api/resourcer/middleware.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Middleware
|
||||
|
||||
与 Koa 的中间件类似,但提供了更多增强的功能,可以方便的进行更多的扩展。
|
||||
|
||||
中间件定义后可以在资源管理器等多处进行插入使用,由开发者自行控制调用的时机。
|
||||
|
||||
## 构造函数
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: Function)`
|
||||
* `constructor(options: MiddlewareOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options` | `Function` | - | 中间件处理函数 |
|
||||
| `options` | `MiddlewareOptions ` | - | 中间件配置项 |
|
||||
| `options.only` | `string[]` | - | 仅允许指定的操作 |
|
||||
| `options.except` | `string[]` | - | 排除指定的操作 |
|
||||
| `options.handler` | `Function` | - | 处理函数 |
|
||||
|
||||
**示例**
|
||||
|
||||
简单定义:
|
||||
|
||||
```ts
|
||||
const middleware = new Middleware((ctx, next) => {
|
||||
await next();
|
||||
});
|
||||
```
|
||||
|
||||
使用相关参数:
|
||||
|
||||
```ts
|
||||
const middleware = new Middleware({
|
||||
only: ['create', 'update'],
|
||||
async handler(ctx, next) {
|
||||
await next();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `getHandler()`
|
||||
|
||||
返回已经过编排的处理函数。
|
||||
|
||||
**示例**
|
||||
|
||||
以下中间件在请求时会先输出 `1`,再输出 `2`。
|
||||
|
||||
```ts
|
||||
const middleware = new Middleware((ctx, next) => {
|
||||
console.log(1);
|
||||
await next();
|
||||
});
|
||||
|
||||
middleware.use(async (ctx, next) => {
|
||||
console.log(2);
|
||||
await next();
|
||||
});
|
||||
|
||||
app.resourcer.use(middleware.getHandler());
|
||||
```
|
||||
|
||||
### `use()`
|
||||
|
||||
对当前中间件添加中间件函数。用于提供中间件的扩展点。示例见 `getHandler()`。
|
||||
|
||||
**签名**
|
||||
|
||||
* `use(middleware: Function)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `middleware` | `Function` | - | 中间件处理函数 |
|
||||
|
||||
### `disuse()`
|
||||
|
||||
移除当前中间件已添加的中间件函数。
|
||||
|
||||
**签名**
|
||||
|
||||
* `disuse(middleware: Function)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `middleware` | `Function` | - | 中间件处理函数 |
|
||||
|
||||
**示例**
|
||||
|
||||
以下示例在请求处理是只输出 `1`,不执行 fn1 中的 `2` 输出。
|
||||
|
||||
```ts
|
||||
const middleware = new Middleware((ctx, next) => {
|
||||
console.log(1);
|
||||
await next();
|
||||
});
|
||||
|
||||
async function fn1(ctx, next) {
|
||||
console.log(2);
|
||||
await next();
|
||||
}
|
||||
|
||||
middleware.use(fn1);
|
||||
|
||||
app.resourcer.use(middleware.getHandler());
|
||||
|
||||
middleware.disuse(fn1);
|
||||
```
|
||||
|
||||
### `canAccess()`
|
||||
|
||||
判断当前中间件针对特定操作是否要被调用,通常由资源管理器内部处理。
|
||||
|
||||
**签名**
|
||||
|
||||
* `canAccess(name: string): boolean`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 操作名称 |
|
||||
|
||||
## 其他导出
|
||||
|
||||
### `branch()`
|
||||
|
||||
创建一个分支中间件,用于在中间件中进行分支处理。
|
||||
|
||||
**签名**
|
||||
|
||||
* `branch(map: { [key: string]: Function }, reducer: Function, options): Function`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `map` | `{ [key: string]: Function }` | - | 分支处理函数映射表,键名由后续计算函数在调用时给出 |
|
||||
| `reducer` | `(ctx) => string` | - | 计算函数,用于基于上下文计算出分支的键名 |
|
||||
| `options?` | `Object` | - | 分支配置项 |
|
||||
| `options.keyNotFound?` | `Function` | `ctx.throw(404)` | 未找到键名时的处理函数 |
|
||||
| `options.handlerNotSet?` | `Function` | `ctx.throw(404)` | 未定义处理函数时的处理 |
|
||||
|
||||
**示例**
|
||||
|
||||
用户验证时,根据请求 URL 中 query 部分的 `authenticator` 参数的值决定后续需要如何处理:
|
||||
|
||||
```ts
|
||||
app.resourcer.use(branch({
|
||||
'password': async (ctx, next) => {
|
||||
// ...
|
||||
},
|
||||
'sms': async (ctx, next) => {
|
||||
// ...
|
||||
},
|
||||
}, (ctx) => {
|
||||
return ctx.action.params.authenticator ?? 'password';
|
||||
}));
|
||||
```
|
114
docs/tr-TR/api/resourcer/resource.md
Normal file
114
docs/tr-TR/api/resourcer/resource.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Resource
|
||||
|
||||
Resource 用于定义资源实例。被 Resourcer 管理的资源实例都可以通过 HTTP 请求访问。
|
||||
|
||||
## 构造函数
|
||||
|
||||
用于创建 Resource 实例。通常由 Resourcer 管理器的 `define()` 接口调用替代,不需要直接使用。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: ResourceOptions, resourcer: Resourcer)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.name` | `string` | - | 资源名称,对应 URL 路由中的资源地址部分。 |
|
||||
| `options.type` | `string` | `'single'` | 资源类型,可选项为 `'single'`、`'hasOne'`、`'hasMany'`、`'belongsTo'`、`'belongsToMany'`。 |
|
||||
| `options.actions` | `Object` | - | 对资源可进行的操作列表,详见示例部分。 |
|
||||
| `options.middlewares` | `MiddlewareType \| MiddlewareType[]` | - | 对当前定义资源进行任意操作访问时的中间件列表,详见示例部分。 |
|
||||
| `options.only` | `ActionName[]` | `[]` | 针对全局操作的白名单列表,当数组中有值时(`length > 0`),只有数组中的操作可被访问。 |
|
||||
| `options.except` | `ActionName[]` | `[]` | 针对全局操作的黑名单列表,当数组中有值时(`length > 0`),除数组中的操作外,其他操作可被访问。 |
|
||||
| `resourcer` | `Resourcer` | - | 所属资源管理器实例。 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
app.resourcer.define({
|
||||
name: 'books',
|
||||
actions: {
|
||||
// 扩展的 action
|
||||
publish(ctx, next) {
|
||||
ctx.body = 'ok';
|
||||
}
|
||||
},
|
||||
middleware: [
|
||||
// 扩展的中间件
|
||||
async (ctx, next) => {
|
||||
await next();
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `options`
|
||||
|
||||
当前资源的配置项。
|
||||
|
||||
### `resourcer`
|
||||
|
||||
所属的资源管理器实例。
|
||||
|
||||
### `middlewares`
|
||||
|
||||
已注册的中间件列表。
|
||||
|
||||
### `actions`
|
||||
|
||||
已注册的操作映射表。
|
||||
|
||||
### `except`
|
||||
|
||||
操作排除的名单列表。
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `getName()`
|
||||
|
||||
获取当前资源的名称。
|
||||
|
||||
**签名**
|
||||
|
||||
* `getName(): string`
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const resource = app.resourcer.define({
|
||||
name: 'books'
|
||||
});
|
||||
|
||||
resource.getName(); // 'books'
|
||||
```
|
||||
|
||||
### `getAction()`
|
||||
|
||||
根据名称获取当前资源的操作。
|
||||
|
||||
**签名**
|
||||
|
||||
* `getAction(name: string): Action`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `string` | - | 操作名称。 |
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
const resource = app.resourcer.define({
|
||||
name: 'books',
|
||||
actions: {
|
||||
publish(ctx, next) {
|
||||
ctx.body = 'ok';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
resource.getAction('publish'); // [Function: publish]
|
||||
```
|
281
docs/tr-TR/api/sdk.md
Normal file
281
docs/tr-TR/api/sdk.md
Normal file
@ -0,0 +1,281 @@
|
||||
# @nocobase/sdk
|
||||
|
||||
## APIClient
|
||||
|
||||
```ts
|
||||
class APIClient {
|
||||
// axios 实例
|
||||
axios: AxiosInstance;
|
||||
// 构造器
|
||||
constructor(instance?: AxiosInstance | AxiosRequestConfig);
|
||||
// 客户端请求,支持 AxiosRequestConfig 和 ResourceActionOptions
|
||||
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D> | ResourceActionOptions): Promise<R>;
|
||||
// 获取资源
|
||||
resource<R = IResource>(name: string, of?: any): R;
|
||||
}
|
||||
```
|
||||
|
||||
初始化实例
|
||||
|
||||
```ts
|
||||
import axios from 'axios';
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
|
||||
// 提供 AxiosRequestConfig 配置参数
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
|
||||
// 提供 AxiosInstance
|
||||
const instance = axios.create({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
const api = new APIClient(instance);
|
||||
```
|
||||
|
||||
## Mock
|
||||
|
||||
```ts
|
||||
import { APIClient } from '@nocobase/sdk';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
});
|
||||
|
||||
const mock = new MockAdapter(api.axios);
|
||||
|
||||
mock.onGet('users:get').reply(200, {
|
||||
data: { id: 1, name: 'John Smith' },
|
||||
});
|
||||
|
||||
await api.request({ url: 'users:get' });
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
APIClient 默认使用的 localStorage,你也可以自定义 Storage,如:
|
||||
|
||||
```ts
|
||||
import { Storage } from '@nocobase/sdk';
|
||||
|
||||
class MemoryStorage extends Storage {
|
||||
items = new Map();
|
||||
|
||||
clear() {
|
||||
this.items.clear();
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.items.get(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
return this.items.set(key, value);
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
return this.items.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
storageClass: CustomStorage,
|
||||
});
|
||||
```
|
||||
|
||||
## Auth
|
||||
|
||||
```ts
|
||||
// 登录并记录 token
|
||||
api.auth.signIn({ email, password });
|
||||
// 注销并删除 token
|
||||
api.auth.signOut();
|
||||
// 设置 token
|
||||
api.auth.setToken('123');
|
||||
// 设置 role(当需要多角色时)
|
||||
api.auth.setRole('admin');
|
||||
// 设置 locale(当需要多语言时)
|
||||
api.auth.setLocale('zh-CN');
|
||||
```
|
||||
|
||||
自定义 Auth
|
||||
|
||||
```ts
|
||||
import { Auth } from '@nocobase/sdk';
|
||||
|
||||
class CustomAuth extends Auth {
|
||||
|
||||
}
|
||||
|
||||
const api = new APIClient({
|
||||
baseURL: 'https://localhost:8000/api',
|
||||
authClass: CustomAuth,
|
||||
});
|
||||
```
|
||||
|
||||
## Request
|
||||
|
||||
```ts
|
||||
// url
|
||||
await api.request({
|
||||
url: 'users:list',
|
||||
// request params
|
||||
params: {
|
||||
filter: {
|
||||
'email.$includes': 'noco',
|
||||
},
|
||||
},
|
||||
// request body
|
||||
data,
|
||||
});
|
||||
|
||||
// resource & action
|
||||
await api.request({
|
||||
resource: 'users',
|
||||
action: 'list',
|
||||
// action params
|
||||
params: {
|
||||
filter: {
|
||||
'email.$includes': 'noco',
|
||||
},
|
||||
page: 1,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Resource action
|
||||
|
||||
```ts
|
||||
await api.resource('collection')[action]();
|
||||
await api.resource('collection.association', collectionId)[action]();
|
||||
```
|
||||
|
||||
## Action API
|
||||
|
||||
```ts
|
||||
await api.resource('collection').create();
|
||||
await api.resource('collection').get();
|
||||
await api.resource('collection').list();
|
||||
await api.resource('collection').update();
|
||||
await api.resource('collection').destroy();
|
||||
await api.resource('collection.association', collectionId).create();
|
||||
await api.resource('collection.association', collectionId).get();
|
||||
await api.resource('collection.association', collectionId).list();
|
||||
await api.resource('collection.association', collectionId).update();
|
||||
await api.resource('collection.association', collectionId).destroy();
|
||||
```
|
||||
|
||||
### `get`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
get: (options?: GetActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface GetActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
fields?: string || string[];
|
||||
appends?: string || string[];
|
||||
expect?: string || string[];
|
||||
sort?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
### `list`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
list: (options?: ListActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface ListActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
fields?: string || string[];
|
||||
appends?: string || string[];
|
||||
expect?: string || string[];
|
||||
sort?: string[];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
paginate?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### `create`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
create: (options?: CreateActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface CreateActionOptions {
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
values?: {[key: sting]: any};
|
||||
}
|
||||
```
|
||||
|
||||
### `update`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
update: (options?: UpdateActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface UpdateActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
whitelist?: string[];
|
||||
blacklist?: string[];
|
||||
values?: {[key: sting]: any};
|
||||
}
|
||||
```
|
||||
|
||||
### `destroy`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
destroy: (options?: DestroyActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface DestroyActionOptions {
|
||||
filter?: any;
|
||||
filterByTk?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### `move`
|
||||
|
||||
```ts
|
||||
interface Resource {
|
||||
move: (options?: MoveActionOptions) => Promise<any>;
|
||||
}
|
||||
|
||||
interface MoveActionOptions {
|
||||
sourceId: any;
|
||||
targetId?: any;
|
||||
/** @default 'sort' */
|
||||
sortField?: any;
|
||||
targetScope?: {[key: string]: any};
|
||||
sticky?: boolean;
|
||||
method?: 'insertAfter' | 'prepend';
|
||||
}
|
||||
```
|
||||
|
||||
### `<custom>`
|
||||
|
||||
```ts
|
||||
interface AttachmentResource {
|
||||
|
||||
}
|
||||
|
||||
interface UploadActionOptions {
|
||||
|
||||
}
|
||||
|
||||
api.resource<AttachmentResource>('attachments').upload();
|
||||
api.resource('attachments').upload<UploadActionOptions>();
|
||||
```
|
1
docs/tr-TR/api/server/app-manager.md
Normal file
1
docs/tr-TR/api/server/app-manager.md
Normal file
@ -0,0 +1 @@
|
||||
# AppManager
|
188
docs/tr-TR/api/server/application.md
Normal file
188
docs/tr-TR/api/server/application.md
Normal file
@ -0,0 +1,188 @@
|
||||
# Application
|
||||
|
||||
基于 [Koa](https://koajs.com/) 实现的 WEB 框架,兼容所有的 Koa 插件。
|
||||
|
||||
## 构造函数
|
||||
|
||||
### `constructor()`
|
||||
|
||||
创建一个应用实例。
|
||||
|
||||
**签名**
|
||||
|
||||
* `constructor(options: ApplicationOptions)`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.database` | `IDatabaseOptions` or `Database` | `{}` | 数据库配置 |
|
||||
| `options.resourcer` | `ResourcerOptions` | `{}` | 资源路由配置 |
|
||||
| `options.cors` | [`CorsOptions`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/koa__cors/index.d.ts#L24) | `{}` | 跨域配置,参考 [@koa/cors](https://npmjs.com/package/@koa/cors) |
|
||||
| `options.dataWrapping` | `boolean` | `true` | 是否包装响应数据,`true` 则将把通常的 `ctx.body` 包装为 `{ data, meta }` 的结构。 |
|
||||
| `options.registerActions` | `boolean` | `true` | 是否注册默认的 [actions](#) |
|
||||
| `options.i18n` | `I18nOptions` | `{}` | 国际化配置,参考 [i18next](https://www.i18next.com/overview/api) |
|
||||
| `options.plugins` | `PluginConfiguration[]` | `[]` | 默认启用的插件配置 |
|
||||
|
||||
Type
|
||||
|
||||
```ts
|
||||
interface ApplicationOptions {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
import Application from '@nocobase/server';
|
||||
|
||||
const app = new Application({
|
||||
database: {
|
||||
dialect: 'mysql',
|
||||
host: 'localhost',
|
||||
port: 3306,
|
||||
username: 'root',
|
||||
password: '123456',
|
||||
database: 'test',
|
||||
},
|
||||
resourcer: {
|
||||
prefix: '/api',
|
||||
},
|
||||
cors: {
|
||||
origin: '*',
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 实例成员
|
||||
|
||||
### `cli`
|
||||
|
||||
命令行工具实例,参考 npm 包 [Commander](https://www.npmjs.com/package/commander)。
|
||||
|
||||
### `db`
|
||||
|
||||
数据库实例,相关 API 参考 [Database](/api/database)。
|
||||
|
||||
### `resourcer`
|
||||
|
||||
应用初始化自动创建的资源路由管理实例,相关 API 参考 [Resourcer](/api/resourcer)。
|
||||
|
||||
### `acl`
|
||||
|
||||
ACL 实例,相关 API 参考 [ACL](/api/acl)。
|
||||
|
||||
### `i18n`
|
||||
|
||||
I18next 实例,相关 API 参考 [I18next](https://www.i18next.com/overview/api)。
|
||||
|
||||
### `pm`
|
||||
|
||||
插件管理器实例,相关 API 参考 [PluginManager](./plugin-manager)。
|
||||
|
||||
### `version`
|
||||
|
||||
应用版本实例,相关 API 参考 [ApplicationVersion](./application-version)。
|
||||
|
||||
### `middleware`
|
||||
|
||||
内置的中间件有:
|
||||
|
||||
- i18next
|
||||
- bodyParser
|
||||
- cors
|
||||
- dataWrapping
|
||||
- collection2resource
|
||||
- restApiMiddleware
|
||||
|
||||
### `context`
|
||||
|
||||
继承自 koa 的 context,可以通过 `app.context` 访问,用于向每个请求注入上下文可访问的内容。参考 [Koa Context](https://koajs.com/#app-context)。
|
||||
|
||||
NocoBase 默认对 context 注入了以下成员,可以在请求处理函数中直接使用:
|
||||
|
||||
| 变量名 | 类型 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| `ctx.app` | `Application` | 应用实例 |
|
||||
| `ctx.db` | `Database` | 数据库实例 |
|
||||
| `ctx.resourcer` | `Resourcer` | 资源路由管理器实例 |
|
||||
| `ctx.action` | `Action` | 资源操作相关对象实例 |
|
||||
| `ctx.i18n` | `I18n` | 国际化实例 |
|
||||
| `ctx.t` | `i18n.t` | 国际化翻译函数快捷方式 |
|
||||
| `ctx.getBearerToken()` | `Function` | 获取请求头中的 bearer token |
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `use()`
|
||||
|
||||
注册中间件,兼容所有 [Koa 插件](https://www.npmjs.com/search?q=koa)
|
||||
|
||||
### `on()`
|
||||
|
||||
订阅应用级事件,主要与生命周期相关,等同于 `eventEmitter.on()`。所有可订阅事件参考 [事件](#事件)。
|
||||
|
||||
### `command()`
|
||||
|
||||
自定义 command
|
||||
|
||||
### `findCommand()`
|
||||
|
||||
查找已定义 command
|
||||
|
||||
### `runAsCLI()`
|
||||
|
||||
以 CLI 的方式运行。
|
||||
|
||||
### `load()`
|
||||
|
||||
加载应用配置。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async load(): Promise<void>`
|
||||
|
||||
### `reload()`
|
||||
|
||||
重载应用配置。
|
||||
|
||||
### `install()`
|
||||
|
||||
初始化安装应用,同步安装插件。
|
||||
|
||||
### `upgrade()`
|
||||
|
||||
升级应用,同步升级插件。
|
||||
|
||||
### `start()`
|
||||
|
||||
启动应用,如果配置了监听的端口,将启动监听,之后应用即可接受 HTTP 请求。
|
||||
|
||||
**签名**
|
||||
|
||||
* `async start(options: StartOptions): Promise<void>`
|
||||
|
||||
**参数**
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
| --- | --- | --- | --- |
|
||||
| `options.listen?` | `ListenOptions` | `{}` | HTTP 监听参数对象 |
|
||||
| `options.listen.port?` | `number` | 13000 | 端口 |
|
||||
| `options.listen.host?` | `string` | `'localhost'` | 域名 |
|
||||
|
||||
### `stop()`
|
||||
|
||||
停止应用,此方法会关闭数据库连接,关闭 HTTP 端口,不会删除数据。
|
||||
|
||||
### `destroy()`
|
||||
|
||||
删除应用,此方法会删除应用对应的数据库。
|
||||
|
||||
## 事件
|
||||
|
||||
### `'beforeLoad'` / `'afterLoad'`
|
||||
### `'beforeInstall'` / `'afterInstall'`
|
||||
### `'beforeUpgrade'` / `'afterUpgrade'`
|
||||
### `'beforeStart'` / `'afterStart'`
|
||||
### `'beforeStop'` / `'afterStop'`
|
||||
### `'beforeDestroy'` / `'afterDestroy'`
|
1
docs/tr-TR/api/server/i18n.md
Normal file
1
docs/tr-TR/api/server/i18n.md
Normal file
@ -0,0 +1 @@
|
||||
# I18n
|
59
docs/tr-TR/api/server/plugin-manager.md
Normal file
59
docs/tr-TR/api/server/plugin-manager.md
Normal file
@ -0,0 +1,59 @@
|
||||
# PluginManager
|
||||
|
||||
应用插件管理器的实例,由应用自动创建,可以通过 `app.pm` 访问。
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `create()`
|
||||
|
||||
在本地创建一个插件脚手架
|
||||
|
||||
**签名**
|
||||
|
||||
```ts
|
||||
create(name, options): void;
|
||||
```
|
||||
|
||||
### `addStatic()`
|
||||
|
||||
**签名**
|
||||
|
||||
```ts
|
||||
addStatic(plugin: any, options?: PluginOptions): Plugin;
|
||||
```
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
pm.addStatic('nocobase');
|
||||
```
|
||||
|
||||
### `add()`
|
||||
|
||||
**签名**
|
||||
|
||||
```ts
|
||||
async add(plugin: any, options?: PluginOptions): Promise<Plugin>;
|
||||
async add(plugin: string[], options?: PluginOptions): Promise<Plugin[]>;
|
||||
```
|
||||
|
||||
**示例**
|
||||
|
||||
```ts
|
||||
await pm.add(['test'], {
|
||||
builtIn: true,
|
||||
enabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
### `get()`
|
||||
|
||||
获取插件实例
|
||||
|
||||
### `enable()`
|
||||
|
||||
### `disable()`
|
||||
|
||||
### `remove()`
|
||||
|
||||
### `upgrade()`
|
59
docs/tr-TR/api/server/plugin.md
Normal file
59
docs/tr-TR/api/server/plugin.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Plugin
|
||||
|
||||
## 示例
|
||||
|
||||
```ts
|
||||
const app = new Application();
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
afterAdd() {}
|
||||
beforeLoad() {}
|
||||
load() {}
|
||||
install() {}
|
||||
afterEnable() {}
|
||||
afterDisable() {}
|
||||
remove() {}
|
||||
}
|
||||
|
||||
app.plugin(MyPlugin, { name: 'my-plugin' });
|
||||
```
|
||||
|
||||
## 属性
|
||||
|
||||
### `options`
|
||||
|
||||
插件配置信息
|
||||
|
||||
### `name`
|
||||
|
||||
插件标识,只读
|
||||
|
||||
## 实例方法
|
||||
|
||||
### `afterAdd()`
|
||||
|
||||
插件 add/addStatic 之后
|
||||
|
||||
### `beforeLoad()`
|
||||
|
||||
插件加载前,如事件或类注册
|
||||
|
||||
### `load()`
|
||||
|
||||
加载插件,配置之类
|
||||
|
||||
### `install()`
|
||||
|
||||
插件安装逻辑,如初始化数据
|
||||
|
||||
### `afterEnable()`
|
||||
|
||||
插件激活之后的逻辑
|
||||
|
||||
### `afterDisable()`
|
||||
|
||||
插件禁用之后的逻辑
|
||||
|
||||
### `remove()`
|
||||
|
||||
用于实现插件删除逻辑
|
64
docs/tr-TR/development/app-ds.md
Normal file
64
docs/tr-TR/development/app-ds.md
Normal file
@ -0,0 +1,64 @@
|
||||
# App directory structure
|
||||
|
||||
Either [Git source](/welcome/getting-started/installation/git-clone) or [create-nocobase-app](/welcome/getting-started/installation/create-nocobase-app), the directory structure of the created NocoBase application is the same, with the following structure.
|
||||
|
||||
```bash
|
||||
├── my-nocobase-app
|
||||
├── packages # NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages
|
||||
├── app
|
||||
├── client # client-side module
|
||||
├── server # server-side modules
|
||||
├─ plugins # plugin directory
|
||||
├── storage # for database files, attachments, cache, etc.
|
||||
├─ db
|
||||
├── .env # environment variables
|
||||
├── .buildrc.ts # package configuration for packages, supports cjs, esm and umd packages.
|
||||
├── jest.config.js
|
||||
├── jest.setup.ts
|
||||
├── lerna.json
|
||||
├── package.json
|
||||
├── tsconfig.jest.json
|
||||
├─ tsconfig.json
|
||||
├── tsconfig.server.json
|
||||
```
|
||||
|
||||
## Packages directory
|
||||
|
||||
```bash
|
||||
├─ packages
|
||||
├─ app
|
||||
├── client
|
||||
├─ public
|
||||
├─ src
|
||||
├─ pages
|
||||
├─ index.tsx
|
||||
├─ .umirc.ts
|
||||
├─ package.json
|
||||
├─ server
|
||||
├─ src
|
||||
├─ config
|
||||
├─ index.ts
|
||||
├─ package.json
|
||||
├─ /plugins
|
||||
├─ my-plugin
|
||||
├─ src
|
||||
├─ package.json
|
||||
```
|
||||
|
||||
NocoBase uses the Monorepo approach to manage code, dividing different modules into different packages.
|
||||
|
||||
- `app/client` is the client module of the application, built on [umi](https://umijs.org/zh-CN).
|
||||
- `app/server` is the server-side module of the application.
|
||||
- The `plugins/*` directory can hold various plugins.
|
||||
|
||||
## storages directory
|
||||
|
||||
Store database files, attachments, cache, etc.
|
||||
|
||||
## .env files
|
||||
|
||||
Environment variables.
|
||||
|
||||
## .buildrc.ts file
|
||||
|
||||
Package configuration for packages, supporting cjs, esm and umd formats.
|
140
docs/tr-TR/development/client/i18n.md
Normal file
140
docs/tr-TR/development/client/i18n.md
Normal file
@ -0,0 +1,140 @@
|
||||
# Internationalization
|
||||
|
||||
Client-side internationalization for multiple languages is implemented based on the npm package [react-i18next](https://npmjs.com/package/react-i18next), which provides a wrapper for the `<I18nextProvider>` component at the top level of the application, allowing the relevant methods to be used directly at any location.
|
||||
|
||||
Adding language packages:
|
||||
|
||||
```tsx | pure
|
||||
import { i18n } from '@nocobase/client';
|
||||
|
||||
i18n.addResources('zh-CN', 'test', {
|
||||
Hello: '你好',
|
||||
World: '世界',
|
||||
});
|
||||
```
|
||||
|
||||
Note: Here the second parameter filled in `'test'` is the language namespace, usually the plugin itself defines the language resources should create a specific namespace according to their own plugin package name, in order to avoid conflicts with other language resources. The default namespace in NocoBase is `'client'` and most common and basic language translations are placed in this namespace. When the required language is not provided, it can be defined by extension in the plugin's own namespace.
|
||||
|
||||
To call the translation function in the component:
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MyComponent() {
|
||||
// Use the previously defined namespace
|
||||
const { t } = useTranslation('test');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{t('World')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The template method `'{{t(<languageKey>)}}'` can be used directly in the SchemaComponent component, and the translation functions in the template will automatically be executed.
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { SchemaComponent } from '@nocobase/client';
|
||||
|
||||
export default function MySchemaComponent() {
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
value: '{{t("Hello", { ns: "test" })}}'
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
In some special cases where it is also necessary to define multilingualism as a template, the NocoBase built-in `compile()` method can be used to compile to multilingual results.
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { useCompile } from '@nocobase/client';
|
||||
|
||||
const title = '{{t("Hello", { ns: "test" })}}';
|
||||
|
||||
export default function MyComponent() {
|
||||
const { compile } = useCompile();
|
||||
|
||||
return (
|
||||
<div>{compile(title)}</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Suggested configuration
|
||||
|
||||
With English text as the key and translation as the value, the benefit of this, even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g.
|
||||
|
||||
```ts
|
||||
i18n.addResources('zh-CN', 'my-plugin', {
|
||||
'Show dialog': '显示对话框',
|
||||
'Hide dialog': '隐藏对话框'
|
||||
});
|
||||
```
|
||||
|
||||
To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it for easy management.
|
||||
|
||||
```bash
|
||||
|- /my-plugin
|
||||
|- /src
|
||||
|- /client
|
||||
|- locale # Multilingual folder
|
||||
|- zh-CN.ts
|
||||
|- en-US.ts
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
### Client-side components with multiple languages
|
||||
|
||||
For example, the order status component, with different text displays depending on the value.
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { Select } from 'antd';
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
i18n.addResources('zh-CN', 'sample-shop-i18n', {
|
||||
Pending: '已下单',
|
||||
Paid: '已支付',
|
||||
Delivered: '已发货',
|
||||
Received: '已签收'
|
||||
});
|
||||
|
||||
const ORDER_STATUS_LIST = [
|
||||
{ value: -1, label: 'Canceled (untranslated)' },
|
||||
{ value: 0, label: 'Pending' },
|
||||
{ value: 1, label: 'Paid' },
|
||||
{ value: 2, label: 'Delivered' },
|
||||
{ value: 3, label: 'Received' },
|
||||
]
|
||||
|
||||
function OrderStatusSelect() {
|
||||
const { t } = useTranslation('sample-shop-i18n');
|
||||
|
||||
return (
|
||||
<Select style={{ minWidth: '8em' }}>
|
||||
{ORDER_STATUS_LIST.map(item => (
|
||||
<Select.Option value={item.value}>{t(item.label)}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<OrderStatusSelect />
|
||||
);
|
||||
}
|
||||
```
|
73
docs/tr-TR/development/client/index.md
Normal file
73
docs/tr-TR/development/client/index.md
Normal file
@ -0,0 +1,73 @@
|
||||
# Overview
|
||||
|
||||
Most of the extensions for the NocoBase client are provided as Providers.
|
||||
|
||||
## Built-in Providers
|
||||
|
||||
- APIClientProvider
|
||||
- I18nextProvider
|
||||
- AntdConfigProvider
|
||||
- RemoteRouteSwitchProvider
|
||||
- SystemSettingsProvider
|
||||
- PluginManagerProvider
|
||||
- SchemaComponentProvider
|
||||
- SchemaInitializerProvider
|
||||
- BlockSchemaComponentProvider
|
||||
- AntdSchemaComponentProvider
|
||||
- DocumentTitleProvider
|
||||
- ACLProvider
|
||||
|
||||
## Registration of client-side Provider modules
|
||||
|
||||
Static Providers are registered with app.use() and dynamic Providers are adapted with dynamicImport.
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import { Application } from '@nocobase/client';
|
||||
|
||||
const app = new Application({
|
||||
apiClient: {
|
||||
baseURL: process.env.API_BASE_URL,
|
||||
},
|
||||
dynamicImport: (name: string) => {
|
||||
return import(`... /plugins/${name}`);
|
||||
},
|
||||
});
|
||||
|
||||
// When visiting the /hello page, display Hello world!
|
||||
const HelloProvider = React.memo((props) => {
|
||||
const location = useLocation();
|
||||
if (location.pathname === '/hello') {
|
||||
return <div>Hello world!</div>
|
||||
}
|
||||
return <>{props.children}</>
|
||||
});
|
||||
|
||||
app.use(HelloProvider);
|
||||
```
|
||||
|
||||
## Client-side of plugins
|
||||
|
||||
Directory structure of the client-side of an empty plugin is as follows
|
||||
|
||||
```bash
|
||||
|- /my-plugin
|
||||
|- /src
|
||||
|- /client
|
||||
|- index.tsx
|
||||
|- client.d.ts
|
||||
|- client.js
|
||||
```
|
||||
|
||||
``client/index.tsx`` reads as follows.
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
|
||||
// This is an empty Provider, only children are passed, no custom Context is provided
|
||||
export default React.memo((props) => {
|
||||
return <>{props.children}</>;
|
||||
});
|
||||
```
|
||||
|
||||
After the plugin pm.add, it writes the `my-plugin.ts` file to the `packages/app/client/src/plugins` directory.
|
1
docs/tr-TR/development/client/m.svg
Normal file
1
docs/tr-TR/development/client/m.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 13 KiB |
33
docs/tr-TR/development/client/settings-center.md
Normal file
33
docs/tr-TR/development/client/settings-center.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Settings Center
|
||||
|
||||
<img src="./settings-center/settings-tab.jpg" style="max-width: 100%;"/>
|
||||
|
||||
## Example
|
||||
|
||||
```tsx | pure
|
||||
import { SettingsCenterProvider } from '@nocobase/client';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
const HelloTab => () => <div>Hello Tab</div>;
|
||||
|
||||
export default React.memo((props) => {
|
||||
return (
|
||||
<SettingsCenterProvider
|
||||
settings={{
|
||||
'sample-hello': {
|
||||
title: 'Hello',
|
||||
icon: 'ApiOutlined',
|
||||
tabs: {
|
||||
tab1: {
|
||||
title: 'Hello Tab',
|
||||
component: HelloTab,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>{props.children}</SettingsCenterProvider>
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
See [samples/hello](https://github.com/nocobase/nocobase/tree/develop/packages/samples/hello) for full examples.
|
BIN
docs/tr-TR/development/client/settings-center/settings-tab.jpg
Normal file
BIN
docs/tr-TR/development/client/settings-center/settings-tab.jpg
Normal file
Binary file not shown.
41
docs/tr-TR/development/client/test.md
Normal file
41
docs/tr-TR/development/client/test.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Testing
|
||||
|
||||
Testing is based on the [Jest](https://jestjs.io/) testing framework. Also included are common React testing libraries such as [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
|
||||
## Example
|
||||
|
||||
```tsx | pure
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RouteSwitch } from '../RouteSwitch';
|
||||
import { RouteSwitchProvider } from '../RouteSwitchProvider';
|
||||
|
||||
const Home = () => <h1>Home</h1>;
|
||||
const About = () => <h1>About</h1>;
|
||||
|
||||
describe('route-switch', () => {
|
||||
it('case 1', () => {
|
||||
const App = () => {
|
||||
return (
|
||||
<RouteSwitchProvider components={{ Home, About }}>
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
<RouteSwitch
|
||||
routes={[
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</RouteSwitchProvider>
|
||||
);
|
||||
};
|
||||
const { container } = render(<App />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
```
|
63
docs/tr-TR/development/client/ui-router.md
Normal file
63
docs/tr-TR/development/client/ui-router.md
Normal file
@ -0,0 +1,63 @@
|
||||
# UI Routing
|
||||
|
||||
NocoBase Client's Router is based on [React Router](https://v5.reactrouter.com/web/guides/quick-start) and can be configured via `<RouteSwitch routes={[]} />` to configure ui routes with the following example.
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* defaultShowCode: true
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Link, MemoryRouter as Router } from 'react-router-dom';
|
||||
import { RouteRedirectProps, RouteSwitchProvider, RouteSwitch } from '@nocobase/client';
|
||||
|
||||
const Home = () => <h1>Home</h1>;
|
||||
const About = () => <h1>About</h1>;
|
||||
|
||||
const routes: RouteRedirectProps[] = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/',
|
||||
exact: true,
|
||||
component: 'Home',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/about',
|
||||
component: 'About',
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<RouteSwitchProvider components={{ Home, About }}>
|
||||
<Router initialEntries={['/']}>
|
||||
<Link to={'/'}>Home</Link>, <Link to={'/about'}>About</Link>
|
||||
<RouteSwitch routes={routes} />
|
||||
</Router>
|
||||
</RouteSwitchProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
In a full NocoBase application, the Route can be extended in a similar way as follows.
|
||||
|
||||
```tsx | pure
|
||||
import { RouteSwitchContext } from '@nocobase/client';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
const HelloWorld = () => {
|
||||
return <div>Hello ui router</div>;
|
||||
};
|
||||
|
||||
export default React.memo((props) => {
|
||||
const ctx = useContext(RouteSwitchContext);
|
||||
ctx.routes.push({
|
||||
type: 'route',
|
||||
path: '/hello-world',
|
||||
component: HelloWorld,
|
||||
});
|
||||
return <RouteSwitchContext.Provider value={ctx}>{props.children}</RouteSwitchContext.Provider>;
|
||||
});
|
||||
```
|
||||
|
||||
See [packages/samples/custom-page](https://github.com/nocobase/nocobase/tree/develop/packages/samples/custom-page) for the full example
|
2
docs/tr-TR/development/client/ui-schema-designer/acl.md
Normal file
2
docs/tr-TR/development/client/ui-schema-designer/acl.md
Normal file
@ -0,0 +1,2 @@
|
||||
# ACL
|
||||
|
@ -0,0 +1,2 @@
|
||||
# 区块
|
||||
|
@ -0,0 +1,128 @@
|
||||
# Collection Manager
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { observer, ISchema, useForm } from '@formily/react';
|
||||
import {
|
||||
SchemaComponent,
|
||||
SchemaComponentProvider,
|
||||
Form,
|
||||
Action,
|
||||
CollectionProvider,
|
||||
CollectionField,
|
||||
} from '@nocobase/client';
|
||||
import 'antd/dist/antd.css';
|
||||
import { FormItem, Input } from '@formily/antd';
|
||||
|
||||
export default observer(() => {
|
||||
const collection = {
|
||||
name: 'tests',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title1',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: 'Title1',
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
required: true,
|
||||
description: 'description1',
|
||||
} as ISchema,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title2',
|
||||
interface: 'input',
|
||||
uiSchema: {
|
||||
title: 'Title2',
|
||||
type: 'string',
|
||||
'x-component': 'Input',
|
||||
description: 'description',
|
||||
default: 'ttt',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title3',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
form1: {
|
||||
type: 'void',
|
||||
'x-component': 'Form',
|
||||
properties: {
|
||||
// 字段 title1 直接使用全局提供的 uiSchema
|
||||
title1: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
default: '111',
|
||||
},
|
||||
// 等同于
|
||||
// title1: {
|
||||
// type: 'string',
|
||||
// title: 'Title',
|
||||
// required: true,
|
||||
// 'x-component': 'Input',
|
||||
// 'x-decorator': 'FormItem',
|
||||
// },
|
||||
title2: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Title4', // 覆盖全局已定义的 Title2
|
||||
required: true, // 扩展的配置参数
|
||||
description: 'description4',
|
||||
},
|
||||
// 等同于
|
||||
// title2: {
|
||||
// type: 'string',
|
||||
// title: 'Title22',
|
||||
// required: true,
|
||||
// 'x-component': 'Input',
|
||||
// 'x-decorator': 'FormItem',
|
||||
// },
|
||||
// 字段 title3 没有提供 uiSchema,自行处理
|
||||
title3: {
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
title: 'Title3',
|
||||
required: true,
|
||||
},
|
||||
action1: {
|
||||
// type: 'void',
|
||||
'x-component': 'Action',
|
||||
title: 'Submit',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
useAction: '{{ useSubmit }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const useSubmit = () => {
|
||||
const form = useForm();
|
||||
return {
|
||||
async run() {
|
||||
form.submit(() => {
|
||||
console.log(form.values);
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<SchemaComponentProvider scope={{ useSubmit }} components={{ Action, Form, CollectionField, Input, FormItem }}>
|
||||
<CollectionProvider collection={collection}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</CollectionProvider>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
});
|
||||
```
|
@ -0,0 +1,82 @@
|
||||
# Schema 组件库
|
||||
|
||||
## 包装器组件
|
||||
|
||||
- BlockItem
|
||||
- FormItem
|
||||
- CardItem
|
||||
|
||||
## 布局
|
||||
|
||||
- Page
|
||||
- Grid
|
||||
- Tabs
|
||||
- Space
|
||||
|
||||
## 字段组件
|
||||
|
||||
字段组件一般不单独使用,而是用在数据展示组件当中
|
||||
|
||||
- CollectionField:万能组件
|
||||
- Cascader
|
||||
- Checkbox
|
||||
- ColorSelect
|
||||
- DatePicker
|
||||
- Filter
|
||||
- Formula
|
||||
- IconPicker
|
||||
- Input
|
||||
- InputNumber
|
||||
- Markdown
|
||||
- Password
|
||||
- Percent
|
||||
- Radio
|
||||
- RecordPicker
|
||||
- RichText
|
||||
- Select
|
||||
- TimePicker
|
||||
- TreeSelect
|
||||
- Upload
|
||||
|
||||
## 数据展示组件
|
||||
|
||||
需要与字段组件搭配使用
|
||||
|
||||
- Calendar
|
||||
- Form
|
||||
- Kanban
|
||||
- Table
|
||||
- TableV2
|
||||
|
||||
## 操作(onClick 事件型组件)
|
||||
|
||||
- Action
|
||||
- Action.Drawer
|
||||
- Action.Modal
|
||||
- ActionBar:用于操作布局
|
||||
- Menu
|
||||
|
||||
## 其他
|
||||
|
||||
- G2plot
|
||||
- Markdown.Void
|
||||
|
||||
## `x-designer` 和 `x-initializer` 的使用场景
|
||||
|
||||
`x-decorator` 或 `x-component` 是以下组件时,`x-designer` 生效:
|
||||
|
||||
- BlockItem
|
||||
- CardItem
|
||||
- FormItem
|
||||
- Table.Column
|
||||
- Tabs.TabPane
|
||||
|
||||
`x-decorator` 或 `x-component` 是以下组件时,`x-initializer` 生效:
|
||||
|
||||
- ActionBar
|
||||
- BlockItem
|
||||
- CardItem
|
||||
- FormItem
|
||||
- Grid
|
||||
- Table
|
||||
- Tabs
|
107
docs/tr-TR/development/client/ui-schema-designer/demo1.tsx
Normal file
107
docs/tr-TR/development/client/ui-schema-designer/demo1.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { ArrayField } from '@formily/core';
|
||||
import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
import { Table, TableColumnType } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const ArrayTable = observer((props: any) => {
|
||||
const { rowKey } = props;
|
||||
const field = useField<ArrayField>();
|
||||
const schema = useFieldSchema();
|
||||
const columnSchemas = schema.reduceProperties((buf, s) => {
|
||||
if (s['x-component'] === 'ArrayTable.Column') {
|
||||
buf.push(s);
|
||||
}
|
||||
return buf;
|
||||
}, []);
|
||||
|
||||
const columns = columnSchemas.map((s) => {
|
||||
return {
|
||||
render: (value, record) => {
|
||||
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
|
||||
},
|
||||
} as TableColumnType<any>;
|
||||
});
|
||||
|
||||
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
|
||||
});
|
||||
|
||||
const Value = connect((props) => {
|
||||
return <li>value: {props.value}</li>;
|
||||
});
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
objArr: {
|
||||
type: 'array',
|
||||
default: [
|
||||
{ __path: '0', id: 1, value: 't1' },
|
||||
{
|
||||
__path: '1',
|
||||
id: 2,
|
||||
value: 't2',
|
||||
children: [
|
||||
{
|
||||
__path: '1.children.0',
|
||||
id: 5,
|
||||
value: 't5',
|
||||
parentId: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__path: '2',
|
||||
id: 3,
|
||||
value: 't3',
|
||||
children: [
|
||||
{
|
||||
__path: '2.children.0',
|
||||
id: 4,
|
||||
value: 't4',
|
||||
parentId: 3,
|
||||
children: [
|
||||
{
|
||||
__path: '2.children.0.children.0',
|
||||
id: 6,
|
||||
value: 't6',
|
||||
parentId: 4,
|
||||
},
|
||||
{
|
||||
__path: '2.children.0.children.1',
|
||||
id: 7,
|
||||
value: 't7',
|
||||
parentId: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
},
|
||||
properties: {
|
||||
c1: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ ArrayTable, Value }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
353
docs/tr-TR/development/client/ui-schema-designer/designable.md
Normal file
353
docs/tr-TR/development/client/ui-schema-designer/designable.md
Normal file
@ -0,0 +1,353 @@
|
||||
# Schema 的设计能力
|
||||
|
||||
Schema 的设计能力主要体现在
|
||||
|
||||
- 邻近位置插入,可用于
|
||||
- 插入新的 schema 节点
|
||||
- 现有 schema 节点的拖拽移动
|
||||
- schema 参数修改
|
||||
|
||||
设计器核心 API 和参数有:
|
||||
|
||||
- 设计器 API:`createDesignable()` & `useDesignable()`
|
||||
- Schema 参数:`x-designer`,用于适配设计器组件
|
||||
|
||||
## 设计器 API
|
||||
|
||||
### createDesignable()
|
||||
|
||||
```ts
|
||||
import { Schema } from '@nocobase/client';
|
||||
|
||||
const current = new Schema({
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
});
|
||||
|
||||
const {
|
||||
designable, // 是否可以配置
|
||||
remove,
|
||||
insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd
|
||||
insertBeforeBegin, // 在当前节点的前面插入
|
||||
insertAfterBegin, // 在当前节点的第一个子节点前面插入
|
||||
insertBeforeEnd, // 在当前节点的最后一个子节点后面
|
||||
insertAfterEnd, // 在当前节点的后面
|
||||
} = createDesignable({
|
||||
current,
|
||||
});
|
||||
|
||||
const newSchema = {
|
||||
type: 'void',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
};
|
||||
|
||||
insertAfterBegin(newSchema);
|
||||
|
||||
console.log(current.toJSON());
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'void',
|
||||
'x-component': 'Hello',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### useDesignable()
|
||||
|
||||
React Hook 场景也可以用 `useDesignable()` 获取当前 schema 组件设计器的 API
|
||||
|
||||
```ts
|
||||
const {
|
||||
designable, // 是否可以配置
|
||||
remove,
|
||||
insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd
|
||||
insertBeforeBegin, // 在当前节点的前面插入
|
||||
insertAfterBegin, // 在当前节点的第一个子节点前面插入
|
||||
insertBeforeEnd, // 在当前节点的最后一个子节点后面
|
||||
insertAfterEnd, // 在当前节点的后面
|
||||
} = useDesignable();
|
||||
|
||||
const schema = {
|
||||
name: uid(),
|
||||
'x-component': 'Hello',
|
||||
};
|
||||
|
||||
// 在当前节点的前面插入
|
||||
insertBeforeBegin(schema);
|
||||
// 等同于
|
||||
insertAdjacent('beforeBegin', schema);
|
||||
|
||||
// 在当前节点的第一个子节点前面插入
|
||||
insertAfterBegin(schema);
|
||||
// 等同于
|
||||
insertAdjacent('afterBegin', schema);
|
||||
|
||||
// 在当前节点的最后一个子节点后面
|
||||
insertBeforeEnd(schema);
|
||||
// 等同于
|
||||
insertAdjacent('beforeEnd', schema);
|
||||
|
||||
// 在当前节点的后面
|
||||
insertAfterEnd(schema);
|
||||
// 等同于
|
||||
insertAdjacent('afterEnd', schema);
|
||||
```
|
||||
|
||||
## 邻近位置插入
|
||||
|
||||
与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `insertAdjacent()` 方法用于解决邻近位置的插入问题。
|
||||
|
||||
四个邻近位置
|
||||
|
||||
```ts
|
||||
{
|
||||
properties: {
|
||||
// beforeBegin 在当前节点的前面插入
|
||||
node1: {
|
||||
properties: {
|
||||
// afterBegin 在当前节点的第一个子节点前面插入
|
||||
// ...
|
||||
// beforeEnd 在当前节点的最后一个子节点后面
|
||||
},
|
||||
},
|
||||
// afterEnd 在当前节点的后面
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
和 HTML 标签一样,Schema 组件库的组件也是可以相互组合,通过 insertAdjacent API 按实际需要插入在合理的邻近位置。
|
||||
|
||||
### 插入新的 schema 节点
|
||||
|
||||
在 Schema 组件里,可以直接通过 `useDesignable()` 在当前 Schema 的相邻位置插入新节点:
|
||||
|
||||
|
||||
示例
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client';
|
||||
import { observer, Schema, useFieldSchema } from '@formily/react';
|
||||
import { Button, Space } from 'antd';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
const Hello = observer((props) => {
|
||||
const { insertAdjacent } = useDesignable();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<div>
|
||||
<h1>{fieldSchema.name}</h1>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('beforeBegin', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
before begin
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('afterBegin', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
after begin
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('beforeEnd', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
before end
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('afterEnd', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
after end
|
||||
</Button>
|
||||
</Space>
|
||||
<div style={{ margin: 50 }}>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Page = observer((props) => {
|
||||
return <div>{props.children}</div>;
|
||||
});
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Page, Hello }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'page',
|
||||
'x-component': 'Page',
|
||||
properties: {
|
||||
hello1: {
|
||||
type: 'void',
|
||||
'x-component': 'Hello',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 现有 schema 节点的拖拽移动
|
||||
|
||||
insertAdjacent 等方法也可用于节点的拖拽移动
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core';
|
||||
import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client';
|
||||
|
||||
const useDragEnd = () => {
|
||||
const { refresh } = useDesignable();
|
||||
|
||||
return ({ active, over }: DragEndEvent) => {
|
||||
const activeSchema = active?.data?.current?.schema;
|
||||
const overSchema = over?.data?.current?.schema;
|
||||
|
||||
if (!activeSchema || !overSchema) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dn = createDesignable({
|
||||
current: overSchema,
|
||||
});
|
||||
|
||||
dn.on('insertAdjacent', refresh);
|
||||
dn.insertBeforeBeginOrAfterEnd(activeSchema);
|
||||
};
|
||||
};
|
||||
|
||||
const Page = observer((props) => {
|
||||
return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>;
|
||||
});
|
||||
|
||||
function Draggable(props) {
|
||||
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||
id: props.id,
|
||||
data: props.data,
|
||||
});
|
||||
const style = transform
|
||||
? {
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Droppable(props) {
|
||||
const { isOver, setNodeRef } = useDroppable({
|
||||
id: props.id,
|
||||
data: props.data,
|
||||
});
|
||||
const style = {
|
||||
color: isOver ? 'green' : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Block = observer((props) => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<Droppable id={field.address.toString()} data={{ schema: fieldSchema }}>
|
||||
<div style={{ marginBottom: 20, padding: '20px', background: '#f1f1f1' }}>
|
||||
Block {fieldSchema.name}{' '}
|
||||
<Draggable id={field.address.toString()} data={{ schema: fieldSchema }}>
|
||||
Drag
|
||||
</Draggable>
|
||||
</div>
|
||||
</Droppable>
|
||||
);
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Page, Block }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'page',
|
||||
'x-component': 'Page',
|
||||
properties: {
|
||||
block1: {
|
||||
'x-component': 'Block',
|
||||
},
|
||||
block2: {
|
||||
'x-component': 'Block',
|
||||
},
|
||||
block3: {
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## `x-designer` 的应用
|
||||
|
||||
`x-designer` 通常只在 BlockItem、CardItem、FormItem 等包装器组件中使用。
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: '标题',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-designer': 'FormItem.Designer',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
title: '状态',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-designer': 'FormItem.Designer',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
说明:NocoBase 提供的 Schema 设计器是以工具栏形式直接嵌入于界面,当激活界面配置时(`designable = true`),`x-designer` 组件(设计器工具栏)会显示出来,就可以通过工具栏更新当前 schema 组件了,工具栏提供的设计能力包括:
|
||||
|
||||
- 拖拽移动:DndContext + DragHandler
|
||||
- 插入新节点:SchemaInitializer
|
||||
- 参数配置:SchemaSettings
|
BIN
docs/tr-TR/development/client/ui-schema-designer/designable.png
Normal file
BIN
docs/tr-TR/development/client/ui-schema-designer/designable.png
Normal file
Binary file not shown.
@ -0,0 +1,518 @@
|
||||
# 扩展 Schema 组件
|
||||
|
||||
除了原生的 html 标签,开发也可以适配更多的自定义组件,用于丰富 Schema 组件库。
|
||||
|
||||
扩展组件时,常用的方法:
|
||||
|
||||
- [connect](https://react.formilyjs.org/api/shared/connect) 无侵入接入第三方组件,一般用于适配字段组件,和 [mapProps](https://react.formilyjs.org/api/shared/map-props)[、mapReadPretty](https://react.formilyjs.org/api/shared/map-read-pretty) 搭配使用
|
||||
- [observer](https://react.formilyjs.org/api/shared/observer) 当组件内部使用了 observable 对象,而你希望组件响应 observable 对象的变化时
|
||||
|
||||
## 最简单的扩展
|
||||
|
||||
直接将现成的 React 组件注册进来。
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* defaultShowCode: true
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const Hello = () => <h1>Hello, world!</h1>;
|
||||
|
||||
const schema = {
|
||||
type: 'void',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Hello }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 通过 connect 接入第三方组件
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* defaultShowCode: true
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Input } from 'antd'
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const ReadPretty = (props) => {
|
||||
return <div>{props.value}</div>
|
||||
};
|
||||
|
||||
const SingleText = connect(
|
||||
Input,
|
||||
mapProps((props, field) => {
|
||||
return {
|
||||
...props,
|
||||
suffix: '后缀',
|
||||
}
|
||||
}),
|
||||
mapReadPretty(ReadPretty),
|
||||
);
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
t1: {
|
||||
type: 'string',
|
||||
default: 'hello t1',
|
||||
'x-component': 'SingleText',
|
||||
},
|
||||
t2: {
|
||||
type: 'string',
|
||||
default: 'hello t2',
|
||||
'x-component': 'SingleText',
|
||||
'x-pattern': 'readPretty',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ SingleText }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 使用 observer 响应数据
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* defaultShowCode: true
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { connect, observer, useForm } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const SingleText = connect(Input);
|
||||
|
||||
const UsedObserver = observer((props) => {
|
||||
const form = useForm();
|
||||
return <div>UsedObserver: {form.values.t1}</div>
|
||||
});
|
||||
|
||||
const NotUsedObserver = (props) => {
|
||||
const form = useForm();
|
||||
return <div>NotUsedObserver: {form.values.t1}</div>
|
||||
};
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
t1: {
|
||||
type: 'string',
|
||||
'x-component': 'SingleText',
|
||||
},
|
||||
t2: {
|
||||
type: 'string',
|
||||
'x-component': 'UsedObserver',
|
||||
},
|
||||
t3: {
|
||||
type: 'string',
|
||||
'x-component': 'NotUsedObserver',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const components = {
|
||||
SingleText,
|
||||
UsedObserver,
|
||||
NotUsedObserver
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={components}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 嵌套的 Schema
|
||||
|
||||
- `props.children` 嵌套,适用于 void 和 object 类型的 properties,例子见 [void 和 object 类型 schema 的嵌套](#void-和-object-类型-schema-的嵌套)
|
||||
- `<RecursionField />` 自定义嵌套,所有类型都适用,例子见 [array 类型 schema 的嵌套](#array-类型-schema-的嵌套)
|
||||
|
||||
注意:
|
||||
|
||||
- 除了 void 和 object 类型以外的 schema 的 `properties` 无法直接通过 `props.children` 渲染,但是可以使用 `<RecursionField />` 解决嵌套问题
|
||||
- 仅 void 和 object 类型的 schema 可以与 onlyRenderProperties 使用
|
||||
|
||||
```tsx | pure
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
```
|
||||
|
||||
### void 和 object 类型 schema 的嵌套
|
||||
|
||||
直接通过 props.children 就可以适配 properties 节点了
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* defaultShowCode: true
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
// Hello 组件适配了 children,可以嵌套 properties 了
|
||||
const Hello = (props) => <h1>Hello, {props.children}!</h1>;
|
||||
const World = () => <span>world</span>;
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
properties: {
|
||||
world: {
|
||||
type: 'string',
|
||||
'x-component': 'World',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Hello, World }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
各类型 properties 渲染结果对比
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const Hello = (props) => <h1>Hello, {props.children}!</h1>;
|
||||
const World = () => <span>world</span>;
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title1: {
|
||||
type: 'void',
|
||||
'x-content': 'Void schema,渲染 properties',
|
||||
},
|
||||
void: {
|
||||
type: 'void',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
properties: {
|
||||
world: {
|
||||
type: 'void',
|
||||
'x-component': 'World',
|
||||
},
|
||||
},
|
||||
},
|
||||
title2: {
|
||||
type: 'void',
|
||||
'x-content': 'Object schema,渲染 properties',
|
||||
},
|
||||
object: {
|
||||
type: 'object',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
properties: {
|
||||
world: {
|
||||
type: 'string',
|
||||
'x-component': 'World',
|
||||
},
|
||||
},
|
||||
},
|
||||
title3: {
|
||||
type: 'void',
|
||||
'x-content': 'Array schema,不渲染 properties',
|
||||
},
|
||||
array: {
|
||||
type: 'array',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
properties: {
|
||||
world: {
|
||||
type: 'string',
|
||||
'x-component': 'World',
|
||||
},
|
||||
},
|
||||
},
|
||||
title4: {
|
||||
type: 'void',
|
||||
'x-content': 'String schema,不渲染 properties',
|
||||
},
|
||||
string: {
|
||||
type: 'string',
|
||||
name: 'hello',
|
||||
'x-component': 'Hello',
|
||||
properties: {
|
||||
world: {
|
||||
type: 'string',
|
||||
'x-component': 'World',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Hello, World }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### array 类型 schema 的嵌套
|
||||
|
||||
可以通过 `<RecursionField />` 解决自定义嵌套问题
|
||||
|
||||
#### Array 元素是 string 或 number 时
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const useValueSchema = () => {
|
||||
const schema = useFieldSchema();
|
||||
return schema.reduceProperties((buf, s) => {
|
||||
if (s['x-component'] === 'Value') {
|
||||
return s;
|
||||
}
|
||||
return buf;
|
||||
});
|
||||
};
|
||||
|
||||
const ArrayList = observer((props) => {
|
||||
const field = useField();
|
||||
const schema = useValueSchema();
|
||||
return (
|
||||
<>
|
||||
String Array
|
||||
<ul>
|
||||
{field.value?.map((item, index) => {
|
||||
// 只有一个元素
|
||||
return <RecursionField name={index} schema={schema} />
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const Value = connect((props) => {
|
||||
return <li>value: {props.value}</li>
|
||||
});
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
strArr: {
|
||||
type: 'array',
|
||||
default: [1, 2, 3],
|
||||
'x-component': 'ArrayList',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'number',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ ArrayList, Value }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Array 元素是 Object 时
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
|
||||
const ArrayList = observer((props) => {
|
||||
const field = useField();
|
||||
const schema = useFieldSchema();
|
||||
// array 类型的 schema 无法 onlyRenderProperties,需要转化为 object 类型
|
||||
const objSchema = new Schema({
|
||||
type: 'object',
|
||||
properties: schema.properties,
|
||||
});
|
||||
return (
|
||||
<ul>
|
||||
{field.value?.map((item, index) => {
|
||||
// array 元素是 object
|
||||
return (
|
||||
<RecursionField name={index} schema={objSchema} onlyRenderProperties />
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
});
|
||||
|
||||
const Value = connect((props) => {
|
||||
return <li>value: {props.value}</li>
|
||||
});
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
objArr: {
|
||||
type: 'array',
|
||||
default: [
|
||||
{ value: 't1' },
|
||||
{ value: 't2' },
|
||||
{ value: 't3' },
|
||||
],
|
||||
'x-component': 'ArrayList',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'number',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ ArrayList, Value }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### Tree 结构数据
|
||||
|
||||
```tsx
|
||||
import { ArrayField } from '@formily/core';
|
||||
import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
import { Table, TableColumnType } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const ArrayTable = observer((props: any) => {
|
||||
const { rowKey } = props;
|
||||
const field = useField<ArrayField>();
|
||||
const schema = useFieldSchema();
|
||||
const columnSchemas = schema.reduceProperties((buf, s) => {
|
||||
if (s['x-component'] === 'ArrayTable.Column') {
|
||||
buf.push(s);
|
||||
}
|
||||
return buf;
|
||||
}, []);
|
||||
|
||||
const columns = columnSchemas.map((s) => {
|
||||
return {
|
||||
render: (value, record) => {
|
||||
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
|
||||
},
|
||||
} as TableColumnType<any>;
|
||||
});
|
||||
|
||||
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
|
||||
});
|
||||
|
||||
const Value = connect((props) => {
|
||||
return <li>value: {props.value}</li>;
|
||||
});
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
objArr: {
|
||||
type: 'array',
|
||||
default: [
|
||||
{ __path: '0', id: 1, value: 't1' },
|
||||
{
|
||||
__path: '1',
|
||||
id: 2,
|
||||
value: 't2',
|
||||
children: [
|
||||
{
|
||||
__path: '1.children.0',
|
||||
id: 5,
|
||||
value: 't5',
|
||||
parentId: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__path: '2',
|
||||
id: 3,
|
||||
value: 't3',
|
||||
children: [
|
||||
{
|
||||
__path: '2.children.0',
|
||||
id: 4,
|
||||
value: 't4',
|
||||
parentId: 3,
|
||||
children: [
|
||||
{
|
||||
__path: '2.children.0.children.0',
|
||||
id: 6,
|
||||
value: 't6',
|
||||
parentId: 4,
|
||||
},
|
||||
{
|
||||
__path: '2.children.0.children.1',
|
||||
id: 7,
|
||||
value: 't7',
|
||||
parentId: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
},
|
||||
properties: {
|
||||
c1: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
'x-component': 'Value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ ArrayTable, Value }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
# Overview
|
||||
|
||||
<code src="./demo1.tsx" />
|
@ -0,0 +1,144 @@
|
||||
# 邻近位置插入
|
||||
|
||||
与 DOM 的 [insert adjacent](https://dom.spec.whatwg.org/#insert-adjacent) 概念相似,Schema 也提供了 `dn.insertAdjacent()` 方法用于解决邻近位置的插入问题。
|
||||
|
||||
## 邻近位置
|
||||
|
||||
```ts
|
||||
{
|
||||
properties: {
|
||||
// beforeBegin 在当前节点的前面插入
|
||||
node1: {
|
||||
properties: {
|
||||
// afterBegin 在当前节点的第一个子节点前面插入
|
||||
// ...
|
||||
// beforeEnd 在当前节点的最后一个子节点后面
|
||||
},
|
||||
},
|
||||
// afterEnd 在当前节点的后面
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## useDesignable()
|
||||
|
||||
获取当前 schema 节点设计器的 API
|
||||
|
||||
```ts
|
||||
const {
|
||||
designable, // 是否可以配置
|
||||
insertAdjacent, // 在某位置插入,四个位置:beforeBegin、afterBegin、beforeEnd、afterEnd
|
||||
insertBeforeBegin, // 在当前节点的前面插入
|
||||
insertAfterBegin, // 在当前节点的第一个子节点前面插入
|
||||
insertBeforeEnd, // 在当前节点的最后一个子节点后面
|
||||
insertAfterEnd, // 在当前节点的后面
|
||||
} = useDesignable();
|
||||
|
||||
const schema = {
|
||||
name: uid(),
|
||||
'x-component': 'Hello',
|
||||
};
|
||||
|
||||
// 在当前节点的前面插入
|
||||
insertBeforeBegin(schema);
|
||||
// 等同于
|
||||
insertAdjacent('beforeBegin', schema);
|
||||
|
||||
// 在当前节点的第一个子节点前面插入
|
||||
insertAfterBegin(schema);
|
||||
// 等同于
|
||||
insertAdjacent('afterBegin', schema);
|
||||
|
||||
// 在当前节点的最后一个子节点后面
|
||||
insertBeforeEnd(schema);
|
||||
// 等同于
|
||||
insertAdjacent('beforeEnd', schema);
|
||||
|
||||
// 在当前节点的后面
|
||||
insertAfterEnd(schema);
|
||||
// 等同于
|
||||
insertAdjacent('afterEnd', schema);
|
||||
```
|
||||
|
||||
示例
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client';
|
||||
import { observer, Schema, useFieldSchema } from '@formily/react';
|
||||
import { Button, Space } from 'antd';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
const Hello = observer((props) => {
|
||||
const { insertAdjacent } = useDesignable();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<div>
|
||||
<h1>{fieldSchema.name}</h1>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('beforeBegin', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
before begin
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('afterBegin', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
after begin
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('beforeEnd', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
before end
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
insertAdjacent('afterEnd', {
|
||||
'x-component': 'Hello',
|
||||
});
|
||||
}}
|
||||
>
|
||||
after end
|
||||
</Button>
|
||||
</Space>
|
||||
<div style={{ margin: 50 }}>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Page = observer((props) => {
|
||||
return <div>{props.children}</div>;
|
||||
});
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Page, Hello }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'page',
|
||||
'x-component': 'Page',
|
||||
properties: {
|
||||
hello1: {
|
||||
type: 'void',
|
||||
'x-component': 'Hello',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
}
|
||||
```
|
@ -0,0 +1,329 @@
|
||||
# What is UI Schema?
|
||||
|
||||
A protocol for describing front-end components, based on Formily Schema 2.0, JSON Schema-like style.
|
||||
|
||||
```ts
|
||||
interface ISchema {
|
||||
type: 'void' | 'string' | 'number' | 'object' | 'array';
|
||||
name?: string;
|
||||
title?: any;
|
||||
// wrapper component
|
||||
['x-decorator']? : string;
|
||||
// Wrapper component properties
|
||||
['x-decorator-props']? : any;
|
||||
// component
|
||||
['x-component']? : string;
|
||||
// Component properties
|
||||
['x-component-props']? : any;
|
||||
// display state, default is 'visible'
|
||||
['x-display']? : 'none' | 'hidden' | 'visible';
|
||||
// child node of the component, simply use
|
||||
['x-content']? : any;
|
||||
// children node schema
|
||||
properties?: Record<string, ISchema>;
|
||||
|
||||
// The following is used only for field components
|
||||
|
||||
// field linkage
|
||||
['x-reactions']? : SchemaReactions;
|
||||
// Field UI interaction mode, default is 'editable'
|
||||
['x-pattern']? : 'editable' | 'disabled' | 'readPretty';
|
||||
// Field validation
|
||||
['x-validator']? : Validator;
|
||||
// default data
|
||||
default: ? :any;
|
||||
|
||||
// Designer related
|
||||
|
||||
// Designer component (toolbar), including: drag and drop to move, insert new nodes, modify parameters, remove, etc.
|
||||
['x-designer']? : any;
|
||||
// Initializer component (toolbar), determines what can be inserted inside the current schema
|
||||
['x-initializer']? : any;
|
||||
}
|
||||
```
|
||||
|
||||
## The simplest component
|
||||
|
||||
All native html tags can be converted to schema writing. For example
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'h1',
|
||||
'x-content': 'Hello, world!
|
||||
}
|
||||
```
|
||||
|
||||
JSX examples
|
||||
|
||||
```tsx | pure
|
||||
<h1>Hello, world!</h1>
|
||||
```
|
||||
|
||||
## children components can be written in properties
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-component': 'input',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
<input name={'title'} />
|
||||
</div>
|
||||
```
|
||||
|
||||
## The clever use of Decorator
|
||||
|
||||
The combination of decorator + component allows you to put two components in a single schema node, reducing the complexity of the schema structure and increasing the reusability of the components.
|
||||
|
||||
For example, in a form scenario, you can combine a FormItem component with any field component, where the FormItem is the Decorator.
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
['x-component']: 'div',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div>
|
||||
<FormItem>
|
||||
<Input name={'title'} />
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Input.TextArea name={'content'} />
|
||||
</FormItem>
|
||||
</div>
|
||||
```
|
||||
|
||||
It is also possible to provide a CardItem component that wraps all blocks, so that all blocks are Card wrapped.
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
['x-component']: 'div',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Table',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Kanban',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div>
|
||||
<CardItem>
|
||||
<Table />
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Kanban />
|
||||
</CardItem>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Display state of the component
|
||||
|
||||
- `'x-display': 'visible'`: the component is displayed
|
||||
- `'x-display': 'hidden'`: component is hidden, data is not hidden
|
||||
- `'x-display': 'none'`: component is hidden, data is also hidden
|
||||
|
||||
### `'x-display': 'visible'`
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-component': 'input',
|
||||
'x-display': 'visible'
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
<input name={'title'} />
|
||||
</div>
|
||||
```
|
||||
|
||||
### `'x-display': 'hidden'`
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-component': 'input',
|
||||
'x-display': 'hidden'
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
{/* The input component is not output here, the corresponding field model with name=title still exists */}
|
||||
</div>
|
||||
```
|
||||
|
||||
### `'x-display': 'none'`
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
'x-component': 'input',
|
||||
'x-display': 'none'
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
{/* The input component is not output here, and the corresponding field model with name=title does not exist anymore */}
|
||||
</div>
|
||||
```
|
||||
|
||||
## Display modes for components
|
||||
|
||||
For field components, there are three display modes:
|
||||
|
||||
- `'x-pattern': 'editable'` Editable
|
||||
- `'x-pattern': 'disabled'` Non-editable
|
||||
- `'x-pattern': 'readPretty'` Friendly reading
|
||||
|
||||
As in the case of the `<SingleText />` component, the editable and disabled modes are `<input />` and the readPretty mode is `<div />`.
|
||||
|
||||
### `'x-pattern': 'editable'`
|
||||
|
||||
```ts
|
||||
const schema = {
|
||||
name: 'test',
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Hello',
|
||||
'x-component': 'SingleText',
|
||||
'x-pattern': 'editable'
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
<input name={'title'} value={'Hello'} />
|
||||
</div>
|
||||
```
|
||||
|
||||
### `'x-pattern': 'disabled'`
|
||||
|
||||
```ts
|
||||
const schema = {
|
||||
name: 'test',
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Hello',
|
||||
'x-component': 'SingleText',
|
||||
'x-pattern': 'disabled'
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
<input name={'title'} value={'Hello'} disabled />
|
||||
</div>
|
||||
```
|
||||
|
||||
### `'x-pattern': 'readPretty'`
|
||||
|
||||
```ts
|
||||
const schema = {
|
||||
name: 'test',
|
||||
type: 'void',
|
||||
'x-component': 'div',
|
||||
'x-component-props': { className: 'form-item' },
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Hello',
|
||||
'x-component': 'SingleText',
|
||||
'x-pattern': 'readPretty',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
JSX is equivalent to
|
||||
|
||||
```tsx | pure
|
||||
<div className={'form-item'}>
|
||||
<div>Hello</div>
|
||||
</div>
|
||||
```
|
@ -0,0 +1,61 @@
|
||||
# x-designer 组件
|
||||
|
||||
## 内置 x-designer 组件
|
||||
|
||||
- Action.Designer
|
||||
- Calendar.Designer
|
||||
- Filter.Action.Designer
|
||||
- Form.Designer
|
||||
- FormItem.Designer
|
||||
- FormV2.Designer
|
||||
- FormV2.ReadPrettyDesigner
|
||||
- DetailsDesigner
|
||||
- G2Plot.Designer
|
||||
- Kanban.Designer
|
||||
- Kanban.Card.Designer
|
||||
- Markdown.Void.Designer
|
||||
- Menu.Designer
|
||||
- TableV2.Column.Designer
|
||||
- TableV2.ActionColumnDesigner
|
||||
- TableBlockDesigner
|
||||
- TableSelectorDesigner
|
||||
- Tabs.Designer
|
||||
|
||||
## 替换
|
||||
|
||||
```tsx | pure
|
||||
import React, { useContext } from 'react';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
SchemaComponentOptions,
|
||||
GeneralSchemaDesigner,
|
||||
SchemaSettings,
|
||||
useCollection
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
const CustomActionDesigner = () => {
|
||||
const { name, title } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<GeneralSchemaDesigner title={title || name}>
|
||||
<SchemaSettings.Remove
|
||||
removeParentsIfNoChildren
|
||||
breakRemoveOn={{
|
||||
'x-component': 'Grid',
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo((props) => {
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
'Action.Designer': CustomActionDesigner,
|
||||
}}
|
||||
>{props.children}</SchemaComponentOptions>
|
||||
);
|
||||
});
|
||||
```
|
@ -0,0 +1,39 @@
|
||||
# x-initializer 组件
|
||||
|
||||
## 内置 x-initializer 组件
|
||||
|
||||
- BlockInitializers
|
||||
- CalendarActionInitializers
|
||||
- CreateFormBlockInitializers
|
||||
- CustomFormItemInitializers
|
||||
- DetailsActionInitializers
|
||||
- FormActionInitializers
|
||||
- FormItemInitializers
|
||||
- KanbanActionInitializers
|
||||
- ReadPrettyFormActionInitializers
|
||||
- ReadPrettyFormItemInitializers
|
||||
- RecordBlockInitializers
|
||||
- RecordFormBlockInitializers
|
||||
- SubTableActionInitializers
|
||||
- TableActionColumnInitializers
|
||||
- TableActionInitializers
|
||||
- TableColumnInitializers
|
||||
- TableSelectorInitializers
|
||||
- TabPaneInitializers
|
||||
|
||||
## 替换
|
||||
|
||||
```tsx |pure
|
||||
import React, { useContext } from 'react';
|
||||
import { SchemaInitializerContext } from '@nocobase/client';
|
||||
|
||||
export default React.memo((props) => {
|
||||
const items = useContext(SchemaInitializerContext);
|
||||
const BlockInitializers = {};
|
||||
return (
|
||||
<SchemaInitializerContext.Provider value={{ ...items, BlockInitializers }}>
|
||||
{props.children}
|
||||
</SchemaInitializerContext.Provider>
|
||||
);
|
||||
});
|
||||
```
|
61
docs/tr-TR/development/index.md
Normal file
61
docs/tr-TR/development/index.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Introduction
|
||||
|
||||
NocoBase adopts microkernel architecture, functions are extended in the form of plugins. Front and back ends are separated. Various plugin interfaces are provided, and plugins are divided by functional modules with pluggable features.
|
||||
|
||||
<img src="https://www.nocobase.com/images/NocoBaseMindMapLite.png" style="max-width: 800px;" >
|
||||
|
||||
The pluggable design reduces the coupling between modules and increases the reuse rate. As the plugin library continues to expand, common scenarios require only a combination of plugins to complete the base build. NocoBase's no-code platform, for example, is a combination of various plugins.
|
||||
|
||||
<img src="./index/pm-built-in.jpg" style="max-width: 800px;" />
|
||||
|
||||
## Plugin Manager
|
||||
|
||||
NocoBase provides a powerful plugin manager for managing plugins. The flow of the plugin manager is as follows
|
||||
|
||||
<img src="./index/pm-flow.svg" style="max-width: 580px;" />
|
||||
|
||||
No-code Users can manage the activation and deactivation of local plugins through the interface at
|
||||
|
||||
<img src="./index/pm-ui.jpg" style="max-width: 800px;" />
|
||||
|
||||
Developers can also manage the complete plugin process by way of the CLI:
|
||||
|
||||
```bash
|
||||
# Create the plugin
|
||||
yarn pm create hello
|
||||
# Register the plugin
|
||||
yarn pm add hello
|
||||
# Activate the plugin
|
||||
yarn pm enable hello
|
||||
# Disable the plugin
|
||||
yarn pm disable hello
|
||||
# Remove the plugin
|
||||
yarn pm remove hello
|
||||
```
|
||||
|
||||
For more plugin examples, see [packages/samples](https://github.com/nocobase/nocobase/tree/main/packages/samples).
|
||||
|
||||
## Extensibility
|
||||
|
||||
Whether it is generic functionality or personalization, it is recommended to write it as a plugin. NocoBase is extensible in all aspects.
|
||||
|
||||
- It can be user-intuitive interface-related page modules, block types, action types, field types, etc.
|
||||
- Filters, validators, access restrictions, etc. for enhancing or restricting the HTTP API
|
||||
- It can also be enhancements to underlying data tables, migrations, events, command lines, etc.
|
||||
|
||||
|
||||
Distribution of modules.
|
||||
|
||||
- Server
|
||||
- Collections & Fields: mainly used for system table configuration. Business tables are recommended to be configured in "Settings Center - Collection manager".
|
||||
- Resources & Actions: Mainly used to extend the Action API
|
||||
- Middleware: Middleware
|
||||
- Events: Events
|
||||
- I18n: server-side internationalization
|
||||
- Commands: Custom command lines
|
||||
- Migrations: Migration scripts
|
||||
- Client
|
||||
- UI Schema Designer: Page Designer
|
||||
- UI Router: When there is a need for custom pages
|
||||
- Settings Center: Provides configuration pages for plugins
|
||||
- I18n: Client side internationalization
|
1
docs/tr-TR/development/index/app-flow.svg
Normal file
1
docs/tr-TR/development/index/app-flow.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
BIN
docs/tr-TR/development/index/pm-built-in.jpg
Normal file
BIN
docs/tr-TR/development/index/pm-built-in.jpg
Normal file
Binary file not shown.
1
docs/tr-TR/development/index/pm-flow.svg
Normal file
1
docs/tr-TR/development/index/pm-flow.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 26 KiB |
BIN
docs/tr-TR/development/index/pm-ui.jpg
Normal file
BIN
docs/tr-TR/development/index/pm-ui.jpg
Normal file
Binary file not shown.
127
docs/tr-TR/development/learning-guide.md
Normal file
127
docs/tr-TR/development/learning-guide.md
Normal file
@ -0,0 +1,127 @@
|
||||
# 学习路线指南
|
||||
|
||||
## 1. 从安装运行 NocoBase 开始
|
||||
|
||||
**相关文档:<a href="/welcome/getting-started/installation" target="_blank">快速开始</a>**
|
||||
|
||||
主要命令包括:
|
||||
|
||||
下载
|
||||
|
||||
```bash
|
||||
yarn create/git clone
|
||||
yarn install
|
||||
```
|
||||
|
||||
安装
|
||||
|
||||
```bash
|
||||
yarn nocobase install
|
||||
```
|
||||
|
||||
运行
|
||||
|
||||
```bash
|
||||
# for development
|
||||
yarn dev
|
||||
|
||||
# for production
|
||||
yarn build
|
||||
yarn start
|
||||
```
|
||||
|
||||
## 2. 了解 NocoBase 平台提供的核心功能
|
||||
|
||||
**相关文档:<a href="/manual" target="_blank">使用手册</a>**
|
||||
|
||||
主要的三部分包括:
|
||||
|
||||
- UI 设计器:主要包括区块、字段和操作
|
||||
- 插件管理器:功能需求扩展
|
||||
- 配置中心:已激活插件提供的配置功能
|
||||
|
||||
## 3. 进一步了解插件管理器的使用
|
||||
|
||||
**相关文档:<a href="/development" target="_blank">插件开发</a>**
|
||||
|
||||
NocoBase 提供了简易的插件管理器界面,但是在界面上只能处理本地插件的 enable、disable 和 remove,完整的操作需要通过 CLI
|
||||
|
||||
```bash
|
||||
# 创建插件
|
||||
yarn pm create hello
|
||||
# 注册插件
|
||||
yarn pm add hello
|
||||
# 激活插件
|
||||
yarn pm enable hello
|
||||
# 禁用插件
|
||||
yarn pm disable hello
|
||||
# 删除插件
|
||||
yarn pm remove hello
|
||||
```
|
||||
|
||||
更多插件示例,查看 packages/samples,通过 samples 插件能够了解插件的基本用法,就可以进一步开发插件了。
|
||||
|
||||
## 4. 开发新插件,了解模块分布
|
||||
|
||||
**相关文档:<a href="/development/guide" target="_blank">扩展指南</a>**
|
||||
|
||||
[编写第一个插件](/development/your-fisrt-plugin) 章节,虽然简单的讲述了插件的主要开发流程,但是为了更快速的介入插件细节,你可能需要进一步了解 NocoBase 框架的模块分布:
|
||||
|
||||
- Server
|
||||
- Collections & Fields:主要用于系统表配置,业务表建议在「配置中心 - 数据表配置」里配置
|
||||
- Resources & Actions:主要用于扩展 Action API
|
||||
- Middleware:中间件
|
||||
- Events:事件
|
||||
- I18n:服务端国际化
|
||||
- Client
|
||||
- UI Schema Designer:页面设计器
|
||||
- UI Router:有自定义页面需求时
|
||||
- Settings Center:为插件提供配置页面
|
||||
- I18n:客户端国际化
|
||||
- Devtools
|
||||
- Commands:自定义命令行
|
||||
- Migrations:迁移脚本
|
||||
|
||||
## 5. 查阅各模块主要 API
|
||||
|
||||
**相关文档:<a href="/api" target="_blank">API 参考</a>**
|
||||
|
||||
查看各模块的 packages/samples,进一步了解模块主要 API 的用法
|
||||
|
||||
- Server
|
||||
- Collections & Fields
|
||||
- db.collection
|
||||
- db.import
|
||||
- Resources & Actions
|
||||
- app.resourcer.define
|
||||
- app.resourcer.registerActions
|
||||
- Middleware
|
||||
- app.use
|
||||
- app.acl.use
|
||||
- app.resourcer.use
|
||||
- Events
|
||||
- app.on
|
||||
- app.db.on
|
||||
- I18n
|
||||
- app.i18n
|
||||
- ctx.i18n
|
||||
- Client
|
||||
- UI Schema Designer
|
||||
- SchemaComponent
|
||||
- SchemaInitializer
|
||||
- SchemaSettings
|
||||
- UI Router
|
||||
- RouteSwitchProvider
|
||||
- RouteSwitch
|
||||
- Settings Center
|
||||
- SettingsCenterProvider
|
||||
- I18n
|
||||
- app.i18n
|
||||
- useTranslation
|
||||
- Devtools
|
||||
- Commands
|
||||
- app.command
|
||||
- app.findCommand
|
||||
- Migrations
|
||||
- app.db.addMigration
|
||||
- app.db.addMigrations
|
41
docs/tr-TR/development/life-cycle.md
Normal file
41
docs/tr-TR/development/life-cycle.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Life cycle
|
||||
|
||||
## Lifecycle of applications
|
||||
|
||||
<img src="./index/app-flow.svg" style="max-width: 380px;" />
|
||||
|
||||
## Lifecycle of plugins
|
||||
|
||||
<img src="./index/pm-flow.svg" style="max-width: 600px;" />
|
||||
|
||||
## Lifecycle methods for plugins
|
||||
|
||||
```ts
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
|
||||
export class MyPlugin extends Plugin {
|
||||
afterAdd() {
|
||||
// After the plugin pm.add is registered. Mainly used to place the app.beforeLoad event.
|
||||
beforeLoad() { }
|
||||
beforeLoad() {
|
||||
// Before all plugins are loaded. Generally used for registering classes and event listeners
|
||||
}
|
||||
async load() {
|
||||
// Load configuration
|
||||
}
|
||||
async install(options?: InstallOptions) {
|
||||
// Logic for installing
|
||||
}
|
||||
async afterEnable() {
|
||||
// After activation
|
||||
}
|
||||
async afterDisable() {
|
||||
// After disable
|
||||
}
|
||||
async remove() {
|
||||
// Logic for removing
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPlugin;
|
||||
```
|
1
docs/tr-TR/development/others/build.md
Normal file
1
docs/tr-TR/development/others/build.md
Normal file
@ -0,0 +1 @@
|
||||
# Building
|
163
docs/tr-TR/development/others/testing.md
Normal file
163
docs/tr-TR/development/others/testing.md
Normal file
@ -0,0 +1,163 @@
|
||||
# 单元测试
|
||||
|
||||
## 介绍
|
||||
|
||||
NocoBase 的测试基于 [Jest](https://jestjs.io/) 测试框架。同时,为了方便的编写测试,我们提供了两个工具类,在测试环境模拟正常的数据库和应用的服务端。
|
||||
|
||||
### MockDatabase
|
||||
|
||||
模拟数据库类继承自 [`Database`](/api/database) 类,大部分内容没有区别,主要在构造函数默认内置了随机表前缀,在每个测试用例初始化数据库时相关数据表都通过前缀名称与其他用例进行隔离,在运行测试用例时互不影响。
|
||||
|
||||
```ts
|
||||
import { MockDatabase } from '@nocobase/test';
|
||||
|
||||
describe('my suite', () => {
|
||||
let db;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = new MockDatabase();
|
||||
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
});
|
||||
|
||||
test('my case', async () => {
|
||||
const postRepository = db.getRepository('posts');
|
||||
const p1 = await postRepository.create({
|
||||
values: {
|
||||
title: 'hello'
|
||||
}
|
||||
});
|
||||
|
||||
expect(p1.get('title')).toEqual('hello');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### MockServer
|
||||
|
||||
模拟服务器也继承自 [Application](/api/server/application) 类,除了内置的数据库实例是通过模拟数据库类生成的以外,还提供了比较方便的生成基于 [superagent](https://www.npmjs.com/package/superagent) 请求代理功能,针对从发送请求到获取响应的写法也集成了 `.resource('posts').create()`,比较简化。
|
||||
|
||||
```ts
|
||||
import { mockServer } from '@nocobase/test';
|
||||
|
||||
describe('my suite', () => {
|
||||
let app;
|
||||
let agent;
|
||||
let db;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = mockServer();
|
||||
agent = app.agent();
|
||||
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await db.sync();
|
||||
await app.load();
|
||||
});
|
||||
|
||||
test('my case', async () => {
|
||||
const { body } = await agent.resource('posts').create({
|
||||
values: {
|
||||
title: 'hello'
|
||||
}
|
||||
});
|
||||
|
||||
expect(body.data.title).toEqual('hello');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 示例
|
||||
|
||||
我们以之前在 [资源与操作](development/guide/resources-actions) 章节的功能为例,来写一个插件的测试:
|
||||
|
||||
```ts
|
||||
import { mockServer } from '@nocobase/test';
|
||||
import Plugin from '../../src/server';
|
||||
|
||||
describe('shop actions', () => {
|
||||
let app;
|
||||
let agent;
|
||||
let db;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = mockServer();
|
||||
app.plugin(Plugin);
|
||||
agent = app.agent();
|
||||
db = app.db;
|
||||
|
||||
await app.load();
|
||||
await db.sync();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
test('product order case', async () => {
|
||||
const { body: product } = await agent.resource('products').create({
|
||||
values: {
|
||||
title: 'iPhone 14 Pro',
|
||||
price: 7999,
|
||||
enabled: true,
|
||||
inventory: 1
|
||||
}
|
||||
});
|
||||
expect(product.data.price).toEqual(7999);
|
||||
|
||||
const { body: order } = await agent.resource('orders').create({
|
||||
values: {
|
||||
productId: product.data.id
|
||||
}
|
||||
});
|
||||
expect(order.data.totalPrice).toEqual(7999);
|
||||
expect(order.data.status).toEqual(0);
|
||||
|
||||
const { body: deliveredOrder } = await agent.resource('orders').deliver({
|
||||
filterByTk: order.data.id,
|
||||
values: {
|
||||
provider: 'SF',
|
||||
trackingNumber: '123456789'
|
||||
}
|
||||
});
|
||||
expect(deliveredOrder.data.status).toBe(2);
|
||||
expect(deliveredOrder.data.delivery.trackingNumber).toBe('123456789');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
编写完成后,在命令行中允许测试命令:
|
||||
|
||||
```bash
|
||||
yarn test packages/samples/shop-actions
|
||||
```
|
||||
|
||||
该测试将验证:
|
||||
|
||||
1. 商品可以创建成功;
|
||||
2. 订单可以创建成功;
|
||||
3. 订单可以发货成功;
|
||||
|
||||
当然这只是个最基本的例子,从业务上来说并不完善,但作为示例已经可以说明整个测试的流程。
|
||||
|
||||
## 小结
|
||||
|
||||
本章涉及的示例代码整合在对应的包 [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions) 中,可以直接在本地运行,查看效果。
|
17
docs/tr-TR/development/plugin-ds.md
Normal file
17
docs/tr-TR/development/plugin-ds.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Plugin directory structure
|
||||
|
||||
An empty plugin can be created quickly with `yarn pm create my-plugin`, with the following directory structure.
|
||||
|
||||
```bash
|
||||
|- /my-plugin
|
||||
|- /src
|
||||
|- /client # client-side of the plugin
|
||||
|- /server # server-side of the plugin
|
||||
|- client.d.ts
|
||||
|- client.js
|
||||
|- package.json # plugin package information
|
||||
|- server.d.ts
|
||||
|- server.js
|
||||
```
|
||||
|
||||
The tutorial for `/src/server` refers to the [server](./server) section, and the tutorial for `/src/client` refers to the [client](./client) section.
|
319
docs/tr-TR/development/server/collections-fields.md
Normal file
319
docs/tr-TR/development/server/collections-fields.md
Normal file
@ -0,0 +1,319 @@
|
||||
## Collections and Fields
|
||||
|
||||
## Basic Concepts
|
||||
|
||||
Data modeling is the lowest level foundation of an application. In NocoBase applications we model data through data tables (Collection) and fields (Field), and the modeling is also mapped to database tables for persistence.
|
||||
|
||||
### Collection
|
||||
|
||||
Collection is a collection of all similar data, which corresponds to the concept of database tables in NocoBase. Such as orders, products, users, comments, etc. can form a collection definition. Different collections are distinguished by name and contain fields defined by `fields`, such as
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{ name: 'title', type: 'string' }
|
||||
{ name: 'content', type: 'text' },
|
||||
// ...
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
The collection is only in memory after the definition, you need to call the [``db.sync()`'' (/api/database#sync) method to synchronize it to the database.
|
||||
|
||||
### Field
|
||||
|
||||
Corresponding to the concept of database table "fields", each data table (Collection) can have a number of Fields, for example.
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'string', name: 'name' }
|
||||
{ type: 'integer', name: 'age' }
|
||||
// Other fields
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
The field name (`name`) and field type (`type`) are required, and different fields are distinguished by the field name (`name`). All field types and their configurations are described in the [List of built-in field types](/api/database/field#List of built-in field types) section of the API reference.
|
||||
|
||||
## Example
|
||||
|
||||
For developers, we usually build functional collections that are different from normal collections and solidify these collections as part of the plugin and combine them with other data processing processes to form a complete functionality.
|
||||
|
||||
Let's take a simple online store plugin as an example to show how to model and manage the collections of the plugin. Assuming you have already learned about [Develop your first plugin](/development/your-first-plugin), we continue to build on the previous plugin code, except that the name of the plugin is changed from `hello` to `shop-modeling`.
|
||||
|
||||
### Define and create collections in the plugin
|
||||
|
||||
For a store, you first need to create a collection of products, named `products`. Instead of calling [`db.collection()`](/api/database#collection) directly, in the plugin we will use a more convenient method to import multiple files of defined data tables at once. So let's start by creating a file for the product collection definition named ``collections/products.ts`` and fill it with the following content.
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'products',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'price'
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'enabled'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'inventory'
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
As you can see, the collections structure definition can be used directly in standard JSON format, where `name` and `fields` are required representing the collection's name and the field definitions in the collection. Field definitions similar to Sequelize create system fields such as primary key (`id`), data creation time (`createdAt`) and data update time (`updatedAt`) by default, which can be overridden by a configuration with the same name if there is a special need.
|
||||
|
||||
The data table defined in this file we can introduce and complete the definition in the `load()` cycle of the main plugin class using `db.import()`. This is shown below.
|
||||
|
||||
```ts
|
||||
import path from 'path';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
await this.db.import({
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.app.acl.allow('products', '*');
|
||||
this.app.acl.allow('categories', '*');
|
||||
this.app.acl.allow('orders', '*');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the meantime, for testing purposes, we will temporarily allow all access permissions for the data in these collections, and later we will detail how to manage data permissions in [Permissions Management](/development/guide/acl).
|
||||
|
||||
This way, when the plugin is loaded by the main application, the `products` collection we defined is also loaded into the memory of the database management instance. At the same time, the NocoBase constraint-based resource mapping of the collections automatically generates the corresponding CRUD HTTP API after the application's service is started.
|
||||
|
||||
When the following URLs are requested from the client, the corresponding responses are obtained.
|
||||
|
||||
* `GET /api/products:list`: Get a list of all product data
|
||||
* `GET /api/products:get?filterByTk=<id>`: Get the product data for the specified ID
|
||||
* `POST /api/products`: Create a new product data
|
||||
* `PUT /api/products:update?filterByTk=<id>`: Update a product data
|
||||
* `DELETE /api/products:destroy?filterByTk=<id>`: Delete a product data
|
||||
|
||||
### Defining associated collections and fields
|
||||
|
||||
In the above example, we only defined a product collection, but in reality a product also needs to be associated to a category, a brand, a supplier, etc. For example, we can define a `categories` collection to store the categories, and then add a `category` field to the product collection to associate it with the category collection.
|
||||
|
||||
Add a new file `collections/categories.ts` and fill in the content.
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'categories',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title'
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'products',
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
We have defined two fields for the `categories` collection, one for the title and another one-to-many field for all the products associated under that category, which will be described later. Since we have already used the `db.import()` method in the plugin's main class to import all the data table definitions under the `collections` directory, the new `categories` collection added here will also be automatically imported into the database management instance.
|
||||
|
||||
Modify the file `collections/products.ts`` to add a `category` field to the `fields`.
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'products',
|
||||
fields: [
|
||||
// ...
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'category',
|
||||
target: 'categories',
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, the `category` field we added to the `products` collection is a `belongsTo` type field, and its `target` property points to the `categories` collection, thus defining a many-to-one relationship between the `products` collection and the `categories` collection. Combined with the `hasMany` field defined in the `categories` collection, we can achieve a relationship where one product can be associated to multiple categories and multiple products under one category. Usually `belongsTo` and `hasMany` can appear in pairs, defined in two separate collections.
|
||||
|
||||
Once the relationship between the two collections is defined, we can also request the associated data directly through the HTTP API
|
||||
|
||||
* `GET /api/products:list?appends=category`: Get all products data, including the associated categories data
|
||||
* `GET /api/products:get?filterByTk=<id>&appends=category`: Get the product data for the specified ID, including the associated category data.
|
||||
* `GET /api/categories/<categoryId>/products:list`: Get all the products under the specified category
|
||||
* `POST /api/categories/<categoryId>/products`: Create a new product under the specified category
|
||||
|
||||
Similar to the general ORM framework, NocoBase has four built-in relational field types, for more information you can refer to the section about API field types.
|
||||
|
||||
* [`belongsTo` type](/api/database/field#belongsto)
|
||||
* [`belongsToMany` type](/api/database/field#belongstomany)
|
||||
* [`hasMany` type](/api/database/field#hasmany)
|
||||
* [`hasOne` type](/api/database/field#hasone)
|
||||
|
||||
### Extend an existing collection
|
||||
|
||||
In the above example, we already have a product collection and a category collection, in order to provide the sales process we also need an order collection. We can add a new `orders.ts` file to the `collections` directory and define an `orders` collection as follows
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'orders',
|
||||
fields: [
|
||||
{
|
||||
type: 'uuid',
|
||||
name: 'id',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'product'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'quantity'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'totalPrice'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'status'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'address'
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For the sake of simplicity, the association between the order collection and the product collection we simply define as a many-to-one relationship, while in the actual business may be used in a complex modeling approach such as many-to-many or snapshot. As you can see, an order in addition to corresponding to a commodity, we also added a relationship definition corresponding to the users, which is a collection managed by the NocoBase built-in plugins (refer to [code for users plugin](https://github.com/nocobase/nocobase/tree/main/packages/) for details plugins/users)). If we want to extend the definition of the "multiple orders owned by a user" relationship for the existing users collection, we can continue to add a new collection file `collections/users.ts` inside the current shop-modeling plugin, which is different from exporting the JSON collection directly. Unlike the direct export of a JSON, the `@nocobase/database` package's `extend()` method is used here to extend the definition of an existing collection: ``ts
|
||||
|
||||
```ts
|
||||
import { extend } from '@nocobase/database';
|
||||
|
||||
export extend({
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'orders'
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
This way, the existing users table also has an `orders` associated field, and we can retrieve all the order data for a given user via `GET /api/users/<userId>/orders:list`.
|
||||
|
||||
This method is very useful when extending collections already defined by other plugins, so that other existing plugins do not depend on the new plugin in reverse, but only form one-way dependencies, facilitating a certain degree of decoupling at the extension level.
|
||||
|
||||
### Extend field types
|
||||
|
||||
We use `uuid` type for `id` field when we define order table, which is a built-in field type. Sometimes we may feel that UUID looks too long and waste space, and the query performance is not good, we want to use a more suitable field type, such as a complex numbering logic with date information, or Snowflake algorithm, we need to extend a custom field type.
|
||||
|
||||
Suppose we need to apply the Snowflake ID generation algorithm directly to extend a ``snowflake`` field type, we can create a ``fields/snowflake.ts`` file.
|
||||
|
||||
```ts
|
||||
// Import the algorithm toolkit
|
||||
import { Snowflake } from 'nodejs-snowflake';
|
||||
// Import field type base class
|
||||
import { DataTypes, Field, BaseColumnFieldOptions } from '@nocobase/database';
|
||||
|
||||
export interface SnowflakeFieldOptions extends BaseColumnFieldOptions {
|
||||
type: 'snowflake';
|
||||
epoch: number;
|
||||
instanceId: number;
|
||||
}
|
||||
|
||||
export class SnowflakeField extends Field {
|
||||
get dataType() {
|
||||
return DataTypes.BIGINT;
|
||||
}
|
||||
|
||||
constructor(options: SnowflakeFieldOptions, context) {
|
||||
super(options, context);
|
||||
|
||||
const {
|
||||
epoch: custom_epoch,
|
||||
instanceId: instance_id = process.env.INSTANCE_ID ? Number.parseInt(process.env.INSTANCE_ID) : 0,
|
||||
} = options;
|
||||
this.generator = new Snowflake({ custom_epoch, instance_id });
|
||||
}
|
||||
|
||||
setValue = (instance) => {
|
||||
const { name } = this.options;
|
||||
instance.set(name, this.generator.getUniqueID());
|
||||
};
|
||||
|
||||
bind() {
|
||||
super.bind();
|
||||
this.on('beforeCreate', this.setValue);
|
||||
}
|
||||
|
||||
unbind() {
|
||||
super.unbind();
|
||||
this.off('beforeCreate', this.setValue);
|
||||
}
|
||||
}
|
||||
|
||||
export default SnowflakeField;
|
||||
```
|
||||
|
||||
Afterwards, register the new field type into the collection in the main plugin file.
|
||||
|
||||
```ts
|
||||
import SnowflakeField from '. /fields/snowflake';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
initialize() {
|
||||
// ...
|
||||
this.db.registerFieldTypes({
|
||||
snowflake: SnowflakeField
|
||||
});
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows us to use the `snowflake` field type in the order table:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'orders',
|
||||
fields: [
|
||||
{
|
||||
type: 'snowflake'
|
||||
name: 'id',
|
||||
primaryKey: true
|
||||
},
|
||||
// ... . other fields
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
With the above example, we basically understand how to model data in a plugin, including.
|
||||
|
||||
* Defining collections and common fields
|
||||
* Defining association collections and fields relationships
|
||||
* Extending fields of an existing collections
|
||||
* Extending new field types
|
||||
|
||||
We have put the code covered in this chapter into a complete sample package [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling), which can be run directly locally to see the results.
|
||||
|
153
docs/tr-TR/development/server/collections/association-fields.md
Normal file
153
docs/tr-TR/development/server/collections/association-fields.md
Normal file
@ -0,0 +1,153 @@
|
||||
# Association Fields
|
||||
|
||||
In a relational database, the standard way to build a table relationship is to add a foreign key field followed by a foreign key constraint. For example, Knex builds a table with the following example.
|
||||
|
||||
```ts
|
||||
knex.schema.table('posts', function (table) {
|
||||
table.integer('userId').unsigned();
|
||||
table.foreign('userId').references('users.id');
|
||||
});
|
||||
```
|
||||
|
||||
This procedure creates a userId field in the posts table and sets the foreign key constraint posts.userId to refer to users.id. In NocoBase's Collection, such a relational constraint is created by configuring the relational field, e.g.
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'user',
|
||||
target: 'users',
|
||||
foreignKey: 'userId',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Relationship parameters
|
||||
|
||||
### BelongsTo
|
||||
|
||||
```ts
|
||||
interface BelongsTo {
|
||||
type: 'belongsTo';
|
||||
name: string;
|
||||
// defaults to name's plural
|
||||
target?: string;
|
||||
// The default value is the primary key of the target model, usually 'id'
|
||||
targetKey?: any;
|
||||
// defaults to target + 'Id'
|
||||
foreignKey?: any;
|
||||
}
|
||||
|
||||
// The authors table's primary key id is concatenated with the books table's foreign key authorId
|
||||
{
|
||||
name: 'books',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'author',
|
||||
target: 'authors',
|
||||
targetKey: 'id', // authors table's primary key
|
||||
foreignKey: 'authorId', // foreign key in books table
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### HasOne
|
||||
|
||||
```ts
|
||||
interface HasOne {
|
||||
type: 'hasOne';
|
||||
name: string;
|
||||
// defaults to name's plural
|
||||
target?: string;
|
||||
// The default value is the primary key of the source model, usually 'id'
|
||||
sourceKey?: string;
|
||||
// default value is the singular form of source collection name + 'Id'
|
||||
foreignKey?: string;
|
||||
foreignKey?}
|
||||
|
||||
// The users table's primary key id is concatenated with the profiles' foreign key userId
|
||||
{
|
||||
name: 'users',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasOne',
|
||||
name: 'profile',
|
||||
target: 'profiles',
|
||||
sourceKey: 'id', // users table's primary key
|
||||
foreignKey: 'userId', // foreign key in profiles table
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### HasMany
|
||||
|
||||
```ts
|
||||
interface HasMany {
|
||||
type: 'hasMany';
|
||||
name: string;
|
||||
// defaults to name
|
||||
target?: string;
|
||||
// The default value is the primary key of the source model, usually 'id'
|
||||
sourceKey?: string;
|
||||
// the default value is the singular form of the source collection name + 'Id'
|
||||
foreignKey?: string;
|
||||
}
|
||||
|
||||
// The posts table's primary key id is concatenated with the comments table's postId
|
||||
{
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'comments',
|
||||
target: 'comments',
|
||||
sourceKey: 'id', // posts table's primary key
|
||||
foreignKey: 'postId', // foreign key in the comments table
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### BelongsToMany
|
||||
|
||||
```ts
|
||||
interface BelongsToMany {
|
||||
type: 'belongsToMany';
|
||||
name: string;
|
||||
// default value is name
|
||||
target?: string;
|
||||
// defaults to the source collection name and target in the natural order of the first letter of the string
|
||||
through?: string;
|
||||
// defaults to the singular form of source collection name + 'Id'
|
||||
foreignKey?: string;
|
||||
// The default value is the primary key of the source model, usually id
|
||||
sourceKey?: string;
|
||||
// the default value is the singular form of target + 'Id'
|
||||
otherKey?: string;
|
||||
// the default value is the primary key of the target model, usually id
|
||||
targetKey?: string;
|
||||
}
|
||||
|
||||
// tags table's primary key, posts table's primary key and posts_tags two foreign keys are linked
|
||||
{
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'believesToMany',
|
||||
name: 'tags',
|
||||
target: 'tags',
|
||||
through: 'posts_tags', // intermediate table
|
||||
foreignKey: 'tagId', // foreign key 1, in posts_tags table
|
||||
otherKey: 'postId', // foreignKey2, in posts_tags table
|
||||
targetKey: 'id', // tags table's primary key
|
||||
sourceKey: 'id', // posts table's primary key
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
1
docs/tr-TR/development/server/collections/cm.svg
Normal file
1
docs/tr-TR/development/server/collections/cm.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.2 KiB |
@ -0,0 +1,82 @@
|
||||
# Collection templates
|
||||
|
||||
<Alert>
|
||||
📢 Collection templates are scheduled to be available in Q4 2022.
|
||||
</Alert>
|
||||
|
||||
In real business scenarios, different collections may have their own initialization rules and business logic, and NocoBase addresses such issues by providing collection templates.
|
||||
|
||||
## General collections
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
}
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Tree structure collections
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'categories',
|
||||
tree: 'adjacency-list',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'parent',
|
||||
target: 'categories',
|
||||
foreignKey: 'parentId',
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'children',
|
||||
target: 'categories',
|
||||
foreignKey: 'parentId',
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Parent-child inheritance collections
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'a',
|
||||
fields: [
|
||||
|
||||
],
|
||||
});
|
||||
|
||||
db.collection({
|
||||
name: 'b',
|
||||
inherits: 'a',
|
||||
fields: [
|
||||
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## More templates
|
||||
|
||||
As in the case of calendar collections, each initialized collection needs to be initialized with special cron and exclude fields, and the definition of such fields is done by the template
|
||||
|
||||
```ts
|
||||
db.collection({
|
||||
name: 'events',
|
||||
template: 'calendar',
|
||||
});
|
||||
```
|
62
docs/tr-TR/development/server/collections/configure.md
Normal file
62
docs/tr-TR/development/server/collections/configure.md
Normal file
@ -0,0 +1,62 @@
|
||||
# How to configure collections?
|
||||
|
||||
NocoBase has three ways to configure collections.
|
||||
|
||||
<img src="./cm.svg" style="max-width: 800px;" />
|
||||
|
||||
## Configuring collections through the interface
|
||||
|
||||
Business data is generally recommended to be configured using the interface, and the NocoBase platform provides two interfaces to configure collections.
|
||||
|
||||
### Regular table interface
|
||||
|
||||
<img src="./table.jpg" style="max-width: 800px;" />
|
||||
|
||||
### Graphical configuration interface
|
||||
|
||||
<img src="./graph.jpg" style="max-width: 800px;" />
|
||||
|
||||
## Defined in the plugin code
|
||||
|
||||
Generally used to configure plugin functions or system configuration tables where users can read and write data, but cannot modify the data structure.
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
load() {
|
||||
this.db.collection();
|
||||
this.db.import();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Related API Reference
|
||||
|
||||
- [db.collection()](/api/database#collection)
|
||||
- [db.import()](/api/database#import)
|
||||
|
||||
The collection configured in the plugin is automatically synchronized with the database when the plugin is activated, giving birth to the corresponding data tables and fields.
|
||||
|
||||
## Managing data tables via REST API
|
||||
|
||||
Third parties can also manage data tables via the HTTP interface (permissions required)
|
||||
|
||||
### Collections
|
||||
|
||||
```bash
|
||||
GET /api/collections
|
||||
POST /api/collections
|
||||
GET /api/collections/<collectionName>
|
||||
PUT /api/collections/<collectionName>
|
||||
DELETE /api/collections/<collectionName>
|
||||
```
|
||||
|
||||
### Collection fields
|
||||
|
||||
```bash
|
||||
GET /api/collections/<collectionName>/fields
|
||||
POST /api/collections/<collectionName>/fields
|
||||
GET /api/collections/<collectionName>/fields/<fieldName>
|
||||
PUT /api/collections/<collectionName>/fields/<fieldName>
|
||||
DELETE /api/collections/<collectionName>/fields/<fieldName>
|
||||
```
|
||||
|
39
docs/tr-TR/development/server/collections/field-extension.md
Normal file
39
docs/tr-TR/development/server/collections/field-extension.md
Normal file
@ -0,0 +1,39 @@
|
||||
# How to extend fields
|
||||
|
||||
The composition of a Collection Field in NocoBase consists of
|
||||
|
||||
<img src="./collection-field.svg" />
|
||||
|
||||
## Extend Field Type
|
||||
|
||||
For example, to extend the password type field ``type: 'password'`
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
beforeLoad() {
|
||||
this.db.registerFieldTypes({
|
||||
password: PasswordField
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class PasswordField extends Field {
|
||||
get dataType() {
|
||||
return DataTypes.STRING;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [More implementations of the built-in field types can be found here](https://github.com/nocobase/nocobase/tree/main/packages/core/database/src/fields)
|
||||
- Also see the full samples plugin [packages/samples/shop-modeling](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-modeling)
|
||||
|
||||
## Extend Field Component
|
||||
|
||||
Related extension documentation can be found at
|
||||
|
||||
- [Extending Schema Components](/development/client/ui-schema-designer/extending-schema-components)
|
||||
- [Schema component library](/development/client/ui-schema-designer/component-library)
|
||||
|
||||
## Extend Field Interface
|
||||
|
||||
- [Built-in field interfaces view here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces)
|
BIN
docs/tr-TR/development/server/collections/graph.jpg
Normal file
BIN
docs/tr-TR/development/server/collections/graph.jpg
Normal file
Binary file not shown.
194
docs/tr-TR/development/server/collections/index.md
Normal file
194
docs/tr-TR/development/server/collections/index.md
Normal file
@ -0,0 +1,194 @@
|
||||
# Core concepts
|
||||
|
||||
## Collection
|
||||
|
||||
Collection is a collection of all kinds of data, such as orders, products, users, comments, etc. Different collections are distinguished by name, e.g.
|
||||
|
||||
```ts
|
||||
// Orders
|
||||
{
|
||||
name: 'orders',
|
||||
}
|
||||
// Products
|
||||
{
|
||||
name: 'products',
|
||||
}
|
||||
// Users
|
||||
{
|
||||
name: 'users',
|
||||
}
|
||||
// Comments
|
||||
{
|
||||
name: 'comments',
|
||||
}
|
||||
```
|
||||
|
||||
## Collection Field
|
||||
|
||||
Each Collection has a number of Fields.
|
||||
|
||||
```ts
|
||||
// Collection configuration
|
||||
{
|
||||
name: 'users',
|
||||
fields: [
|
||||
{ type: 'string', name: 'name' },
|
||||
{ type: 'integer', name: 'age' },
|
||||
// Other fields
|
||||
],
|
||||
}
|
||||
// sample data
|
||||
[
|
||||
{
|
||||
name: 'Jason',
|
||||
age: 20,
|
||||
},
|
||||
{ {
|
||||
name: 'Li Si',
|
||||
age: 18,
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
The composition of a Collection Field in NocoBase consists of
|
||||
|
||||
<img src="./collection-field.svg" />
|
||||
|
||||
### Field Type
|
||||
|
||||
Different fields are distinguished by name, and type indicates the data type of the field, which is divided into Attribute Type and Association Type, e.g.
|
||||
|
||||
**Attribute - Attribute Type**
|
||||
|
||||
- string
|
||||
- text
|
||||
- date
|
||||
- boolean
|
||||
- time
|
||||
- float
|
||||
- json
|
||||
- location
|
||||
- password
|
||||
- virtual
|
||||
- ...
|
||||
|
||||
**Relationship - Association Type**
|
||||
|
||||
- hasOne
|
||||
- hasMany
|
||||
- belongsTo
|
||||
- belongsToMany
|
||||
- ...
|
||||
|
||||
### Field Component
|
||||
|
||||
The field has a data type, the IO of the field value is fine, but it is not enough, if you need to display the field on the interface, you need another dimension of configuration -- `uiSchema`, e.g.
|
||||
|
||||
```tsx | pure
|
||||
// Email field, displayed with Input component, using email validation rules
|
||||
{
|
||||
type: 'string',
|
||||
name: 'email',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': { size: 'large' },
|
||||
'x-validator': 'email',
|
||||
'x-pattern': 'editable', // editable state, and readonly state, read-pretty state
|
||||
},
|
||||
}
|
||||
|
||||
// Example data
|
||||
{
|
||||
email: 'admin@nocobase.com',
|
||||
}
|
||||
|
||||
// Component example
|
||||
<Input name={'email'} size={'large'} value={'admin@nocobase.com'} />
|
||||
```
|
||||
|
||||
The uiSchema is used to configure the components of the field to be displayed on the interface, each field component will correspond to a value and includes several maintained configurations:
|
||||
|
||||
- The component of the field
|
||||
- The parameters of the component
|
||||
- The field's validation rules
|
||||
- The mode of the field (editable, readonly, read-pretty)
|
||||
- The default value of the field
|
||||
- Other
|
||||
|
||||
[see the UI Schema chapter for more information](/development/client/ui-schema-designer/what-is-ui-schema).
|
||||
|
||||
The built-in field components of NocoBase are
|
||||
|
||||
- Input
|
||||
- InputNumber
|
||||
- Select
|
||||
- Radio
|
||||
- Checkbox
|
||||
- ...
|
||||
|
||||
### Field Interface
|
||||
|
||||
With Field Type and Field Component you can freely combine several fields, we call this combined template Field Interface, e.g.
|
||||
|
||||
```ts
|
||||
// email field, string + input, email validation rules
|
||||
{
|
||||
type: 'string',
|
||||
name: 'email',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-validator': 'email',
|
||||
},
|
||||
}
|
||||
|
||||
// phone field, string + input, phone validation rules
|
||||
{
|
||||
type: 'string',
|
||||
name: 'phone',
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-validator': 'phone',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The above email and phone require a full uiSchema to be configured each time which is very tedious. To simplify the configuration, another concept Field interface is introduced, which can template some parameters, e.g.
|
||||
|
||||
```ts
|
||||
// Template for the email field
|
||||
interface email {
|
||||
type: 'string';
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-validator': 'email',
|
||||
};
|
||||
}
|
||||
|
||||
// Template for the phone field
|
||||
interface phone {
|
||||
type: 'string';
|
||||
uiSchema: {
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {},
|
||||
'x-validator': 'phone',
|
||||
};
|
||||
}
|
||||
|
||||
// Simplified field configuration
|
||||
// email
|
||||
{
|
||||
interface: 'email',
|
||||
name: 'email',
|
||||
}
|
||||
|
||||
// phone
|
||||
{
|
||||
interface: 'phone',
|
||||
name: 'phone',
|
||||
}
|
||||
```
|
||||
|
||||
[More Field Interface here](https://github.com/nocobase/nocobase/tree/main/packages/core/client/src/collection-manager/interfaces)
|
137
docs/tr-TR/development/server/collections/options.md
Normal file
137
docs/tr-TR/development/server/collections/options.md
Normal file
@ -0,0 +1,137 @@
|
||||
# Collection protocol
|
||||
|
||||
Collection is the backbone of NocoBase, a protocol for describing data structures (collections and fields), very close to the concept of a relational database, but not limited to relational databases, but can also be a data source for NoSQL databases, HTTP APIs, etc.
|
||||
|
||||
<img src="./schema.svg" style="max-width: 800px;" >
|
||||
|
||||
At this stage, the Collection protocol is based on the relational database interface (db.collections), and data sources such as NoSQL databases and HTTP APIs will be implemented gradually in the future.
|
||||
|
||||
Collection protocol mainly includes two parts: CollectionOptions and FieldOptions. Because Field is extensible, the parameters of FieldOptions are very flexible.
|
||||
|
||||
## CollectionOptions
|
||||
|
||||
```ts
|
||||
interface CollectionOptions {
|
||||
name: string;
|
||||
title?: string;
|
||||
// Tree structure table, TreeRepository
|
||||
tree?: 'adjacency-list' | 'closure-table' | 'materialized-path' | 'nested-set';
|
||||
// parent-child inheritance
|
||||
inherits?: string | string[];
|
||||
fields?: FieldOptions[];
|
||||
timestamps?: boolean;
|
||||
paranoid?: boolean;
|
||||
sortable?: CollectionSortable;
|
||||
model?: string;
|
||||
repository?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type CollectionSortable = string | boolean | { name?: string; scopeKey?: string };
|
||||
```
|
||||
|
||||
## FieldOptions
|
||||
|
||||
Generic field parameters
|
||||
|
||||
```ts
|
||||
interface FieldOptions {
|
||||
name: string;
|
||||
type: string;
|
||||
hidden?: boolean;
|
||||
index?: boolean;
|
||||
interface?: string;
|
||||
uiSchema?: ISchema;
|
||||
```
|
||||
|
||||
[Introduction to UI Schema here](/development/client/ui-schema-designer/what-is-ui-schema)
|
||||
|
||||
### Field Type
|
||||
|
||||
Field Type includes Attribute Type and Association Type.
|
||||
|
||||
**Attribute Type**
|
||||
|
||||
- 'boolean'
|
||||
- 'integer'
|
||||
- 'bigInt'
|
||||
- 'double'
|
||||
- 'real'
|
||||
- 'decimal'
|
||||
- 'string'
|
||||
- 'text'
|
||||
- 'password'
|
||||
- 'date'
|
||||
- 'time'
|
||||
- 'array'
|
||||
- 'json'
|
||||
- 'jsonb'
|
||||
- 'uuid'
|
||||
- 'uid'
|
||||
- 'formula'
|
||||
- 'radio'
|
||||
- 'sort'
|
||||
- 'virtual'
|
||||
|
||||
**Association Type**
|
||||
|
||||
- 'belongsTo'
|
||||
- 'hasOne'
|
||||
- 'hasMany'
|
||||
- 'belongsToMany'
|
||||
|
||||
### Field Interface
|
||||
|
||||
**Basic**
|
||||
|
||||
- input
|
||||
- textarea
|
||||
- phone
|
||||
- email
|
||||
- integer
|
||||
- number
|
||||
- percent
|
||||
- password
|
||||
- icon
|
||||
|
||||
**Choices**
|
||||
|
||||
- checkbox
|
||||
- select
|
||||
- multipleSelect
|
||||
- radioGroup
|
||||
- checkboxGroup
|
||||
- chinaRegion
|
||||
|
||||
**Media**
|
||||
|
||||
- attachment
|
||||
- markdown
|
||||
- richText
|
||||
|
||||
**Date & Time**
|
||||
|
||||
- datetime
|
||||
- time
|
||||
|
||||
**Relation**
|
||||
|
||||
- linkTo - `type: 'believesToMany'`
|
||||
- oho - `type: 'hasOne'`
|
||||
- obo - `type: 'believesTo'`
|
||||
- o2m - `type: 'hasMany'`
|
||||
- m2o - `type: 'believesTo'`
|
||||
- m2m - `type: 'believesToMany'`
|
||||
|
||||
**Advanced**
|
||||
|
||||
- formula
|
||||
- sequence
|
||||
|
||||
**System info**
|
||||
|
||||
- id
|
||||
- createdAt
|
||||
- createdBy
|
||||
- updatedAt
|
||||
- updatedBy
|
1
docs/tr-TR/development/server/collections/schema.svg
Normal file
1
docs/tr-TR/development/server/collections/schema.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 35 KiB |
BIN
docs/tr-TR/development/server/collections/table.jpg
Normal file
BIN
docs/tr-TR/development/server/collections/table.jpg
Normal file
Binary file not shown.
100
docs/tr-TR/development/server/commands.md
Normal file
100
docs/tr-TR/development/server/commands.md
Normal file
@ -0,0 +1,100 @@
|
||||
# Commands
|
||||
|
||||
NocoBase Server Application is a powerful and extensible CLI tool in addition to being used as a WEB server.
|
||||
|
||||
Create a new `app.js` file with the following code.
|
||||
|
||||
```ts
|
||||
const Application = require('@nocobase/server');
|
||||
|
||||
// omit the specific configuration here
|
||||
const app = new Application({/*... */});
|
||||
|
||||
app.runAsCLI();
|
||||
```
|
||||
|
||||
app.js run as ``runAsCLI()`` is a CLI, it will work like this in the command line tool.
|
||||
|
||||
```bash
|
||||
node app.js install # install
|
||||
node app.js start # start
|
||||
```
|
||||
|
||||
To better develop, build and deploy NocoBase applications, NocoBase has many built-in commands, see the [NocoBase CLI](/api/cli) section for details.
|
||||
|
||||
## How to customize Command?
|
||||
|
||||
NocoBase CLI is designed to be very similar to [Laravel Artisan](https://laravel.com/docs/9.x/artisan), both are extensible. NocoBase CLI is based on [commander](https://www.npmjs.com/ package/commander) implementation, which extends Command like this
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
load() {
|
||||
this.app
|
||||
.command('echo')
|
||||
.option('--v, --version');
|
||||
.action(async ([options]) => {
|
||||
console.log('Hello World!');
|
||||
if (options.version) {
|
||||
console.log('Current version:', app.getVersion());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This method defines the following command.
|
||||
|
||||
```bash
|
||||
yarn nocobase echo
|
||||
# Hello World!
|
||||
yarn nocobase echo -v
|
||||
# Hello World!
|
||||
# Current version: 0.8.0-alpha.1
|
||||
```
|
||||
|
||||
More API details can be found in the [Application.command()](/api/server/application#command) section.
|
||||
|
||||
## Example
|
||||
|
||||
### Defining a command for exporting collections
|
||||
|
||||
If we want to export the data in the application's collections to a JSON file, we can define a subcommand as follows.
|
||||
|
||||
```ts
|
||||
import path from 'path';
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
load() {
|
||||
this.app
|
||||
.command('export')
|
||||
.option('-o, --output-dir')
|
||||
.action(async (options, . .collections) => {
|
||||
const { outputDir = path.join(process.env.PWD, 'storage') } = options;
|
||||
await collections.reduce((promise, collection) => promise.then(async () => {
|
||||
if (!this.db.hasCollection(collection)) {
|
||||
console.warn('No such collection:', collection);
|
||||
return;
|
||||
}
|
||||
|
||||
const repo = this.db.getRepository(collection);
|
||||
const data = repo.find();
|
||||
await fs.writeFile(path.join(outputDir, `${collection}.json`), JSON.stringify(data), { mode: 0o644 });
|
||||
}), Promise.resolve());
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After registering and activating the plugin call from the command line.
|
||||
|
||||
```bash
|
||||
mkdir -p . /storage/backups
|
||||
yarn nocobase export -o . /storage/backups users
|
||||
```
|
||||
|
||||
After execution, it will generate `. /storage/backups/users.json` file containing the data from the collections.
|
||||
|
||||
## Summary
|
||||
|
||||
The sample code covered in this chapter is integrated in the [packages/samples/command](https://github.com/nocobase/nocobase/tree/main/packages/samples/command) package and can be run directly locally to see the results.
|
168
docs/tr-TR/development/server/events.md
Normal file
168
docs/tr-TR/development/server/events.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Events
|
||||
|
||||
NocoBase provides a very large number of event listeners in the lifecycle of applications, plugins, and database, and these methods will only be executed when an event is triggered.
|
||||
|
||||
## How to add event listeners?
|
||||
|
||||
The registration of events is usually placed in afterAdd or beforeLoad
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
// After the plugin is added, afterAdd() is executed with or without activation
|
||||
afterAdd() {
|
||||
this.app.on();
|
||||
this.db.on();
|
||||
}
|
||||
|
||||
// beforeLoad() will only be executed after the plugin is activated
|
||||
beforeLoad() {
|
||||
this.app.on();
|
||||
this.db.on();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `db.on`
|
||||
|
||||
Database related events are related to Collection configuration, CRUD of Repository, including:
|
||||
|
||||
- 'beforeSync' / 'afterSync'
|
||||
- 'beforeValidate' / 'afterValidate'
|
||||
- 'beforeCreate' / 'afterCreate'
|
||||
- 'beforeUpdate' / 'afterUpdate'
|
||||
- 'beforeSave' / 'afterSave'
|
||||
- 'beforeDestroy' / 'afterDestroy'
|
||||
- 'afterCreateWithAssociations'
|
||||
- 'afterUpdateWithAssociations'
|
||||
- 'afterSaveWithAssociations'
|
||||
- 'beforeDefineCollection'
|
||||
- 'afterDefineCollection'
|
||||
- 'beforeRemoveCollection' / 'afterRemoveCollection'
|
||||
|
||||
See [Database API](/api/database) for more details.
|
||||
|
||||
### `app.on()`
|
||||
|
||||
The app's events are related to the application's lifecycle, and the relevant events are:
|
||||
|
||||
- 'beforeLoad' / 'afterLoad'
|
||||
- 'beforeInstall' / 'afterInstall'
|
||||
- 'beforeUpgrade' / 'afterUpgrade'
|
||||
- 'beforeStart' / 'afterStart'
|
||||
- 'beforeStop' / 'afterStop'
|
||||
- 'beforeDestroy' / 'afterDestroy'
|
||||
|
||||
Refer to [Application API](/api/server/application#Events) for more details.
|
||||
|
||||
## Example
|
||||
|
||||
Let's continue with a simple online store as an example, the related collections modeling can be reviewed in the [Collections and Fields](/development/) section for examples.
|
||||
|
||||
### Deducting product inventory after creating an order
|
||||
|
||||
Usually we have different collections for products and orders. The problem of overselling can be solved by subtracting the inventory of the item after the customer has placed the order. At this point we can define the corresponding event for the action of Creating Order and solve the inventory modification problem at this time together with:
|
||||
|
||||
```ts
|
||||
class ShopPlugin extends Plugin {
|
||||
beforeLoad() {
|
||||
this.db.on('orders.afterCreate', async (order, options) => {
|
||||
const product = await order.getProduct({
|
||||
transaction: options.transaction
|
||||
});
|
||||
|
||||
await product.update({
|
||||
inventory: product.inventory - order.quantity
|
||||
}, {
|
||||
transaction: options.transaction
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since the default Sequelize event carries information about the transaction, we can use transaction directly to ensure that both data actions are performed in the same transaction.
|
||||
|
||||
Similarly, you can change the order status to shipped after creating the shipping record: ```ts
|
||||
|
||||
```ts
|
||||
class ShopPlugin extends Plugin {
|
||||
load() {
|
||||
this.db.on('deliveries.afterCreate', async (delivery, options) => {
|
||||
const orderRepo = this.db.getRepository('orders');
|
||||
await orderRepo.update({
|
||||
filterByTk: delivery.orderId,
|
||||
value: {
|
||||
status: 2
|
||||
}
|
||||
transaction: options.transaction
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Timed tasks that exist alongside the application
|
||||
|
||||
Without considering complex cases such as using workflow plugins, we can also implement a simple timed task mechanism via application-level events, and it can be bound to the application's process and stop when it exits. For example, if we want to scan all orders at regular intervals and automatically sign them after the sign-off time.
|
||||
|
||||
```ts
|
||||
class ShopPlugin extends Plugin {
|
||||
timer = null;
|
||||
orderReceiveExpires = 86400 * 7;
|
||||
|
||||
checkOrder = async () => {
|
||||
const expiredDate = new Date(Date.now() - this.orderReceiveExpires);
|
||||
const deliveryRepo = this.db.getRepository('deliveries');
|
||||
const expiredDeliveries = await deliveryRepo.find({
|
||||
fields: ['id', 'orderId'],
|
||||
filter: {
|
||||
status: 0,
|
||||
createdAt: {
|
||||
$lt: expiredDate
|
||||
}
|
||||
}
|
||||
});
|
||||
await deliveryRepo.update({
|
||||
filter: {
|
||||
id: expiredDeliveries.map(item => item.get('id')),
|
||||
},
|
||||
values: {
|
||||
status: 1
|
||||
}
|
||||
});
|
||||
const orderRepo = this.db.getRepository('orders');
|
||||
const [updated] = await orderRepo.update({
|
||||
filter: {
|
||||
status: 2,
|
||||
id: expiredDeliveries.map(item => item.get('orderId'))
|
||||
},
|
||||
values: {
|
||||
status: 3
|
||||
}
|
||||
});
|
||||
|
||||
console.log('%d orders expired', updated);
|
||||
};
|
||||
|
||||
load() {
|
||||
this.app.on('beforeStart', () => {
|
||||
// execute every minute
|
||||
this.timer = setInterval(this.checkOrder, 1000 * 60);
|
||||
});
|
||||
|
||||
this.app.on('beforeStop', () => {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The above example gives us a basic understanding of what events do and the ways they can be used to extend.
|
||||
|
||||
* Database related events
|
||||
* Application related events
|
||||
|
||||
The sample code covered in this chapter is integrated in the corresponding package [packages/samples/shop-events](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-events), which can be run directly in run locally to see the results.
|
134
docs/tr-TR/development/server/i18n.md
Normal file
134
docs/tr-TR/development/server/i18n.md
Normal file
@ -0,0 +1,134 @@
|
||||
# Internationalization
|
||||
|
||||
Internationalization in NocoBase is implemented based on [i18next](https://npmjs.com/package/i18next).
|
||||
|
||||
## How to register a multilingual package?
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
load() {
|
||||
this.app.i18n.addResources('zh-CN', 'test', {
|
||||
Hello: '你好',
|
||||
World: '世界',
|
||||
});
|
||||
this.app.i18n.addResources('en-US', 'test', {
|
||||
Hello: 'Hello',
|
||||
World: 'World',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Two i18n instances
|
||||
|
||||
### app.i18n
|
||||
|
||||
Global i18n instance, typically used in the CLI.
|
||||
|
||||
```ts
|
||||
app.i18n.t('World') // "世界" or "World"
|
||||
```
|
||||
|
||||
### ctx.i18n
|
||||
|
||||
CloneInstance of global i18n with a completely independent context for each request, typically used to respond to multilingual messages based on the client language.
|
||||
|
||||
```ts
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = `${ctx.i18n.t('Hello')} ${ctx.i18n.t('World')}`;
|
||||
await next();
|
||||
});
|
||||
```
|
||||
|
||||
The client request parameters can be placed in the query string
|
||||
|
||||
```bash
|
||||
GET /?locale=en-US HTTP/1.1
|
||||
Host: localhost:13000
|
||||
```
|
||||
|
||||
or in the request headers
|
||||
|
||||
```bash
|
||||
GET / HTTP/1.1
|
||||
Host: localhost:13000
|
||||
X-Locale: en-US
|
||||
```
|
||||
|
||||
## Suggested configuration
|
||||
|
||||
With English text as the key and translation as the value, this has the advantage that even if multiple languages are missing, it will be displayed in English and will not cause reading barriers, e.g.
|
||||
|
||||
```ts
|
||||
i18n.addResources('zh-CN', 'your-namespace', {
|
||||
'Show dialog': '显示对话框',
|
||||
'Hide dialog': '隐藏对话框'
|
||||
});
|
||||
```
|
||||
|
||||
To make it easier to manage multilingual files, it is recommended to create a `locale` folder in the plugin and place all the corresponding language files in it:
|
||||
|
||||
```bash
|
||||
|- /my-plugin
|
||||
|- /src
|
||||
|- /server
|
||||
|- locale # Multi-language folder
|
||||
|- en-cn.ts
|
||||
|- en-US.ts
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
### Server-side error alert
|
||||
|
||||
For example, when a user places an order for a product in the store, if the product is not in stock, or not on the shelf, then the order interface should return the appropriate error when it is called.
|
||||
|
||||
```ts
|
||||
const namespace = 'shop';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.i18n.addResources('zh-CN', namespace, {
|
||||
'No such product': '商品不存在',
|
||||
'Product not on sale': '商品已下架',
|
||||
'Out of stock': '库存不足',
|
||||
});
|
||||
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
async create(ctx, next) {
|
||||
const productRepo = ctx.db.getRepository('products');
|
||||
const product = await productRepo.findOne({
|
||||
filterByTk: ctx.action.params.values.productId
|
||||
productId });
|
||||
|
||||
if (!product) {
|
||||
return ctx.throw(404, ctx.t('No such product'));
|
||||
}
|
||||
|
||||
if (!product.enabled) {
|
||||
return ctx.throw(400, ctx.t('Product not on sale'));
|
||||
}
|
||||
|
||||
if (!product.inventory) {
|
||||
return ctx.throw(400, ctx.t('Out of stock'));
|
||||
}
|
||||
|
||||
const orderRepo = ctx.db.getRepository('orders');
|
||||
ctx.body = await orderRepo.create({
|
||||
values: {
|
||||
productId: product.id,
|
||||
quantity: 1,
|
||||
totalPrice: product.price,
|
||||
userId: ctx.state.currentUser.id
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
72
docs/tr-TR/development/server/index.md
Normal file
72
docs/tr-TR/development/server/index.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Overview
|
||||
|
||||
The server-side for an initialized, empty plugin has the following directory structure.
|
||||
|
||||
```bash
|
||||
|- /my-plugin
|
||||
|- /src
|
||||
|- /server # Server-side code of the plugin
|
||||
|- plugin.ts # Classes of the plugin
|
||||
|- index.ts # server-side entry
|
||||
|- server.d.ts
|
||||
|- server.js
|
||||
```
|
||||
|
||||
`plugin.ts` provides calls to various methods of the plugin lifecycle
|
||||
|
||||
```ts
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
|
||||
export class MyPlugin extends Plugin {
|
||||
afterAdd() {
|
||||
// After the plugin pm.add is registered. This is mainly used to place a listener for the app beforeLoad event
|
||||
this.app.on('beforeLoad');
|
||||
}
|
||||
beforeLoad() {
|
||||
// Custom classes or methods
|
||||
this.db.registerFieldTypes()
|
||||
this.db.registerModels()
|
||||
this.db.registerRepositories()
|
||||
this.db.registerOperators()
|
||||
// Event Listening
|
||||
this.app.on();
|
||||
this.db.on();
|
||||
}
|
||||
async load() {
|
||||
// Define collection
|
||||
this.db.collection();
|
||||
// Import collection
|
||||
this.db.import();
|
||||
this.db.addMigrations();
|
||||
|
||||
// Define resource
|
||||
this.resourcer.define();
|
||||
// resource action
|
||||
this.resourcer.registerActions();
|
||||
|
||||
// Register middleware
|
||||
this.resourcer.use();
|
||||
this.acl.use();
|
||||
this.app.use();
|
||||
|
||||
// Customize the multilingual package
|
||||
this.app.i18n.addResources();
|
||||
// Customize command line
|
||||
this.app.command();
|
||||
}
|
||||
async install(options?: InstallOptions) {
|
||||
// Logic for installing
|
||||
}
|
||||
async afterEnable() {
|
||||
// After activation
|
||||
}
|
||||
async afterDisable() {
|
||||
// After disable
|
||||
}
|
||||
async remove() {
|
||||
// Logic for removing
|
||||
}
|
||||
}
|
||||
|
||||
export default MyPlugin;
|
||||
```
|
160
docs/tr-TR/development/server/middleware.md
Normal file
160
docs/tr-TR/development/server/middleware.md
Normal file
@ -0,0 +1,160 @@
|
||||
# Middleware
|
||||
|
||||
## How to register middleware?
|
||||
|
||||
The registration method for middleware is usually written in the load method
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
load() {
|
||||
this.app.acl.use();
|
||||
this.app.resourcer.use();
|
||||
this.app.use();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes.
|
||||
|
||||
1. `app.acl.use()` Add a resource-permission-level middleware to be executed before permission determination
|
||||
2. `app.resourcer.use()` Adds a resource-level middleware that is executed only when a defined resource is requested
|
||||
3. `app.use()` Add an application-level middleware to be executed on every request
|
||||
|
||||
## Onion Circle Model
|
||||
|
||||
```ts
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(1);
|
||||
await next();
|
||||
ctx.body.push(2);
|
||||
});
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(3);
|
||||
await next();
|
||||
ctx.body.push(4);
|
||||
});
|
||||
```
|
||||
|
||||
Visit http://localhost:13000/api/hello to see that the browser responds with the following data
|
||||
|
||||
```js
|
||||
{"data": [1,3,4,2]}
|
||||
```
|
||||
|
||||
## Built-in middlewares and execution order
|
||||
|
||||
1. `cors`
|
||||
2. `bodyParser`
|
||||
3. `i18n`
|
||||
4. `dataWrapping`
|
||||
5. `db2resource` 6.
|
||||
6. `restApi` 1.
|
||||
1. `parseToken` 2.
|
||||
2. `checkRole`
|
||||
3. `acl` 1.
|
||||
1. `acl.use()` Additional middleware added
|
||||
4. `resourcer.use()` Additional middleware added
|
||||
5. `action handler`
|
||||
7. `app.use()` Additional middleware added
|
||||
|
||||
You can also use `before` or `after` to insert the middleware into the location of one of the preceding `tag`, e.g.
|
||||
|
||||
```ts
|
||||
app.use(m1, { tag: 'restApi' });
|
||||
app.resourcer.use(m2, { tag: 'parseToken' });
|
||||
app.resourcer.use(m3, { tag: 'checkRole' });
|
||||
// m4 will come before m1
|
||||
app.use(m4, { before: 'restApi' });
|
||||
// m5 will be inserted between m2 and m3
|
||||
app.resourcer.use(m5, { after: 'parseToken', before: 'checkRole' });
|
||||
```
|
||||
|
||||
If no location is specifically specified, the order of execution of the added middlewares is
|
||||
|
||||
1. middlewares added by `acl.use` will be executed first
|
||||
2. then the ones added by `resourcer.use`, including the middleware handler and action handler
|
||||
3. and finally the ones added by `app.use`
|
||||
|
||||
```ts
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(1);
|
||||
await next();
|
||||
ctx.body.push(2);
|
||||
});
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(3);
|
||||
await next();
|
||||
ctx.body.push(4);
|
||||
});
|
||||
|
||||
app.acl.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(5);
|
||||
await next();
|
||||
ctx.body.push(6);
|
||||
});
|
||||
|
||||
app.resourcer.define({
|
||||
name: 'test',
|
||||
actions: {
|
||||
async list(ctx, next) {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(7);
|
||||
await next();
|
||||
ctx.body.push(8);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Visit http://localhost:13000/api/hello to see that the browser responds with the data
|
||||
|
||||
```js
|
||||
{"data": [1,2]}
|
||||
```
|
||||
|
||||
Visiting http://localhost:13000/api/test:list to see, the browser responds with the following data
|
||||
|
||||
```js
|
||||
{"data": [5,3,7,1,2,8,4,6]}
|
||||
```
|
||||
|
||||
### Resource undefined, middlewares added by resourcer.use() will not be executed
|
||||
|
||||
```ts
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(1);
|
||||
await next();
|
||||
ctx.body.push(2);
|
||||
});
|
||||
|
||||
app.resourcer.use(async (ctx, next) => {
|
||||
ctx.body = ctx.body || [];
|
||||
ctx.body.push(3);
|
||||
await next();
|
||||
ctx.body.push(4);
|
||||
});
|
||||
```
|
||||
|
||||
Visit http://localhost:13000/api/hello to see that the browser responds with the following data
|
||||
|
||||
```js
|
||||
{"data": [1,2]}
|
||||
```
|
||||
|
||||
In the above example, the hello resource is not defined and will not enter the resourcer, so the middleware in the resourcer will not be executed
|
||||
|
||||
## Middleware Usage
|
||||
|
||||
TODO
|
||||
|
||||
## Example
|
||||
|
||||
- [samples/ratelimit](https://github.com/nocobase/nocobase/blob/main/packages/samples/ratelimit/) IP rate-limiting
|
50
docs/tr-TR/development/server/migration.md
Normal file
50
docs/tr-TR/development/server/migration.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Database Migration
|
||||
|
||||
There may be some incompatible changes during the plugin update iteration, these incompatible upgrade scripts can be handled by writing migration files.
|
||||
|
||||
## How to add a migration file?
|
||||
|
||||
```ts
|
||||
export class MyPlugin extends Plugin {
|
||||
load() {
|
||||
// Load a single Migration file
|
||||
this.db.addMigration();
|
||||
// Load multiple Migration files
|
||||
this.db.addMigrations();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
API reference.
|
||||
|
||||
- [db.addMigration()](/api/database#addmigration)
|
||||
- [db.addMigrations()](/api/database#addmigrations)
|
||||
|
||||
## When to execute?
|
||||
|
||||
```bash
|
||||
## When the app is upgraded, migrator.up() and db.sync() are executed
|
||||
yarn nocobase upgrade
|
||||
# Trigger migration separately
|
||||
yarn nocobase migrator up
|
||||
```
|
||||
|
||||
## When do I need to write a migration file?
|
||||
|
||||
It is usually used to update the system configuration stored in the database during the upgrade process. If it is just a collection configuration change, you don't need to configure migration, just execute ``yarn nocobase db:sync`` to synchronize to the database.
|
||||
|
||||
## Migration file
|
||||
|
||||
```ts
|
||||
import { Migration } from '@nocobase/server';
|
||||
|
||||
export default class CustomMigration extends Migration {
|
||||
async up() {
|
||||
//
|
||||
}
|
||||
|
||||
async down() {
|
||||
//
|
||||
}
|
||||
}
|
||||
```
|
464
docs/tr-TR/development/server/resources-actions.md
Normal file
464
docs/tr-TR/development/server/resources-actions.md
Normal file
@ -0,0 +1,464 @@
|
||||
# Resources and Actions
|
||||
|
||||
In the web development world, you may have heard of the concept of RESTful, and NocoBase borrows this concept of resources to map various entities in the system, such as data in a database, a file in a file system or a service. However, NocoBase does not fully follow the RESTful conventions based on practical considerations, but rather extends the specifications from the [Google Cloud API Design Guide](https://cloud.google.com/apis/design) to fit more scenarios.
|
||||
|
||||
## Basic concepts
|
||||
|
||||
The same concept as resources in RESTful, which are externally available objects in the system that can be manipulated, such as data tables, files, and other custom objects.
|
||||
|
||||
Actions refer to reading and writing to resources, usually for accessing data, creating data, updating data, deleting data, etc. NocoBase implements access to resources by defining actions, the core of which is actually a Koa-compatible middleware function for handling requests.
|
||||
|
||||
### Automatic mapping of collections to resources
|
||||
|
||||
NocoBase automatically maps collections to resources by default, and also provides a server-side data interface. So by default, as long as a collection is defined using `db.collection()`, you can access the data resources of this collection via NocoBase HTTP API. The name of the automatically generated resource is the same as the collection name, for example, the collection defined by `db.collection({ name: 'users' })` has the corresponding resource name `users`.
|
||||
|
||||
Also, there are built-in common CRUD actions for these data resources, and built-in actions methods for associative data for relational data resources.
|
||||
|
||||
The default actions for a simple data resource:
|
||||
|
||||
* [`list`](/api/actions#list): Query the list of data in the collection
|
||||
* [`get`](/api/actions#get): Query a single record in the collection
|
||||
* [`create`](/api/actions#create): Create a single record to the collection
|
||||
* [`update`](/api/actions#update): Update a single record on the collection
|
||||
* [`destroy`](/api/actions#destroy): Delete a single record from the collection
|
||||
|
||||
In addition to simple CRUD actions, relational resources have default relational actions:
|
||||
|
||||
* [`add`](/api/actions#add): Add a association to the data
|
||||
* [`remove`](/api/actions#remove): Removes an association from the data
|
||||
* [`set`](/api/actions#set): Set the association to the data
|
||||
* [`toggle`](/api/actions#toggle): Add or remove associations to data
|
||||
|
||||
For example, to define an article collection and synchronize it to the database.
|
||||
|
||||
```ts
|
||||
app.db.collection({
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{ type: 'string', name: 'title' }
|
||||
]
|
||||
});
|
||||
|
||||
await app.db.sync();
|
||||
```
|
||||
|
||||
All CRUD methods for the `posts` data resource can then be called directly via the HTTP API: ```bash
|
||||
|
||||
```bash
|
||||
# create
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"title": "first"}' http://localhost:13000/api/posts:create
|
||||
# list
|
||||
curl http://localhost:13000/api/posts:list
|
||||
# update
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"title": "second"}' http://localhost:13000/api/posts:update
|
||||
# destroy
|
||||
curl -X DELETE http://localhost:13000/api/posts:destroy?filterByTk=1
|
||||
```
|
||||
|
||||
### Customize Actions
|
||||
|
||||
It is also possible to extend specific resources with more actions when the default provided actions such as CRUD do not satisfy the business scenario. For example, additional processing of built-in actions, or the need to set default parameters.
|
||||
|
||||
Custom actions for specific resources, such as overriding the `create` action in the article collection.
|
||||
|
||||
```ts
|
||||
// Equivalent to app.resourcer.registerActions()
|
||||
// Register the create action method for article resources
|
||||
app.actions({
|
||||
async ['posts:create'](ctx, next) {
|
||||
const postRepo = ctx.db.getRepository('posts');
|
||||
await postRepo.create({
|
||||
values: {
|
||||
... . ctx.action.params.values,
|
||||
// restrict the current user to be the creator of the post
|
||||
userId: ctx.state.currentUserId
|
||||
}
|
||||
});
|
||||
|
||||
await next();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This adds a reasonable restriction in the business that users cannot create articles as other users.
|
||||
|
||||
Custom operations for all global resources, such as adding `export` action to all collections.
|
||||
|
||||
```ts
|
||||
app.actions({
|
||||
// Add export method to all resources for exporting data
|
||||
async export(ctx, next) {
|
||||
const repo = ctx.db.getRepository(ctx.action.resource);
|
||||
const results = await repo.find({
|
||||
filter: ctx.action.params.filter
|
||||
});
|
||||
ctx.type = 'text/csv';
|
||||
// Splice to CSV format
|
||||
ctx.body = results
|
||||
.map(row => Object.keys(row)
|
||||
.reduce((arr, col) => [... . arr, row[col]], []).join(',')
|
||||
).join('\n');
|
||||
|
||||
next();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The data in CSV format can then be exported as follows from the HTTP API.
|
||||
|
||||
```bash
|
||||
curl http://localhost:13000/api/<any_table>:export
|
||||
```
|
||||
|
||||
### Action parameters
|
||||
|
||||
Once the client's request reaches the server, the relevant request parameters are parsed by rule and placed on the request's `ctx.action.params` object. there are three main sources for Action parameters.
|
||||
|
||||
1. default parameters at the time of Action definition
|
||||
2. carried by the client request
|
||||
3. other middleware preprocessing
|
||||
|
||||
The parameters from these three parts are combined in this order and eventually passed into the action's execution function before being processed by the real action handler. This is also true in multiple middleware, where the parameters from the previous middleware are continued to be passed to the next middleware with `ctx`.
|
||||
|
||||
The parameters available for built-in actions can be found in the [@nocobase/actions](/api/actions) package. Except for custom actions, client requests mainly use these parameters, and custom actions can be extended with the required parameters according to business requirements.
|
||||
|
||||
Middleware preprocessing mainly uses the `ctx.action.mergeParams()` method and has different merge strategies depending on the parameter types, see also the [mergeParams()](/api/resourcer/action#mergeparams) method for details.
|
||||
|
||||
The default parameters of the built-in Action can only be executed with the `mergeParams()` method for each parameter's default policy when merging, in order to achieve the purpose of limiting certain operations on the server side. For example
|
||||
|
||||
```ts
|
||||
app.resource({
|
||||
name: 'posts',
|
||||
actions: {
|
||||
create: {
|
||||
whitelist: ['title', 'content'],
|
||||
blacklist: ['createdAt', 'createdById'],
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The above defines the `create` action for the `posts` resource, where `whitelist` and `blacklist` are whitelisted and blacklisted respectively for the `values` parameter, i.e. only the `title` and `content` fields in the `values` parameter are allowed, and the ` createdAt` and `createdById` fields in the `values` parameter are disabled.
|
||||
|
||||
### Custom resources
|
||||
|
||||
Data-based resources are also divided into standalone resources and association resources.
|
||||
|
||||
* Standalone resources: `<collection>`
|
||||
* Association resources: `<collection>. <association>`
|
||||
|
||||
```ts
|
||||
// Equivalent to app.resourcer.define()
|
||||
|
||||
// Define article resources
|
||||
app.resource({
|
||||
name: 'posts'
|
||||
});
|
||||
|
||||
// Define the article's author resource
|
||||
app.resource({
|
||||
name: 'posts.user'
|
||||
});
|
||||
|
||||
// Define the article's comment resource
|
||||
app.resource({
|
||||
name: 'posts.coments'
|
||||
});
|
||||
```
|
||||
|
||||
The cases where customization is needed are mainly for non-database table-like resources, such as in-memory data, proxy interfaces for other services, etc., and for cases where specific actions need to be defined for existing table-like resources.
|
||||
|
||||
For example, to define a database-independent resource that sends a notification action.
|
||||
|
||||
```ts
|
||||
app.resource({
|
||||
name: 'notifications',
|
||||
actions: {
|
||||
async send(ctx, next) {
|
||||
await someProvider.send(ctx.request.body);
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Then it can be accessed in the HTTP API as follows
|
||||
|
||||
```bash
|
||||
curl -X POST -d '{"title": "Hello", "to": "hello@nocobase.com"}' 'http://localhost:13000/api/notifications:send'
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Let's continue the simple store scenario from the previous [Collections and fields example](/development/server/collections-fields#Example) to further understand the concepts related to resources and actions. It is assumed here that we base further resource and action definitions on the previous collection's example, so the definition of collection is not repeated here.
|
||||
|
||||
As long as the corresponding collections are defined, we can use default actions directly for data resources such as products, orders, etc. in order to complete the most basic CRUD scenarios.
|
||||
|
||||
### Overriding default actions
|
||||
|
||||
|
||||
Sometimes, there are operations that are not simply for a single record, or the parameters of the default actions need to have some control, we can override the default actions. For example, when we create an order, instead of the client submitting `userId` to represent the ownership of the order, the server should determine the ownership of the order based on the currently logged-in user, so we can override the default `create` action. For simple extensions, we write directly in the main class of the plugin.
|
||||
|
||||
```ts
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import actions from '@nocobase/actions';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
// ...
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
async create(ctx, next) {
|
||||
ctx.action.mergeParams({
|
||||
values: {
|
||||
userId: ctx.state.user.id
|
||||
}
|
||||
});
|
||||
|
||||
return actions.create(ctx, next);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this way, we override the default `create` action for order data resources during plugin loading, but the default logic is still called after modifying the action parameters, so there is no need to write it yourself. The `mergeParams()` method that modifies the submit parameters is useful for the built-in default actions, which we will describe later.
|
||||
|
||||
### Custom actions for collection resources
|
||||
|
||||
When the built-in actions do not meet the business needs, we can extend the functionality of the resource by customizing the actions. For example, usually an order will have many statuses, if we design the values of the `status` field as a series of enumerated values.
|
||||
|
||||
* `-1`: cancelled
|
||||
* `0`: order placed, not paid
|
||||
* `1`: Paid, not shipped
|
||||
* `2`: shipped, not signed
|
||||
* `3`: signed, order completed
|
||||
|
||||
Then we can realize the change of order status through custom actions, such as a shipping action on the order. Although the simple case can be realized through the `update` action, if there are more complicated cases such as payment and signing, using only `update` will cause the problem of unclear semantics and confusing parameters, so we can realize it through custom actions.
|
||||
|
||||
First we add a definition of a shipping information collection, saved to `collections/deliveries.ts`.
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'deliveries',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'order'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'provider'
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'trackingNumber'
|
||||
},
|
||||
{
|
||||
type: 'integer',
|
||||
name: 'status'
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
Also extend the orders collection with an associated field for shipping information (`collections/orders.ts`).
|
||||
|
||||
```ts
|
||||
export default {
|
||||
name: 'orders',
|
||||
fields: [
|
||||
// ... . other fields
|
||||
{
|
||||
type: 'hasOne',
|
||||
name: 'delivery'
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
Then we add the corresponding action definition in the main class of the plugin:
|
||||
|
||||
```ts
|
||||
import { Plugin } from '@nocobase/server';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
// ...
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
async deliver(ctx, next) {
|
||||
const { filterByTk } = ctx.action.params;
|
||||
const orderRepo = ctx.db.getRepository('orders');
|
||||
|
||||
const [order] = await orderRepo.update({
|
||||
filterByTk,
|
||||
values: {
|
||||
status: 2,
|
||||
delivery: {
|
||||
... . ctx.action.params.values,
|
||||
status: 0
|
||||
status: 0 }
|
||||
}
|
||||
});
|
||||
|
||||
ctx.body = order;
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The Repository uses the data repository class of collection, from which most of the data reading and writing actions are done, see the [Repository API](/api/database/repository) section for details.
|
||||
|
||||
Once defined, we can call the "ship" action from the client via the HTTP API:
|
||||
|
||||
```bash
|
||||
curl \
|
||||
-X POST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"provider": "SF", "trackingNumber": "SF1234567890"}' \
|
||||
'/api/orders:deliver/<id>'
|
||||
```
|
||||
|
||||
Similarly, we can define more similar actions, such as payment, signup, etc.
|
||||
|
||||
### Parameter merging
|
||||
|
||||
Suppose we want to allow users to query their own and only their own orders, and we need to restrict them from querying cancelled orders, then we can define with the default parameters of the action.
|
||||
|
||||
```ts
|
||||
import { Plugin } from '@nocobase/server';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
// ...
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
// default parameters for list actions
|
||||
list: {
|
||||
filter: {
|
||||
// Filter operator extended by the users plugin
|
||||
$isCurrentUser: true,
|
||||
status: {
|
||||
$ne: -1
|
||||
}
|
||||
},
|
||||
fields: ['id', 'status', 'createdAt', 'updatedAt']
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the user queries from the client, additional parameters can also be added to the requested URL, such as
|
||||
|
||||
```bash
|
||||
curl 'http://localhost:13000/api/orders:list?productId=1&fields=id,status,quantity,totalPrice&appends=product'
|
||||
```
|
||||
|
||||
The actual query criteria will be combined as
|
||||
|
||||
```json
|
||||
{
|
||||
"filter": {
|
||||
"$and": {
|
||||
"$isCurrentUser": true,
|
||||
"status": {
|
||||
"$ne": -1
|
||||
},
|
||||
"productId": 1
|
||||
}
|
||||
},
|
||||
"fields": ["id", "status", "quantity", "totalPrice", "createdAt", "updatedAt"],
|
||||
"appends": ["product"]
|
||||
}
|
||||
```
|
||||
|
||||
and get the expected query results.
|
||||
|
||||
Alternatively, if we need to restrict the interface for creating orders to fields such as order number (`id`), total price (`totalPrice`), etc. that cannot be submitted by the client, this can be controlled by defining default parameters for the `create` action as follows
|
||||
|
||||
```ts
|
||||
import { Plugin } from '@nocobase/server';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
// ...
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
create: {
|
||||
blacklist: ['id', 'totalPrice', 'status', 'createdAt', 'updatedAt'],
|
||||
values: {
|
||||
status: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, even if the client intentionally submits these fields, they will be filtered out and will not exist in the `ctx.action.params` parameter set.
|
||||
|
||||
If there are more complex restrictions, such as only being able to place an order if the item is on the shelf and in stock, this can be achieved by configuring the middleware to
|
||||
|
||||
```ts
|
||||
import { Plugin } from '@nocobase/server';
|
||||
|
||||
export default class ShopPlugin extends Plugin {
|
||||
async load() {
|
||||
// ...
|
||||
this.app.resource({
|
||||
name: 'orders',
|
||||
actions: {
|
||||
create: {
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
const { productId } = ctx.action.params.values;
|
||||
|
||||
const product = await ctx.db.getRepository('products').findOne({
|
||||
filterByTk: productId,
|
||||
filter: {
|
||||
enabled: true,
|
||||
inventory: {
|
||||
$gt: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return ctx.throw(404);
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Putting some of the business logic (especially the preprocessing) into middleware makes our code clearer and easier to maintain.
|
||||
|
||||
## Summary
|
||||
|
||||
With the above example we have described how to define resources and related actions. To review this chapter.
|
||||
|
||||
* Automatic mapping of collections to resources
|
||||
* Built-in default resource actions
|
||||
* Custom actions on resources
|
||||
* Parameter merging order and strategy for operations
|
||||
|
||||
The code covered in this chapter is included in a complete sample package [packages/samples/shop-actions](https://github.com/nocobase/nocobase/tree/main/packages/samples/shop-actions ), which can be run directly locally to see the results.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user