Merge branch 'develop' of github.com:nocobase/nocobase into feat-main-datasource-mssql

This commit is contained in:
aaaaaajie 2025-03-26 02:17:56 +08:00
commit b93d3bc01d
161 changed files with 2602 additions and 2050 deletions

View File

@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
### 🐛 Bug Fixes
- **[client]**
- Unable to use 'Current User' variable when adding a link page ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
- field assignment with null value is ineffective ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
- `yarn doc` command error ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
- Remove the 'Allow multiple selection' option from dropdown single-select fields in filter forms ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
- Relational field's data range linkage is not effective ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
- **[Collection: Tree]** Migration issue for plugin-collection-tree ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
- **[Action: Custom request]** Unable to download UTF-8 encoded files ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
### 🐛 Bug Fixes

View File

@ -5,6 +5,25 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
## [v1.6.10](https://github.com/nocobase/nocobase/compare/v1.6.9...v1.6.10) - 2025-03-25
### 🐛 修复
- **[client]**
- 添加链接页面时,无法使用“当前用户”变量 ([#6536](https://github.com/nocobase/nocobase/pull/6536)) by @zhangzhonghe
- 字段赋值对字段进行“空值”赋值无效 ([#6549](https://github.com/nocobase/nocobase/pull/6549)) by @katherinehhh
- `yarn doc` 命令报错 ([#6540](https://github.com/nocobase/nocobase/pull/6540)) by @gchust
- 筛选表单中,移除下拉单选字段的“允许多选”选项 ([#6515](https://github.com/nocobase/nocobase/pull/6515)) by @zhangzhonghe
- 关系字段的数据范围联动不生效 ([#6530](https://github.com/nocobase/nocobase/pull/6530)) by @zhangzhonghe
- **[数据表:树]** 树表插件的迁移脚本问题 ([#6537](https://github.com/nocobase/nocobase/pull/6537)) by @2013xile
- **[操作:自定义请求]** 无法下载utf8编码的文件 ([#6541](https://github.com/nocobase/nocobase/pull/6541)) by @2013xile
## [v1.6.9](https://github.com/nocobase/nocobase/compare/v1.6.8...v1.6.9) - 2025-03-23
### 🐛 修复

View File

@ -1,5 +1,5 @@
{
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": ["--ignore-engines"],

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/acl",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/resourcer": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/resourcer": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"minimatch": "^5.1.1"
},
"repository": {

View File

@ -1,14 +1,14 @@
{
"name": "@nocobase/actions",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/cache": "1.7.0-alpha.4",
"@nocobase/database": "1.7.0-alpha.4",
"@nocobase/resourcer": "1.7.0-alpha.4"
"@nocobase/cache": "1.7.0-beta.9",
"@nocobase/database": "1.7.0-beta.9",
"@nocobase/resourcer": "1.7.0-beta.9"
},
"repository": {
"type": "git",

View File

@ -1,17 +1,17 @@
{
"name": "@nocobase/app",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/database": "1.7.0-alpha.4",
"@nocobase/preset-nocobase": "1.7.0-alpha.4",
"@nocobase/server": "1.7.0-alpha.4"
"@nocobase/database": "1.7.0-beta.9",
"@nocobase/preset-nocobase": "1.7.0-beta.9",
"@nocobase/server": "1.7.0-beta.9"
},
"devDependencies": {
"@nocobase/client": "1.7.0-alpha.4"
"@nocobase/client": "1.7.0-beta.9"
},
"repository": {
"type": "git",

View File

@ -1,16 +1,16 @@
{
"name": "@nocobase/auth",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/actions": "1.7.0-alpha.4",
"@nocobase/cache": "1.7.0-alpha.4",
"@nocobase/database": "1.7.0-alpha.4",
"@nocobase/resourcer": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/actions": "1.7.0-beta.9",
"@nocobase/cache": "1.7.0-beta.9",
"@nocobase/database": "1.7.0-beta.9",
"@nocobase/resourcer": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/build",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "Library build tool based on rollup.",
"main": "lib/index.js",
"types": "./lib/index.d.ts",

View File

@ -1,12 +1,12 @@
{
"name": "@nocobase/cache",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/lock-manager": "1.7.0-alpha.4",
"@nocobase/lock-manager": "1.7.0-beta.9",
"bloom-filters": "^3.0.1",
"cache-manager": "^5.2.4",
"cache-manager-redis-yet": "^4.1.2"

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/cli",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./src/index.js",
@ -8,7 +8,7 @@
"nocobase": "./bin/index.js"
},
"dependencies": {
"@nocobase/app": "1.7.0-alpha.4",
"@nocobase/app": "1.7.0-beta.9",
"@types/fs-extra": "^11.0.1",
"@umijs/utils": "3.5.20",
"chalk": "^4.1.1",
@ -25,7 +25,7 @@
"tsx": "^4.19.0"
},
"devDependencies": {
"@nocobase/devtools": "1.7.0-alpha.4"
"@nocobase/devtools": "1.7.0-beta.9"
},
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/client",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "lib/index.js",
"module": "es/index.mjs",
@ -27,9 +27,9 @@
"@formily/reactive-react": "^2.2.27",
"@formily/shared": "^2.2.27",
"@formily/validator": "^2.2.27",
"@nocobase/evaluators": "1.7.0-alpha.4",
"@nocobase/sdk": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/evaluators": "1.7.0-beta.9",
"@nocobase/sdk": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"ahooks": "^3.7.2",
"antd": "5.24.2",
"antd-style": "3.7.1",

View File

@ -102,7 +102,8 @@ export const SettingsCenterConfigure = () => {
expandable={{
defaultExpandAllRows: true,
}}
columns={[
columns={
[
{
dataIndex: 'title',
title: t('Plugin name'),
@ -139,7 +140,8 @@ export const SettingsCenterConfigure = () => {
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
},
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={settings
.filter((v) => {
return v.isTopLevel !== false;

View File

@ -121,7 +121,8 @@ export const MenuConfigure = () => {
expandable={{
defaultExpandAllRows: true,
}}
columns={[
columns={
[
{
dataIndex: 'title',
title: t('Menu item title'),
@ -154,7 +155,8 @@ export const MenuConfigure = () => {
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
},
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={translateTitle(items)}
/>
);

View File

@ -105,7 +105,8 @@ export const RolesResourcesActions = connect((props) => {
className={antTableCell}
size={'small'}
pagination={false}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -146,7 +147,8 @@ export const RolesResourcesActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let enabled = false;
let scope = null;
@ -169,7 +171,8 @@ export const RolesResourcesActions = connect((props) => {
className={antTableCell}
pagination={false}
dataSource={fieldPermissions}
columns={[
columns={
[
{
dataIndex: ['uiSchema', 'title'],
title: t('Field display name'),
@ -222,7 +225,8 @@ export const RolesResourcesActions = connect((props) => {
),
};
}),
] as TableProps['columns']}
] as TableProps['columns']
}
/>
</FormItem>
</FormLayout>

View File

@ -55,7 +55,8 @@ export const StrategyActions = connect((props) => {
size={'small'}
pagination={false}
rowKey={'name'}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -110,7 +111,8 @@ export const StrategyActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let scope = 'all';
let enabled = false;

View File

@ -167,7 +167,7 @@ export function useCollectValuesToSubmit() {
if (parsedValue !== null && parsedValue !== undefined) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -203,6 +203,12 @@ export function useCollectValuesToSubmit() {
]);
}
function interpolateVariables(str: string, scope: Record<string, any>): string {
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
return scope[key] !== undefined ? String(scope[key]) : '';
});
}
export const useCreateActionProps = () => {
const filterByTk = useFilterByTk();
const record = useCollectionRecord();
@ -219,11 +225,20 @@ export const useCreateActionProps = () => {
const collectValues = useCollectValuesToSubmit();
const action = record.isNew ? actionField.componentProps.saveMode || 'create' : 'update';
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
const localVariables = useLocalVariables();
const variables = useVariables();
return {
async onClick() {
const { onSuccess, skipValidator, triggerWorkflows } = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
if (!skipValidator) {
await form.submit();
}
@ -241,6 +256,15 @@ export const useCreateActionProps = () => {
: undefined,
updateAssociationValues,
});
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(data?.data?.data, {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}
@ -338,7 +362,7 @@ export const useAssociationCreateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -588,7 +612,13 @@ export const useCustomizeUpdateActionProps = () => {
skipValidator,
triggerWorkflows,
} = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
@ -605,7 +635,7 @@ export const useCustomizeUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -614,7 +644,7 @@ export const useCustomizeUpdateActionProps = () => {
if (skipValidator === false) {
await form.submit();
}
await resource.update({
const result = await resource.update({
filterByTk,
values: { ...assignedValues },
// TODO(refactor): should change to inject by plugin
@ -622,6 +652,16 @@ export const useCustomizeUpdateActionProps = () => {
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
: undefined,
});
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}
@ -708,7 +748,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -913,7 +953,13 @@ export const useUpdateActionProps = () => {
skipValidator,
triggerWorkflows,
} = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
@ -930,7 +976,7 @@ export const useUpdateActionProps = () => {
if (parsedValue) {
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});
@ -952,7 +998,7 @@ export const useUpdateActionProps = () => {
actionField.data = field.data || {};
actionField.data.loading = true;
try {
await resource.update({
const result = await resource.update({
filterByTk,
values: {
...values,
@ -971,6 +1017,15 @@ export const useUpdateActionProps = () => {
if (callBack) {
callBack?.();
}
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}

View File

@ -157,6 +157,8 @@ export const useCollectionFilterOptions = (collection: any, dataSource?: string)
const option = {
name: field.name,
title: field?.uiSchema?.title || field.name,
label: field?.uiSchema?.title || field.name,
value: field.name,
schema: field?.uiSchema,
operators:
operators?.filter?.((operator) => {

View File

@ -843,5 +843,242 @@
"Ellipsis overflow content": "Contenuto Ellipsis overflow",
"Hide column": "Nascondi colonna",
"In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "In modalità di configurazione, l'intera colonna diventa trasparente. In modalità non di configurazione, l'intera colonna verrà nascosta. Anche se l'intera colonna è nascosta, i suoi valori predefiniti configurati e le altre impostazioni avranno comunque effetto.",
"The following old template features have been deprecated and will be removed in next version.": "Le seguenti funzionalità dei modelli vecchi sono state deprecate e saranno rimosse nella prossima versione."
"Page number": "Numero di pagina",
"Page size": "Numero di voci per pagina",
"Enable": "Abilita",
"Disable": "Disabilita",
"Tab": "Scheda",
"Calculation engine": "Motore di calcolo",
"Expression collection": "Raccolta espressioni",
"Tree collection": "Raccolta struttura ad albero",
"Parent ID": "ID record padre",
"Parent": "Record padre",
"Children": "Record figlio",
"Confirm": "Conferma",
"Block": "Blocco",
"Unnamed": "Senza nome",
"SQL collection": "Raccolta dati SQL",
"Configure field": "Configura campo",
"Username": "Nome utente",
"Null": "Null",
"Boolean": "Booleano",
"String": "Stringa",
"Syntax references": "Riferimenti sintassi",
"Math.js comes with a large set of built-in functions and constants, and offers an integrated solution to work with different data types.": "Math.js include un ampio set di funzioni e costanti integrate e offre una soluzione integrata per lavorare con diversi tipi di dati.",
"Formula.js supports most Microsoft Excel formula functions.": "Formula.js supporta la maggior parte delle funzioni delle formule di Microsoft Excel.",
"String template": "Modello stringa",
"Simple string replacement, can be used to interpolate variables in a string.": "Sostituzione semplice di stringhe, può essere utilizzata per interpolare variabili in una stringa.",
"https://docs.nocobase.com/handbook/calculation-engines/formula": "https://docs-cn.nocobase.com/handbook/calculation-engines/formula",
"https://docs.nocobase.com/handbook/calculation-engines/mathjs": "https://docs-cn.nocobase.com/handbook/calculation-engines/mathjs",
"Display <icon></icon> when unchecked": "Visualizza <icon></icon> quando deselezionato",
"Allow dissociate": "Consenti dissociazione",
"Edit block title & description": "Modifica titolo e descrizione blocco",
"Add Markdown": "Aggiungi Markdown",
"Must be 1-50 characters in length (excluding @.<>\"'/)": "Deve essere lungo 1-50 caratteri (esclusi @.<>\"'/)",
"Data source permissions": "Permessi origine dati",
"Now": "Adesso",
"Access control": "Controllo accessi",
"Remove": "Rimuovi",
"Docs": "Documenti",
"Enable page header": "Abilita intestazione pagina",
"Display page title": "Visualizza titolo pagina",
"Edit page title": "Modifica titolo pagina",
"Enable page tabs": "Abilita schede pagina",
"Constant": "Costante",
"Select a variable": "Seleziona una variabile",
"Double click to choose entire object": "Doppio clic per scegliere l'intero oggetto",
"True": "Vero",
"False": "Falso",
"Prettify": "Formatta",
"Theme": "Tema",
"Default theme": "Tema predefinito",
"Compact theme": "Tema compatto",
"Download": "Scarica",
"File type is not supported for previewing, please download it to preview.": "Il tipo di file non è supportato per l'anteprima, scaricalo per visualizzarlo.",
"Click or drag file to this area to upload": "Clicca o trascina il file in quest'area per caricarlo",
"Support for a single or bulk upload.": "Supporta il caricamento singolo o in blocco.",
"File size should not exceed {{size}}.": "La dimensione del file non deve superare {{size}}.",
"File size exceeds the limit": "La dimensione del file supera il limite",
"File type is not allowed": "Il tipo di file non è consentito",
"Incomplete uploading files need to be resolved": "I caricamenti incompleti dei file devono essere completati",
"Default title for each record": "Titolo predefinito per ogni record",
"If collection inherits, choose inherited collections as templates": "Se la raccolta eredita, scegli le raccolte ereditate come modelli",
"Select an existing piece of data as the initialization data for the form": "Seleziona un dato esistente come dati di inizializzazione per il modulo",
"Only the selected fields will be used as the initialization data for the form": "Solo i campi selezionati verranno utilizzati come dati di inizializzazione per il modulo",
"Template Data": "Dati modello",
"Data fields": "Campi dati",
"Add template": "Aggiungi modello",
"Enable form data template": "Abilita modello dati modulo",
"Form data templates": "Modelli dati modulo",
"No configuration available.": "Nessuna configurazione disponibile.",
"Reload application": "Ricarica applicazione",
"The application is reloading, please do not close the page.": "L'applicazione si sta ricaricando, non chiudere la pagina.",
"Application reloading": "Ricaricamento applicazione",
"Allows to clear cache, reboot application": "Consente di cancellare la cache, riavviare l'applicazione",
"The will interrupt service, it may take a few seconds to restart. Are you sure to continue?": "Il riavvio interromperà il servizio, potrebbero essere necessari alcuni secondi. Sei sicuro di continuare?",
"Clear cache": "Cancella cache",
"Quick create": "Creazione rapida",
"Dropdown": "Menu a discesa",
"Pop-up": "Popup",
"Direct duplicate": "Duplicazione diretta",
"Copy into the form and continue to fill in": "Copia nel modulo e continua a compilare",
"Failed to load plugin": "Caricamento plugin fallito",
"Date range limit": "Limite intervallo date",
"MinDate": "Data minima",
"MaxDate": "Data massima",
"Please select time or variable": "Seleziona ora o variabile",
"Filter out a single piece or a group of records as a template": "Filtra un singolo dato o un gruppo di record come modello",
"The title field is used to identify the template record": "Il campo titolo è utilizzato per identificare il record modello",
"Template fields": "Campi modello",
"The selected fields will automatically populate the form": "I campi selezionati popoleranno automaticamente il modulo",
"UnSelect all": "Deseleziona tutto",
"Secondary confirmation": "Conferma secondaria",
"Perform the {{title}}": "Esegui {{title}}",
"Are you sure you want to perform the {{title}} action?": "Sei sicuro di voler eseguire l'azione {{title}}?",
"Permission denied": "Permesso negato",
"Allow add new": "Consenti aggiunta",
"Data model": "Modello dati",
"Security": "Sicurezza",
"Action": "Azione",
"System": "Sistema",
"Other": "Altro",
"Allow selection of existing records": "Consenti selezione di record esistenti",
"Data Model": "Modello dati",
"Blocks": "Blocchi",
"Users & permissions": "Utenti e permessi",
"System management": "Gestione sistema",
"System & security": "Sistema e sicurezza",
"Workflow": "Workflow",
"Third party services": "Servizi di terze parti",
"Data model tools": "Strumenti modello dati",
"Data sources": "Origini dati",
"Collections": "Raccolte",
"Collection fields": "Campi raccolta",
"Authentication": "Autenticazione",
"Logging and monitoring": "Registrazione e monitoraggio",
"Main": "Principale",
"Index": "Indice",
"Field values must be unique.": "I valori dei campi devono essere univoci.",
"Alphabet": "Alfabeto",
"Accuracy": "Precisione",
"Millisecond": "Millisecondo",
"Second": "Secondo",
"Unix Timestamp": "Timestamp Unix",
"Field value do not meet the requirements": "Il valore del campo non soddisfa i requisiti",
"Field value size is": "La dimensione del valore del campo è",
"Unit conversion": "Conversione unità",
"Separator": "Separatore",
"Prefix": "Prefisso",
"Suffix": "Suffisso",
"Record unique key": "Chiave univoca record",
"Filter target key": "Chiave target filtro",
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "Se una raccolta non ha una chiave primaria, devi configurare un record come chiave univoca per individuare i record di ogni riga all'interno di un blocco, la mancata configurazione impedirà la creazione di blocchi di dati per la raccolta.",
"Filter data based on the specific field, with the requirement that the field value must be unique.": "Filtra i dati in base a un campo specifico, con il requisito che il valore del campo deve essere univoco.",
"Multiply by": "Moltiplica per",
"Divide by": "Dividi per",
"Scientifix notation": "Notazione scientifica",
"Normal": "Normale",
"Automatically generate default values": "Genera automaticamente valori predefiniti",
"Refresh data on close": "Refresh dei dati alla chiusura",
"Refresh data on action": "Refresh dei dati su azione",
"Unknown field type": "Tipo di campo sconosciuto",
"The following field types are not compatible and do not support output and display": "I seguenti tipi di campo non sono compatibili e non supportano l'output e la visualizzazione",
"Not fixed": "Non fissato",
"Left fixed": "Fissato a sinistra",
"Right fixed": "Fissato a destra",
"Fixed": "Colonna fissa",
"Set block height": "Imposta altezza del blocco",
"Specify height": "Specifica altezza",
"Full height": "Altezza completa",
"Please configure the URL": "Configura l'URL",
"URL": "URL",
"Search parameters": "Parametri di ricerca URL",
"Do not concatenate search params in the URL": "Non concatenare i parametri di ricerca nell'URL",
"Edit link": "Modifica link",
"Add parameter": "Aggiungi parametro",
"Use simple pagination mode": "Usa modalità di paginazione semplice",
"Set Template Engine": "Imposta motore template",
"Template engine": "Motore template",
"The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "L'utente corrente ha solo il permesso di configurazione dell'interfaccia utente, ma non ha il permesso di visualizzazione per la raccolta \"{{name}}\"",
"Default value to current time": "Imposta il valore predefinito del campo all'ora corrente",
"Automatically update timestamp on update": "Aggiorna automaticamente il timestamp all'aggiornamento",
"Default value to current server time": "Imposta il valore predefinito del campo all'ora corrente del server",
"Automatically update timestamp to the current server time on update": "Aggiorna automaticamente il timestamp all'ora corrente del server all'aggiornamento",
"Datetime (with time zone)": "Data e ora (con fuso orario)",
"Datetime (without time zone)": "Data e ora (senza fuso orario)",
"DateOnly": "Solo data",
"Content": "Contenuto",
"Perform the Update record": "Esegui aggiornamento record",
"Are you sure you want to perform the Update record action?": "Sei sicuro di voler eseguire l'azione di aggiornamento record?",
"Perform the Custom request": "Esegui richiesta personalizzata",
"Are you sure you want to perform the Custom request action": "Sei sicuro di voler eseguire l'azione di richiesta personalizzata?",
"Perform the Refresh": "Esegui refresh",
"Are you sure you want to perform the Refresh action?": "Sei sicuro di voler eseguire l'azione di refresh?",
"Perform the Submit": "Esegui l'invio",
"Are you sure you want to perform the Submit action?": "Sei sicuro di voler eseguire l'azione di invio?",
"Perform the Trigger workflow": "Esegui il trigger del workflow",
"Are you sure you want to perform the Trigger workflow action?": "Sei sicuro di voler eseguire l'azione di trigger del workflow?",
"Picker": "Selettore",
"Quarter": "Trimestre",
"Switching the picker, the value and default value will be cleared": "Cambiando il selettore, il valore e il valore predefinito verranno cancellati",
"Stay on the current popup or page": "Rimani nel popup o nella pagina corrente",
"Return to the previous popup or page": "Torna al popup o alla pagina precedente",
"Action after successful submission": "Azione dopo l'invio riuscito",
"Allow disassociation": "Consenti la dissociazione",
"Layout": "Layout",
"Vertical": "Verticale",
"Horizontal": "Orizzontale",
"Edit group title": "Modifica titolo del gruppo",
"Title position": "Posizione titolo",
"Dashed": "Tratteggiato",
"Left": "Sinistra",
"Center": "Centro",
"Right": "Destra",
"Divider line color": "Colore linea di divisione",
"Label align": "Allineamento etichetta",
"Label width": "Larghezza etichetta",
"When the Label exceeds the width": "Quando l'etichetta supera la larghezza",
"Line break": "A capo",
"Ellipsis": "Ellipsis",
"Set block layout": "Imposta layout del blocco",
"Add & Update": "Aggiungi e aggiorna",
"Table size": "Dimensione della tabella",
"Plugin": "Plugin",
"Bulk enable": "Abilitazione in blocco",
"Search plugin...": "Cerca plugin...",
"Package name": "Nome pacchetto",
"Associate": "Associa",
"Please add or select record": "Aggiungi o seleziona record",
"No data": "Nessun dato",
"Fields can only be used correctly if they are defined with an interface.": "I campi possono essere utilizzati correttamente solo se sono definiti con un'interfaccia.",
"Unauthenticated. Please sign in to continue.": "Non autenticato. Accedi per continuare.",
"User not found. Please sign in again to continue.": "Impossibile trovare l'utente. Accedi nuovamente per continuare.",
"Your session has expired. Please sign in again.": "La tua sessione è scaduta. Accedi nuovamente.",
"User password changed, please signin again.": "La password dell'utente è stata modificata, accedi di nuovo.",
"Show file name": "Mostra nome del file",
"Outlined": "Contornato",
"Filled": "Riempito",
"Two tone": "Due toni",
"Desktop routes": "Percorsi desktop",
"Route permissions": "Permessi percorso",
"New routes are allowed to be accessed by default": "I nuovi percorsi sono accessibili per impostazione predefinita",
"Route name": "Nome percorso",
"Mobile routes": "Percorsi mobile",
"Show in menu": "Mostra nel menu",
"Hide in menu": "Nascondi nel menu",
"Path": "Percorso",
"Type": "Tipo",
"Access": "Accesso",
"Routes": "Percorsi",
"Add child route": "Aggiungi percorso figlio",
"Delete routes": "Elimina percorsi",
"Delete route": "Elimina percorso",
"Are you sure you want to hide these routes in menu?": "Sei sicuro di voler nascondere questi percorsi nel menu?",
"Are you sure you want to show these routes in menu?": "Sei sicuro di voler mostrare questi percorsi nel menu?",
"Are you sure you want to hide this menu?": "Sei sicuro di voler nascondere questo menu?",
"After hiding, this menu will no longer appear in the menu bar. To show it again, you need to go to the route management page to configure it.": "Dopo averlo nascosto, questo menu non apparirà più nella barra dei menu. Per mostrarlo di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo.",
"If selected, the page will display Tab pages.": "Se selezionato, la pagina visualizzerà le pagine schede.",
"If selected, the route will be displayed in the menu.": "Se selezionato, il percorso verrà visualizzato nel menu.",
"Are you sure you want to hide this tab?": "Sei sicuro di voler nascondere questa scheda?",
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Dopo averla nascosta, questa scheda non apparirà più nella barra delle schede. Per mostrarla di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo."
}

View File

@ -1094,5 +1094,6 @@
"Font Sizepx": "字体大小(像素)",
"Font Weight": "字体粗细",
"Font Style": "字体样式",
"Italic": "斜体"
"Italic": "斜体",
"Response record":"响应结果记录"
}

View File

@ -126,6 +126,10 @@ export const getAllowMultiple = (params?: { title: string }) => {
return {
name: 'allowMultiple',
type: 'switch',
useVisible() {
const isAssociationField = useIsAssociationField();
return isAssociationField;
},
useComponentProps() {
const { t } = useTranslation();
const field = useField<Field>();

View File

@ -14,6 +14,14 @@ import React, { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Router } from 'react-router-dom';
import { SchemaInitializerItem } from '../../application';
import {
CollectionManagerProvider,
useCollectionManager,
} from '../../data-source/collection/CollectionManagerProvider';
import {
DataSourceManagerProvider,
useDataSourceManager,
} from '../../data-source/data-source/DataSourceManagerProvider';
import { useGlobalTheme } from '../../global-theme';
import { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
import {
@ -34,6 +42,8 @@ export const LinkMenuItem = () => {
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
const parentRoute = useParentRoute();
const { createRoute } = useNocoBaseRoutes();
const dm = useDataSourceManager();
const cm = useCollectionManager();
const handleClick = useCallback(async () => {
const values = await FormDialog(
@ -41,6 +51,8 @@ export const LinkMenuItem = () => {
() => {
const history = createMemoryHistory();
return (
<DataSourceManagerProvider dataSourceManager={dm}>
<CollectionManagerProvider instance={cm} dataSource={cm?.dataSource?.key}>
<Router location={history.location} navigator={history}>
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
@ -66,6 +78,8 @@ export const LinkMenuItem = () => {
</FormLayout>
</SchemaComponentOptions>
</Router>
</CollectionManagerProvider>
</DataSourceManagerProvider>
);
},
theme,

View File

@ -127,7 +127,8 @@ function BulkEnableButton({ plugins = [] }) {
}}
size={'small'}
pagination={false}
columns={[
columns={
[
{
title: t('Plugin'),
dataIndex: 'displayName',
@ -145,7 +146,8 @@ function BulkEnableButton({ plugins = [] }) {
width: 300,
ellipsis: true,
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={items}
/>
</Modal>

View File

@ -52,6 +52,7 @@ import { KeepAlive } from './KeepAlive';
import { NocoBaseDesktopRoute, NocoBaseDesktopRouteType } from './convertRoutesToSchema';
import { MenuSchemaToolbar, ResetThemeTokenAndKeepAlgorithm } from './menuItemSettings';
import { userCenterSettings } from './userCenterSettings';
import { createGlobalStyle, createStyles } from 'antd-style';
export { KeepAlive, NocoBaseDesktopRouteType };
@ -268,7 +269,7 @@ const GroupItem: FC<{ item: any }> = (props) => {
};
const WithTooltip: FC<{ title: string; hidden: boolean }> = (props) => {
const { inHeader } = useContext(headerContext);
const { inHeader } = useContext(HeaderContext);
return (
<RouteContext.Consumer>
@ -406,7 +407,7 @@ const contentStyle = {
paddingInline: 0,
};
const headerContext = React.createContext<{ inHeader: boolean }>({ inHeader: false });
const HeaderContext = React.createContext<{ inHeader: boolean }>({ inHeader: false });
const popoverStyle = css`
.ant-popover-inner {
@ -495,9 +496,38 @@ const collapsedButtonRender = (collapsed, dom) => {
return <CollapsedButton collapsed={collapsed}>{dom}</CollapsedButton>;
};
/**
* antd bug antd
* - issue: https://github.com/ant-design/pro-components/issues/8593
* - issue: https://github.com/ant-design/pro-components/issues/8522
* - issue: https://github.com/ant-design/pro-components/issues/8432
*/
const useHeaderStyle = createStyles(({ token }: any) => {
return {
headerPopup: {
'&.ant-menu-submenu-popup>.ant-menu': {
backgroundColor: `${token.colorBgHeader}`,
},
},
headerMenuActive: {
'& .ant-menu-submenu-selected>.ant-menu-submenu-title': {
color: token.colorTextHeaderMenuActive,
},
},
};
});
const headerContextValue = { inHeader: true };
const HeaderWrapper: FC = (props) => {
const { styles } = useHeaderStyle();
return (
<span className={styles.headerMenuActive}>
<HeaderContext.Provider value={headerContextValue}>{props.children}</HeaderContext.Provider>
</span>
);
};
const headerRender = (props: HeaderViewProps, defaultDom: React.ReactNode) => {
return <headerContext.Provider value={headerContextValue}>{defaultDom}</headerContext.Provider>;
return <HeaderWrapper>{defaultDom}</HeaderWrapper>;
};
const IsMobileLayoutContext = React.createContext<{
@ -531,6 +561,7 @@ export const InternalAdminLayout = () => {
const doNotChangeCollapsedRef = useRef(false);
const { t } = useMenuTranslation();
const designable = isMobileLayout ? false : _designable;
const { styles } = useHeaderStyle();
const route = useMemo(() => {
return {
@ -544,7 +575,7 @@ export const InternalAdminLayout = () => {
colorBgHeader: token.colorBgHeader,
colorTextMenu: token.colorTextHeaderMenu,
colorTextMenuSelected: token.colorTextHeaderMenuActive,
colorTextMenuActive: token.colorTextHeaderMenuActive,
colorTextMenuActive: token.colorTextHeaderMenuHover,
colorBgMenuItemHover: token.colorBgHeaderMenuHover,
colorBgMenuItemSelected: token.colorBgHeaderMenuActive,
heightLayoutHeader: 46,
@ -591,6 +622,12 @@ export const InternalAdminLayout = () => {
});
}, []);
const menuProps = useMemo(() => {
return {
overflowedIndicatorPopupClassName: styles.headerPopup,
};
}, [styles.headerPopup]);
return (
<DndContext onDragEnd={onDragEnd}>
<ProLayout
@ -612,6 +649,7 @@ export const InternalAdminLayout = () => {
onCollapse={onCollapse}
collapsed={collapsed}
onPageChange={onPageChange}
menuProps={menuProps}
>
<RouteContext.Consumer>
{(value: RouteContextType) => {

View File

@ -34,6 +34,8 @@ import {
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
import { useLinkageAction } from './hooks';
import { requestSettingsSchema } from './utils';
import { useAfterSuccessOptions } from './hooks/useGetAfterSuccessVariablesOptions';
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
const MenuGroup = (props) => {
return props.children;
@ -280,11 +282,24 @@ export function SkipValidation() {
);
}
const fieldNames = {
value: 'value',
label: 'label',
};
const useVariableProps = (environmentVariables) => {
const scope = useAfterSuccessOptions();
return {
scope: [environmentVariables, ...scope].filter(Boolean),
fieldNames,
};
};
export function AfterSuccess() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
const environmentVariables = useGlobalVariable('$env');
return (
<SchemaSettingsModalItem
title={t('After successful submission')}
@ -363,8 +378,9 @@ export function AfterSuccess() {
redirectTo: {
title: t('Link'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
'x-component': 'Variable.TextArea',
// eslint-disable-next-line react-hooks/rules-of-hooks
'x-use-component-props': () => useVariableProps(environmentVariables),
},
},
} as ISchema

View File

@ -16,16 +16,19 @@ import Action from './Action';
import { ComposedAction } from './types';
import { Icon } from '../../../icon';
const WrapperComponent = ({ component: Component = 'a', icon, onlyIcon, children, ...restProps }: any) => {
const WrapperComponent = React.forwardRef(
({ component: Component = 'a', icon, onlyIcon, children, ...restProps }: any, ref) => {
return (
<Component {...restProps}>
<Component ref={ref} {...restProps}>
<Tooltip title={restProps.title}>
<span style={{ marginRight: 3 }}>{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}</span>
</Tooltip>
{onlyIcon ? children[1] : children}
</Component>
);
};
},
);
WrapperComponent.displayName = 'WrapperComponentLink';
export const ActionLink: ComposedAction = withDynamicSchemaProps(
observer((props: any) => {
@ -34,6 +37,7 @@ export const ActionLink: ComposedAction = withDynamicSchemaProps(
{...props}
component={props.component || WrapperComponent}
className={classnames('nb-action-link', props.className)}
isLink
/>
);
}),

View File

@ -247,7 +247,6 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
const aclCtx = useACLActionParamsContext();
const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any);
const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction;
const buttonStyle = useMemo(() => {
return {
...style,
@ -538,6 +537,7 @@ const RenderButtonInner = observer(
Designer: React.ElementType;
designerProps: any;
title: string;
isLink?: boolean;
}) => {
const {
designable,
@ -558,6 +558,7 @@ const RenderButtonInner = observer(
Designer,
designerProps,
title,
isLink,
...others
} = props;
const debouncedClick = useCallback(
@ -583,7 +584,7 @@ const RenderButtonInner = observer(
const actionTitle = title || field?.title;
const { opacity, ...restButtonStyle } = buttonStyle;
const linkStyle = isLink && opacity ? { opacity } : undefined;
return (
<SortableItem
role="button"
@ -592,9 +593,9 @@ const RenderButtonInner = observer(
onMouseEnter={handleMouseEnter}
// @ts-ignore
loading={field?.data?.loading || loading}
icon={typeof icon === 'string' ? <Icon style={{ opacity }} type={icon} /> : icon}
icon={typeof icon === 'string' ? <Icon type={icon} style={linkStyle} /> : icon}
disabled={disabled}
style={restButtonStyle}
style={isLink ? restButtonStyle : buttonStyle}
onClick={process.env.__E2E__ ? handleButtonClick : debouncedClick} // E2E 中的点击操作都是很快的,如果加上 debounce 会导致 E2E 测试失败
component={tarComponent || Button}
className={classnames(componentCls, hashId, className, 'nb-action')}
@ -602,7 +603,7 @@ const RenderButtonInner = observer(
title={actionTitle}
>
{actionTitle && (
<span className={icon ? 'nb-action-title' : null} style={{ opacity }}>
<span className={icon ? 'nb-action-title' : null} style={linkStyle}>
{actionTitle}
</span>
)}

View File

@ -0,0 +1,66 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useMemo } from 'react';
import { useCollection_deprecated, useCollectionFilterOptions } from '../../../../collection-manager';
import { useCollectionRecordData } from '../../../../data-source';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../../';
import { useBlockContext } from '../../../../block-provider/BlockProvider';
import { usePopupVariable } from '../../../../schema-settings/VariableInput/hooks';
import { useCurrentRoleVariable } from '../../../../schema-settings/VariableInput/hooks';
export const useAfterSuccessOptions = () => {
const collection = useCollection_deprecated();
const { t } = useTranslation();
const fieldsOptions = useCollectionFilterOptions(collection);
const userFieldOptions = useCollectionFilterOptions('users', 'main');
const compile = useCompile();
const recordData = useCollectionRecordData();
const { name: blockType } = useBlockContext() || {};
const [fields, userFields] = useMemo(() => {
return [compile(fieldsOptions), compile(userFieldOptions)];
}, [fieldsOptions, userFieldOptions]);
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
const { currentRoleSettings } = useCurrentRoleVariable();
const record = useCollectionRecordData();
return useMemo(() => {
return [
(record || blockType === 'form') && {
value: '$record',
label: t('Response record', { ns: 'client' }),
children: [...fields],
},
recordData && {
value: 'currentRecord',
label: t('Current record', { ns: 'client' }),
children: [...fields],
},
shouldDisplayPopupRecord && {
...popupRecordSettings,
},
{
value: 'currentUser',
label: t('Current user', { ns: 'client' }),
children: userFields,
},
currentRoleSettings,
{
value: 'currentTime',
label: t('Current time', { ns: 'client' }),
children: null,
},
{
value: '$nToken',
label: 'API token',
children: null,
},
].filter(Boolean);
}, [recordData, t, fields, blockType, userFields]);
};

View File

@ -16,5 +16,6 @@ export * from './hooks/useGetAriaLabelOfAction';
export * from './hooks/useGetAriaLabelOfDrawer';
export * from './hooks/useGetAriaLabelOfModal';
export * from './hooks/useGetAriaLabelOfPopover';
export * from './hooks/useGetAfterSuccessVariablesOptions';
export * from './types';
export * from './zIndexContext';

View File

@ -14,22 +14,22 @@ import { uid } from '@formily/shared';
import { Space, message } from 'antd';
import { isEqual } from 'lodash';
import { isFunction } from 'mathjs';
import React, { useEffect, useState, useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ClearCollectionFieldContext,
NocoBaseRecursionField,
RecordProvider,
SchemaComponentContext,
useAPIClient,
useCollectionRecordData,
SchemaComponentContext,
} from '../../../';
import { Action } from '../action';
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
import { isVariable } from '../../../variables/utils/isVariable';
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
import { Action } from '../action';
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
import useServiceOptions, { useAssociationFieldContext } from './hooks';
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
export const AssociationFieldAddNewer = (props) => {
const schemaComponentCtxValue = useContext(SchemaComponentContext);

View File

@ -158,7 +158,10 @@ export const InternalPicker = observer(
}),
[],
);
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [isMobileLayout, fieldSchema]);
const newSchema = useMemo(
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
[isMobileLayout, fieldSchema],
);
return (
<PopupSettingsProvider enableURL={false}>

View File

@ -144,8 +144,14 @@ const ToManyNester = observer(
const update = useUpdate();
const { isMobileLayout } = useMobileLayout();
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [isMobileLayout, fieldSchema]);
const newParentSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema.parent) : fieldSchema.parent, [isMobileLayout, fieldSchema.parent]);
const newSchema = useMemo(
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
[isMobileLayout, fieldSchema],
);
const newParentSchema = useMemo(
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema.parent) : fieldSchema.parent),
[isMobileLayout, fieldSchema.parent],
);
const refreshComponent = useRefreshComponent();
const refresh = useCallback(() => {

View File

@ -60,6 +60,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
};
}
// 用于获取关系字段请求数据时所需的一些参数
export default function useServiceOptions(props) {
const { action = 'list', service, useOriginalFilter } = props;
const fieldSchema = useFieldSchema();

View File

@ -50,7 +50,7 @@ describe('CollectionSelect', () => {
>
<div
aria-label="block-item-demo title"
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-11aiz3o"
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
role="button"
>
<div
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
>
<div
aria-label="block-item-demo title"
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-11aiz3o"
class="nb-block-item nb-form-item css-9qorhu ant-nb-block-item css-dev-only-do-not-override-1rquknz"
role="button"
>
<div

View File

@ -92,7 +92,7 @@ export const useGetFilterFieldOptions = () => {
const getOptions = (fields, depth, usedInVariable?: boolean) => {
const options = [];
fields.forEach((field) => {
fields?.forEach((field) => {
const option = field2option(field, depth, usedInVariable);
if (option) {
options.push(option);

View File

@ -52,7 +52,10 @@ const FormComponent: React.FC<FormProps> = (props) => {
labelWrap = true,
} = cardItemSchema?.['x-component-props'] || {};
const { isMobileLayout } = useMobileLayout();
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [fieldSchema, isMobileLayout]);
const newSchema = useMemo(
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
[fieldSchema, isMobileLayout],
);
return (
<FieldContext.Provider value={undefined}>
@ -81,12 +84,7 @@ const FormComponent: React.FC<FormProps> = (props) => {
}
`}
>
<NocoBaseRecursionField
basePath={f.address}
schema={newSchema}
onlyRenderProperties
isUseFormilyField
/>
<NocoBaseRecursionField basePath={f.address} schema={newSchema} onlyRenderProperties isUseFormilyField />
</div>
</FormLayout>
</FormContext.Provider>
@ -104,19 +102,17 @@ const FormDecorator: React.FC<FormProps> = (props) => {
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
const Component = useComponent(fieldSchema['x-component'], Def);
const { isMobileLayout } = useMobileLayout();
const newSchema = useMemo(() => isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema, [fieldSchema, isMobileLayout]);
const newSchema = useMemo(
() => (isMobileLayout ? transformMultiColumnToSingleColumn(fieldSchema) : fieldSchema),
[fieldSchema, isMobileLayout],
);
return (
<FieldContext.Provider value={undefined}>
<FormContext.Provider value={form}>
<FormLayout layout={'vertical'} {...others}>
<FieldContext.Provider value={f}>
<Component {...field.componentProps}>
<NocoBaseRecursionField
basePath={f.address}
schema={newSchema}
onlyRenderProperties
isUseFormilyField
/>
<NocoBaseRecursionField basePath={f.address} schema={newSchema} onlyRenderProperties isUseFormilyField />
</Component>
</FieldContext.Provider>
{/* <FieldContext.Provider value={f}>{children}</FieldContext.Provider> */}

View File

@ -1,5 +0,0 @@
# Page
Can be used in conjunction with DocumentTitleProvider to display the page title on document.title.
<code src="./demos/demo1.tsx"></code>

View File

@ -1,5 +0,0 @@
# Page
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
<code src="./demos/demo1.tsx"></code>

View File

@ -343,14 +343,14 @@ export const TableBlockDesigner = () => {
}}
/>
<SchemaSettingsConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
{supportTemplate && <SchemaSettingsDivider />}
{/* {supportTemplate && <SchemaSettingsDivider />}
{supportTemplate && (
<SchemaSettingsTemplate
componentName={`${componentNamePrefix}Table`}
collectionName={name}
resourceName={defaultResource}
/>
)}
)} */}
<SchemaSettingsDivider />
<SchemaSettingsRemove
removeParentsIfNoChildren

View File

@ -233,10 +233,10 @@ describe('Table.settings', () => {
},
],
},
{
title: 'Save as template',
type: 'modal',
},
// {
// title: 'Save as template',
// type: 'modal',
// },
{
title: 'Delete',
type: 'delete',
@ -299,7 +299,12 @@ describe('Table.settings', () => {
test('menu list', async () => {
await renderSettings(getRenderSettingsOptions());
await checkTableSettings();
await checkTableSettings([
{
title: 'Save as template',
type: 'modal',
},
]);
});
test('old schema', async () => {

View File

@ -45,9 +45,9 @@ export const Sortable = (props: any) => {
const { draggable, droppable } = useContext(SortableContext);
const { isOver, setNodeRef } = droppable;
const droppableStyle = { ...style };
const isLinkComponent = component === 'a' || component?.displayName === 'WrapperComponentLink';
if (isOver && draggable?.active?.id !== droppable?.over?.id) {
droppableStyle[component === 'a' ? 'color' : 'background'] = getComputedColor(token.colorSettings);
droppableStyle[isLinkComponent ? 'color' : 'background'] = getComputedColor(token.colorSettings);
Object.assign(droppableStyle, overStyle);
}
@ -118,7 +118,6 @@ export const SortableItem: React.FC<SortableItemProps> = React.memo((props) => {
if (designable) {
return <InternalSortableItem {...props} />;
}
return React.createElement(
component || 'div',
_.omit(others, ['children', 'schema', 'overStyle', 'openMode', 'id', 'eid', 'removeParentsIfNoChildren']),

View File

@ -267,6 +267,13 @@ function FinallyButton({
}) {
const { getCollection } = useCollectionManager_deprecated();
const aclCtx = useACLActionParamsContext();
const buttonStyle = useMemo(() => {
const shouldApplyOpacity = designable && (field?.data?.hidden || !aclCtx);
const opacityValue = componentType !== 'link' ? (shouldApplyOpacity ? 0.1 : 1) : 1;
return {
opacity: opacityValue,
};
}, [designable, field?.data?.hidden, aclCtx, componentType]);
if (inheritsCollections?.length > 0) {
if (!linkageFromForm) {
@ -276,6 +283,7 @@ function FinallyButton({
danger={props.danger}
type={componentType}
icon={<DownOutlined />}
style={{ ...props?.style, ...buttonStyle }}
buttonsRender={([leftButton, rightButton]) => [
React.cloneElement(leftButton as React.ReactElement<any, string>, {
style: props?.style,
@ -296,7 +304,13 @@ function FinallyButton({
) : (
<Dropdown menu={menu}>
{
<Button aria-label={props['aria-label']} icon={icon} type={componentType} danger={props.danger}>
<Button
aria-label={props['aria-label']}
icon={icon}
type={componentType}
danger={props.danger}
style={{ ...props?.style, ...buttonStyle }}
>
{props.children} <DownOutlined />
</Button>
}
@ -321,6 +335,7 @@ function FinallyButton({
style={{
display: !designable && field?.data?.hidden && 'none',
opacity: designable && field?.data?.hidden && 0.1,
...buttonStyle,
}}
>
{props.children}
@ -342,7 +357,7 @@ function FinallyButton({
...props?.style,
display: !designable && field?.data?.hidden && 'none',
opacity: designable && field?.data?.hidden && 0.1,
height: '100%',
...buttonStyle,
}}
>
{props.onlyIcon ? props?.children?.[1] : props?.children}

View File

@ -57,16 +57,6 @@ export const useLinkageCollectionFieldOptions = (collectionName: string, readPre
if (nested || children || ['formula', 'richText', 'sequence'].includes(fieldInterface.name)) {
return operator?.value !== ActionType.Value && operator?.value !== ActionType.Options;
}
if (!['select', 'radioGroup', 'multipleSelect', 'checkboxGroup'].includes(fieldInterface.name)) {
return operator?.value !== ActionType.Options;
}
if (
!['date', 'datetime', 'dateOnly', 'datetimeNoTz', 'unixTimestamp', 'createdAt', 'updatedAt'].includes(
fieldInterface.name,
)
) {
return operator?.value !== ActionType.DateScope;
}
return true;
}) || [],
};

View File

@ -10,6 +10,7 @@
import { useField } from '@formily/react';
import { useEffect, useContext } from 'react';
import { LinkageLogicContext } from './context';
import { ActionType } from './type';
const findOption = (dataIndex = [], options) => {
let items = options;
@ -45,9 +46,34 @@ export const useValues = (options) => {
field.data = field.data || {};
const operators = option?.operators;
field.data.operators = operators?.filter((v) => {
if (dataIndex.length > 1) {
return !['value', 'dateScope', 'options'].includes(v.value);
const isOptionField = ['select', 'radioGroup', 'multipleSelect', 'checkboxGroup'].includes(
option?.interface || '',
);
const isDateField = [
'date',
'datetime',
'dateOnly',
'datetimeNoTz',
'unixTimestamp',
'createdAt',
'updatedAt',
].includes(option?.interface || '');
// 如果 多个字段,则排除 Value、DateScope、Options
if (dataIndex.length > 1 && [ActionType.Value, ActionType.DateScope, ActionType.Options].includes(v.value)) {
return false;
}
// 非选项字段,去掉 Options
if (!isOptionField && v.value === ActionType.Options) {
return false;
}
// 非时间字段,去掉 DateScope
if (!isDateField && v.value === ActionType.DateScope) {
return false;
}
return true;
});
field.data.schema = option?.schema;

View File

@ -18,7 +18,6 @@ import { getDataSourceHeaders } from '../data-source/utils';
import { useCompile } from '../schema-component';
import useBuiltInVariables from './hooks/useBuiltinVariables';
import { VariableOption, VariablesContextType } from './types';
import { cacheLazyLoadedValues, getCachedLazyLoadedValues } from './utils/cacheLazyLoadedValues';
import { filterEmptyValues } from './utils/filterEmptyValues';
import { getAction } from './utils/getAction';
import { getPath } from './utils/getPath';
@ -144,14 +143,13 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
.then((data) => {
clearRequested(url);
const value = data.data.data;
cacheLazyLoadedValues(item, currentVariablePath, value);
return value;
});
stashRequested(url, result);
return result;
}
}
return getCachedLazyLoadedValues(item, currentVariablePath) || item?.[key];
return item?.[key];
});
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
} else if (
@ -180,17 +178,9 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
}
const value = data.data.data;
if (!getCachedLazyLoadedValues(current, currentVariablePath)) {
// Cache the API response data to avoid repeated requests
cacheLazyLoadedValues(current, currentVariablePath, value);
}
current = removeThroughCollectionFields(value, associationField);
} else {
current = removeThroughCollectionFields(
getCachedLazyLoadedValues(current, currentVariablePath) || getValuesByPath(current, key),
associationField,
);
current = removeThroughCollectionFields(getValuesByPath(current, key), associationField);
}
if (associationField?.target) {
@ -359,13 +349,8 @@ export default VariablesProvider;
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
let result = false;
if (getCachedLazyLoadedValues(variableCtx, variablePath)) {
return false;
}
// value may be a reactive object, using untracked to avoid unexpected autorun
untracked(() => {
// fix https://nocobase.height.app/T-2502
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
result = true;

View File

@ -1,25 +0,0 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
const cache = new Map<Record<string, any>, any>();
export const cacheLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string, value: any) => {
const cachedValue = cache.get(variableCtx);
if (cachedValue) {
cachedValue[variablePath] = value;
} else {
cache.set(variableCtx, { [variablePath]: value });
}
};
export const getCachedLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string) => {
const cachedValue = cache.get(variableCtx);
return cachedValue?.[variablePath];
};

View File

@ -1,6 +1,6 @@
{
"name": "create-nocobase-app",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "src/index.js",
"license": "AGPL-3.0",
"dependencies": {

View File

@ -1,16 +1,16 @@
{
"name": "@nocobase/data-source-manager",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"@nocobase/actions": "1.7.0-alpha.4",
"@nocobase/cache": "1.7.0-alpha.4",
"@nocobase/database": "1.7.0-alpha.4",
"@nocobase/resourcer": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/actions": "1.7.0-beta.9",
"@nocobase/cache": "1.7.0-beta.9",
"@nocobase/database": "1.7.0-beta.9",
"@nocobase/resourcer": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"@types/jsonwebtoken": "^8.5.8",
"jsonwebtoken": "^8.5.1"
},

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/database",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@nocobase/logger": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/logger": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"async-mutex": "^0.3.2",
"chalk": "^4.1.1",
"cron-parser": "4.4.0",

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/devtools",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"license": "AGPL-3.0",
"main": "./src/index.js",
"dependencies": {
"@nocobase/build": "1.7.0-alpha.4",
"@nocobase/client": "1.7.0-alpha.4",
"@nocobase/test": "1.7.0-alpha.4",
"@nocobase/build": "1.7.0-beta.9",
"@nocobase/client": "1.7.0-beta.9",
"@nocobase/test": "1.7.0-beta.9",
"@types/koa": "^2.15.0",
"@types/koa-bodyparser": "^4.3.4",
"@types/lodash": "^4.14.177",

View File

@ -1,13 +1,13 @@
{
"name": "@nocobase/evaluators",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@formulajs/formulajs": "4.4.9",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-beta.9",
"mathjs": "^10.6.0"
},
"repository": {

View File

@ -1,10 +1,10 @@
{
"name": "@nocobase/lock-manager",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "lib/index.js",
"license": "AGPL-3.0",
"devDependencies": {
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-beta.9",
"async-mutex": "^0.5.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/logger",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "nocobase logging library",
"license": "AGPL-3.0",
"main": "./lib/index.js",

View File

@ -1,12 +1,12 @@
{
"name": "@nocobase/resourcer",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
"dependencies": {
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-beta.9",
"deepmerge": "^4.2.2",
"koa-compose": "^4.1.0",
"lodash": "^4.17.21",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/sdk",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/server",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",
@ -10,19 +10,19 @@
"@koa/cors": "^5.0.0",
"@koa/multer": "^3.0.2",
"@koa/router": "^9.4.0",
"@nocobase/acl": "1.7.0-alpha.4",
"@nocobase/actions": "1.7.0-alpha.4",
"@nocobase/auth": "1.7.0-alpha.4",
"@nocobase/cache": "1.7.0-alpha.4",
"@nocobase/data-source-manager": "1.7.0-alpha.4",
"@nocobase/database": "1.7.0-alpha.4",
"@nocobase/evaluators": "1.7.0-alpha.4",
"@nocobase/lock-manager": "1.7.0-alpha.4",
"@nocobase/logger": "1.7.0-alpha.4",
"@nocobase/resourcer": "1.7.0-alpha.4",
"@nocobase/sdk": "1.7.0-alpha.4",
"@nocobase/telemetry": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/acl": "1.7.0-beta.9",
"@nocobase/actions": "1.7.0-beta.9",
"@nocobase/auth": "1.7.0-beta.9",
"@nocobase/cache": "1.7.0-beta.9",
"@nocobase/data-source-manager": "1.7.0-beta.9",
"@nocobase/database": "1.7.0-beta.9",
"@nocobase/evaluators": "1.7.0-beta.9",
"@nocobase/lock-manager": "1.7.0-beta.9",
"@nocobase/logger": "1.7.0-beta.9",
"@nocobase/resourcer": "1.7.0-beta.9",
"@nocobase/sdk": "1.7.0-beta.9",
"@nocobase/telemetry": "1.7.0-beta.9",
"@nocobase/utils": "1.7.0-beta.9",
"@types/decompress": "4.2.7",
"@types/ini": "^1.3.31",
"@types/koa-send": "^4.1.3",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/telemetry",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"description": "nocobase telemetry library",
"license": "AGPL-3.0",
"main": "./lib/index.js",
@ -11,7 +11,7 @@
"directory": "packages/telemetry"
},
"dependencies": {
"@nocobase/utils": "1.7.0-alpha.4",
"@nocobase/utils": "1.7.0-beta.9",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/instrumentation": "^0.46.0",
"@opentelemetry/resources": "^1.19.0",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/test",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "lib/index.js",
"module": "./src/index.ts",
"types": "./lib/index.d.ts",
@ -51,7 +51,7 @@
},
"dependencies": {
"@faker-js/faker": "8.1.0",
"@nocobase/server": "1.7.0-alpha.4",
"@nocobase/server": "1.7.0-beta.9",
"@playwright/test": "^1.45.3",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/utils",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "lib/index.js",
"types": "./lib/index.d.ts",
"license": "AGPL-3.0",

View File

@ -37,7 +37,7 @@ vi.mock('@formily/json-schema', () => {
const result = { ...schema, parent };
result._isJSONSchemaObject = true;
return result;
})
}),
};
});
@ -271,16 +271,16 @@ describe('transformMultiColumnToSingleColumn', () => {
},
parent: {
properties: {
grid1: {} // Will be replaced by result
}
grid1: {}, // Will be replaced by result
},
},
toJSON: vi.fn().mockImplementation(function () {
return {
name: this.name,
'x-component': this['x-component'],
properties: this.properties
properties: this.properties,
};
})
}),
};
const result = transformMultiColumnToSingleColumn(mockSchema);

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "权限控制",
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/acl",

View File

@ -276,7 +276,8 @@ export const MenuPermissions: React.FC<{
expandable={{
defaultExpandAllRows: false,
}}
columns={[
columns={
[
{
dataIndex: 'title',
title: t('Route name'),
@ -310,7 +311,8 @@ export const MenuPermissions: React.FC<{
return <Checkbox checked={checked} onChange={() => handleChange(checked, schema)} />;
},
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={translateTitle(items, t, compile)}
/>
</>

View File

@ -113,7 +113,8 @@ export const PluginPermissions: React.FC<{
expandable={{
defaultExpandAllRows: true,
}}
columns={[
columns={
[
{
dataIndex: 'title',
title: t('Plugin name'),
@ -150,7 +151,8 @@ export const PluginPermissions: React.FC<{
return <Checkbox checked={checked} onChange={() => handleChange(checked, record)} />;
},
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={settings
.filter((v) => {
return v.isTopLevel !== false;

View File

@ -106,7 +106,8 @@ export const RolesResourcesActions = connect((props) => {
className={styles}
size={'small'}
pagination={false}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -147,7 +148,8 @@ export const RolesResourcesActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let enabled = false;
let scope = null;
@ -170,7 +172,8 @@ export const RolesResourcesActions = connect((props) => {
className={styles}
pagination={false}
dataSource={fieldPermissions}
columns={[
columns={
[
{
dataIndex: ['uiSchema', 'title'],
title: t('Field display name'),
@ -223,7 +226,8 @@ export const RolesResourcesActions = connect((props) => {
),
};
}),
] as TableProps['columns']}
] as TableProps['columns']
}
/>
</FormItem>
</FormLayout>

View File

@ -55,7 +55,8 @@ export const StrategyActions = connect((props) => {
rowKey={'name'}
size={'small'}
pagination={false}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -110,7 +111,8 @@ export const StrategyActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let scope = 'all';
let enabled = false;

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-edit",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-bulk-update",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",

View File

@ -8,7 +8,6 @@
*/
import { ISchema, useFieldSchema } from '@formily/react';
import { isValid } from '@formily/shared';
import {
ActionDesigner,
SchemaSettings,
@ -19,6 +18,8 @@ import {
useDesignable,
useSchemaToolbar,
RefreshDataBlockRequest,
useAfterSuccessOptions,
useGlobalVariable,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import React from 'react';
@ -49,11 +50,22 @@ function UpdateMode() {
/>
);
}
const fieldNames = {
value: 'value',
label: 'label',
};
const useVariableProps = (environmentVariables) => {
const scope = useAfterSuccessOptions();
return {
scope: [environmentVariables, ...scope].filter(Boolean),
fieldNames,
};
};
function AfterSuccess() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const environmentVariables = useGlobalVariable('$env');
return (
<SchemaSettingsModalItem
title={t('After successful submission')}
@ -100,8 +112,9 @@ function AfterSuccess() {
redirectTo: {
title: t('Link'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
'x-component': 'Variable.TextArea',
// eslint-disable-next-line react-hooks/rules-of-hooks
'x-use-component-props': () => useVariableProps(environmentVariables),
},
},
} as ISchema

View File

@ -68,7 +68,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
} else if (value !== '') {
assignedValues[key] = value;
}
});

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-custom-request",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",

View File

@ -68,10 +68,13 @@ export const useCustomizeRequestActionProps = () => {
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
});
if (res.headers['content-disposition']) {
const regex = /attachment;\s*filename="([^"]+)"/;
const match = res.headers['content-disposition'].match(regex);
if (match?.[1]) {
saveAs(res.data, match[1]);
const contentDisposition = res.headers['content-disposition'];
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
const asciiMatch = contentDisposition.match(/filename="([^"]+)"/i);
if (utf8Match) {
saveAs(res.data, decodeURIComponent(utf8Match[1]));
} else if (asciiMatch) {
saveAs(res.data, asciiMatch[1]);
}
}
actionField.data.loading = false;

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-duplicate",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-duplicate",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导出记录",
"description": "Export filtered records to excel, you can configure which fields to export.",
"description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-export",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "操作:导入记录",
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-import",

View File

@ -12,6 +12,7 @@ import { ISchema, useFieldSchema } from '@formily/react';
import { Action, ActionContextProvider, PopupSettingsProvider, SchemaComponent, useCompile } from '@nocobase/client';
import React, { useState } from 'react';
import { NAMESPACE } from './constants';
import { useTranslation } from 'react-i18next';
import { UploadOutlined } from '@ant-design/icons';
const importFormSchema: ISchema = {
@ -128,7 +129,7 @@ export const ImportAction = (props) => {
return (
<ActionContextProvider value={{ visible, setVisible, fieldSchema }}>
<Action
icon={props.icon || <UploadOutlined />}
icon={props.icon || 'uploadoutlined'}
title={compile(fieldSchema?.title || "t('Import')")}
{...props}
onClick={() => setVisible(true)}

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-action-print",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/action-print",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "AI 集成",
"description": "Support integration with AI services, providing AI-related workflow nodes to enhance business processing capabilities.",
"description.zh-CN": "支持接入 AI 服务,提供 AI 相关的工作流节点,增强业务处理能力。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-api-doc",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"displayName": "API documentation",
"displayName.zh-CN": "API 文档",
"description": "An OpenAPI documentation generator for NocoBase HTTP API.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证API 密钥",
"description": "Allows users to use API key to access application's HTTP API",
"description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/api-keys",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "异步任务管理器",
"description": "Manage and monitor asynchronous tasks such as data import/export. Support task progress tracking and notification.",
"description.zh-CN": "管理和监控数据导入导出等异步任务。支持任务进度跟踪和通知。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"peerDependencies": {
"@nocobase/client": "1.x",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-audit-logs",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"displayName": "Audit logs (deprecated)",
"displayName.zh-CN": "审计日志(废弃)",
"description": "This plugin is deprecated. There will be a new audit log plugin in the future.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "认证:短信",
"description": "SMS authentication.",
"description.zh-CN": "通过短信验证码认证身份。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth-sms",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-auth",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/auth",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "应用的备份与还原(废弃)",
"description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.",
"description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/backup-restore",

View File

@ -111,11 +111,11 @@ const LearnMore: any = (props: { collectionsData?: any; isBackup?: boolean }) =>
render: (_, data) => {
const title = compile(data.title);
const name = data.name;
return name === title ? title : (
return name === title ? (
title
) : (
<div>
{data.name}
{' '}
<span style={{ color: 'rgba(0, 0, 0, 0.3)', fontSize: '0.9em' }}>({compile(data.title)})</span>
{data.name} <span style={{ color: 'rgba(0, 0, 0, 0.3)', fontSize: '0.9em' }}>({compile(data.title)})</span>
</div>
);
},
@ -418,7 +418,8 @@ export const BackupAndRestoreList = () => {
<Table
dataSource={dataSource}
loading={loading}
columns={[
columns={
[
{
title: t('Backup file'),
dataIndex: 'name',
@ -484,7 +485,8 @@ export const BackupAndRestoreList = () => {
</Space>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
/>
</Card>
</div>

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "区块iframe",
"description": "Create an iframe block on the page to embed and display external web pages or content.",
"description.zh-CN": "在页面上创建和管理iframe用于嵌入和展示外部网页或内容。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-iframe",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "区块:模板",
"description": "Create and manage block templates for reuse on pages.",
"description.zh-CN": "创建和管理区块模板,用于在页面中重复使用。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"license": "AGPL-3.0",
"main": "dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/block-template",

View File

@ -23,7 +23,7 @@ import { useLocation } from 'react-router-dom';
const blockDecoratorMenuMaps = {
TableBlockProvider: ['Table', 'table'],
FormBlockProvider: ['Form', 'form'],
FormBlockProvider: ['FormItem', 'form'],
DetailsBlockProvider: ['Details', 'details'],
'List.Decorator': ['List', 'list'],
'GridCard.Decorator': ['GridCard', 'gridCard'],
@ -281,6 +281,7 @@ function getTemplateSchemaFromPage(schema: ISchema) {
if (s['x-template-root-uid']) {
return;
}
t = t || {};
_.merge(t, _.omit(s, ['x-uid', 'properties']));
t['x-uid'] = uid();
if (s.properties) {
@ -288,7 +289,7 @@ function getTemplateSchemaFromPage(schema: ISchema) {
if (s.properties[key]['x-template-root-uid']) {
continue;
}
_.set(t, `properties.${key}`, {});
_.set(t, `properties.['${key}']`, {});
traverseSchema(s.properties[key], t.properties[key]);
}
}

View File

@ -169,6 +169,9 @@ export class PluginBlockTemplateClient extends Plugin {
'blockSettings:createForm',
'blockSettings:details',
'blockSettings:detailsWithPagination',
'blockSettings:multiDataDetails',
'blockSettings:singleDataDetails',
'blockSettings:stepsForm',
'blockSettings:filterCollapse',
'blockSettings:filterForm',
'blockSettings:gantt',
@ -176,6 +179,13 @@ export class PluginBlockTemplateClient extends Plugin {
'blockSettings:kanban',
'blockSettings:list',
'blockSettings:table',
'blockSettings:tree',
'ReadPrettyFormSettings',
'GanttBlockSettings',
'FormV1Settings',
'FormSettings',
'FormItemSettings',
'FormDetailsSettings',
];
if (blockSettings.includes(key)) {
// schemaSetting.add('template-saveAsTemplateItem', saveAsTemplateSetting);

View File

@ -43,6 +43,10 @@ export function convertTplBlock(
if (newSchema['x-decorator'] === 'TemplateGridDecorator') {
delete newSchema['x-decorator'];
}
if (newSchema['x-linkage-rules']) {
// linkage rules 有可能保存在Grid组件中
delete newSchema['x-linkage-rules'];
}
for (const key in tpl.properties) {
const t = convertTplBlock(tpl.properties[key], virtual, isRoot, newRootId, templateKey, options);
if (isRoot) {
@ -154,8 +158,12 @@ export function formSchemaPatch(currentSchema: ISchema, options?: any) {
return key !== 'grid';
});
if (actionKey) {
_.set(currentSchema, `properties.${comKey}.x-use-component-props`, 'useEditFormBlockProps');
_.set(currentSchema, `properties.${comKey}.properties.${actionKey}.x-initializer`, 'editForm:configureActions');
_.set(currentSchema, `properties.['${comKey}'].x-use-component-props`, 'useEditFormBlockProps');
_.set(
currentSchema,
`properties.['${comKey}'].properties.['${actionKey}'].x-initializer`,
'editForm:configureActions',
);
const actionBarSchema = _.get(currentSchema, `properties.${comKey}.properties.${actionKey}.properties`, {});
for (const key in actionBarSchema) {

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-block-workbench",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"displayName": "Block: Action panel",
"displayName.zh-CN": "区块:操作面板",
"description": "Centrally manages and displays various actions, allowing users to efficiently perform tasks. It supports extensibility, with current action types including pop-ups, links, scanning, and custom requests.",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-calendar",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"displayName": "Calendar",
"displayName.zh-CN": "日历",
"description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.",

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "图表(废弃)",
"description": "The plugin has been deprecated, please use the data visualization plugin instead.",
"description.zh-CN": "已废弃插件,请使用数据可视化插件代替。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "WEB 客户端",
"description": "Provides a client interface for the NocoBase server",
"description.zh-CN": "为 NocoBase 服务端提供客户端界面",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "./dist/server/index.js",
"license": "AGPL-3.0",
"devDependencies": {

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据表: SQL",
"description": "Provides SQL collection template",
"description.zh-CN": "提供 SQL 数据表模板",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"homepage": "https://docs-cn.nocobase.com/handbook/collection-sql",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/collection-sql",
"main": "dist/server/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-collection-tree",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"displayName": "Collection: Tree",
"displayName.zh-CN": "数据表:树",
"description": "Provides tree collection template",

View File

@ -31,6 +31,8 @@ export default class extends Migration {
await this.db.sequelize.transaction(async (transaction) => {
const treeCollections = await this.getTreeCollections({ transaction });
for (const treeCollection of treeCollections) {
await treeCollection.load({ transaction });
const name = `main_${treeCollection.name}_path`;
const collectionOptions = {
name,
@ -47,35 +49,16 @@ export default class extends Migration {
},
],
};
const collectionInstance = this.db.getCollection(treeCollection.name);
const treeCollectionSchema = collectionInstance.collectionSchema();
if (this.app.db.inDialect('postgres') && treeCollectionSchema != this.app.db.options.schema) {
collectionOptions['schema'] = treeCollectionSchema;
}
this.app.db.collection(collectionOptions);
const treeExistsInDb = await this.app.db.getCollection(name).existsInDb({ transaction });
if (!treeExistsInDb) {
await this.app.db.getCollection(name).sync({ transaction } as SyncOptions);
const opts = {
name: treeCollection.name,
autoGenId: false,
timestamps: false,
fields: [
{ type: 'integer', name: 'id' },
{ type: 'integer', name: 'parentId' },
],
};
if (treeCollectionSchema != this.app.db.options.schema) {
opts['schema'] = treeCollectionSchema;
}
this.app.db.collection(opts);
const chunkSize = 1000;
await this.app.db.getRepository(treeCollection.name).chunk({
chunkSize: chunkSize,

View File

@ -4,7 +4,7 @@
"displayName.zh-CN": "数据源:主数据库",
"description": "NocoBase main database, supports relational databases such as PostgreSQL, MySQL, MariaDB and so on.",
"description.zh-CN": "NocoBase 主数据库,支持 PostgreSQL、MySQL、MariaDB 等关系型数据库。",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "./dist/server/index.js",
"homepage": "https://docs.nocobase.com/handbook/data-source-main",
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main",

View File

@ -1,6 +1,6 @@
{
"name": "@nocobase/plugin-data-source-manager",
"version": "1.7.0-alpha.4",
"version": "1.7.0-beta.9",
"main": "dist/server/index.js",
"displayName": "Data source manager",
"displayName.zh-CN": "数据源管理",

View File

@ -101,7 +101,8 @@ export const RolesResourcesActions = connect((props) => {
className={styles}
size={'small'}
pagination={false}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -142,7 +143,8 @@ export const RolesResourcesActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let enabled = false;
let scope = null;
@ -165,7 +167,8 @@ export const RolesResourcesActions = connect((props) => {
className={styles}
pagination={false}
dataSource={fieldPermissions}
columns={[
columns={
[
{
dataIndex: ['uiSchema', 'title'],
title: t('Field display name'),
@ -217,7 +220,8 @@ export const RolesResourcesActions = connect((props) => {
),
};
}),
] as TableProps['columns']}
] as TableProps['columns']
}
/>
</FormItem>
</FormLayout>

View File

@ -56,7 +56,8 @@ export const StrategyActions = connect((props) => {
size={'small'}
pagination={false}
rowKey={'name'}
columns={[
columns={
[
{
dataIndex: 'displayName',
title: t('Action display name'),
@ -110,7 +111,8 @@ export const StrategyActions = connect((props) => {
/>
),
},
] as TableProps['columns']}
] as TableProps['columns']
}
dataSource={availableActions?.map((item) => {
let scope = 'all';
let enabled = false;

Some files were not shown because too many files have changed in this diff Show More