mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
* fix: update FilterContext to allow null parent value * fix: refactor context usage from FilterContext to DataBlocksContext in FilterProvider and SchemaSettings * feat: add highlight and unhighlight functionality for data blocks in SchemaSettings * Revert "fix: refactor context usage from FilterContext to DataBlocksContext in FilterProvider and SchemaSettings" This reverts commit a75c7002010785f1cfd2e78c4f5998d0194366bc. * Revert "fix: update FilterContext to allow null parent value" This reverts commit 6eb0b1989e20be8310f8dbce4e875e862123f2b3. * feat: add AllDataBlocksProvider and integrate it into SchemaSettings and Page components * feat: add BlocksSelector component and integrate data block refresh functionality in Action and SchemaSettings * feat: optimize handleClick to use useMemo for better performance and refresh data blocks after onClick * feat: add dialog visibility control in BlocksSelector for improved user experience * fix: avoid error * feat: add highlight and scroll tracking functionality for data blocks * feat: add transition * feat: add tootip * fix: prevent closed dialog blocks from appearing in the BlocksSelector options * fix: handle errors during block refresh to prevent crashes * chore: fix build * feat: add AllDataBlocksProvider to BlockTemplatePage and export from index * feat: set width for AfterSuccess dialog to 700 * feat: wrap MobileRouter with AllDataBlocksProvider for improved data handling * feat: export BlocksSelector component and integrate into AfterSuccess settings * fix: ensure container visibility is managed correctly in highlightBlock and unhighlightBlock functions * fix: remove unnecessary display property manipulation in highlightBlock and simplify unhighlightBlock logic * chore: hide data refresh after sucess option from block template configure page * fix: revert code format --------- Co-authored-by: gchust <gchust@qq.com>
This commit is contained in:
parent
23d7e09fa5
commit
ba83b2b1be
@ -19,6 +19,7 @@ import { mergeFilter, useAssociatedFields } from './utils';
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { useAllDataBlocks } from '../schema-component/antd/page/AllDataBlocksProvider';
|
||||||
|
|
||||||
enum FILTER_OPERATOR {
|
enum FILTER_OPERATOR {
|
||||||
AND = '$and',
|
AND = '$and',
|
||||||
@ -71,6 +72,10 @@ export interface DataBlock {
|
|||||||
* manual: 只有当点击了筛选按钮,才会请求数据
|
* manual: 只有当点击了筛选按钮,才会请求数据
|
||||||
*/
|
*/
|
||||||
dataLoadingMode?: 'auto' | 'manual';
|
dataLoadingMode?: 'auto' | 'manual';
|
||||||
|
/** 让整个区块悬浮起来 */
|
||||||
|
highlightBlock: () => void;
|
||||||
|
/** 取消悬浮 */
|
||||||
|
unhighlightBlock: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterContextValue {
|
interface FilterContextValue {
|
||||||
@ -124,7 +129,7 @@ export const DataBlockCollector = ({
|
|||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const associatedFields = useAssociatedFields();
|
const associatedFields = useAssociatedFields();
|
||||||
const container = useRef(null);
|
const container = useRef<HTMLDivElement | null>(null);
|
||||||
const dataLoadingMode = useDataLoadingMode();
|
const dataLoadingMode = useDataLoadingMode();
|
||||||
|
|
||||||
const shouldApplyFilter =
|
const shouldApplyFilter =
|
||||||
@ -172,6 +177,34 @@ export const DataBlockCollector = ({
|
|||||||
field.data?.clearSelectedRowKeys?.();
|
field.data?.clearSelectedRowKeys?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
highlightBlock() {
|
||||||
|
const dom = container.current;
|
||||||
|
|
||||||
|
if (!dom) return;
|
||||||
|
|
||||||
|
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||||
|
if (designer) {
|
||||||
|
designer.classList.remove(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = '0 3px 12px rgba(0, 0, 0, 0.15)';
|
||||||
|
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||||
|
dom.scrollIntoView?.({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'start',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unhighlightBlock() {
|
||||||
|
const dom = container.current;
|
||||||
|
|
||||||
|
if (!dom) return;
|
||||||
|
|
||||||
|
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||||
|
if (designer) {
|
||||||
|
designer.classList.add(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||||
|
}
|
||||||
|
dom.style.boxShadow = 'none';
|
||||||
|
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
associatedFields,
|
associatedFields,
|
||||||
@ -197,12 +230,14 @@ export const DataBlockCollector = ({
|
|||||||
*/
|
*/
|
||||||
export const useFilterBlock = () => {
|
export const useFilterBlock = () => {
|
||||||
const ctx = React.useContext(FilterContext);
|
const ctx = React.useContext(FilterContext);
|
||||||
|
const allDataBlocksCtx = useAllDataBlocks();
|
||||||
|
|
||||||
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
||||||
const getDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getDataBlocks() || [], [ctx]);
|
const getDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getDataBlocks() || [], [ctx]);
|
||||||
|
|
||||||
const recordDataBlocks = useCallback(
|
const recordDataBlocks = useCallback(
|
||||||
(block: DataBlock) => {
|
(block: DataBlock) => {
|
||||||
|
allDataBlocksCtx.recordDataBlocks(block);
|
||||||
const existingBlock = ctx?.getDataBlocks().find((item) => item.uid === block.uid);
|
const existingBlock = ctx?.getDataBlocks().find((item) => item.uid === block.uid);
|
||||||
|
|
||||||
if (existingBlock) {
|
if (existingBlock) {
|
||||||
@ -218,6 +253,7 @@ export const useFilterBlock = () => {
|
|||||||
|
|
||||||
const removeDataBlock = useCallback(
|
const removeDataBlock = useCallback(
|
||||||
(uid: string) => {
|
(uid: string) => {
|
||||||
|
allDataBlocksCtx.removeDataBlock(uid);
|
||||||
if (ctx?.getDataBlocks().every((item) => item.uid !== uid)) return;
|
if (ctx?.getDataBlocks().every((item) => item.uid !== uid)) return;
|
||||||
ctx?.setDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
ctx?.setDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
||||||
},
|
},
|
||||||
|
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
let container: HTMLElement | null = null;
|
||||||
|
|
||||||
|
export const highlightBlock = (clonedBlockDom: HTMLElement, boxRect: DOMRect) => {
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
container.style.position = 'absolute';
|
||||||
|
container.style.transition = 'opacity 0.3s ease';
|
||||||
|
container.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(clonedBlockDom);
|
||||||
|
container.style.opacity = '1';
|
||||||
|
container.style.width = `${boxRect.width}px`;
|
||||||
|
container.style.height = `${boxRect.height}px`;
|
||||||
|
container.style.top = `${boxRect.top}px`;
|
||||||
|
container.style.left = `${boxRect.left}px`;
|
||||||
|
container.style.zIndex = '2000';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unhighlightBlock = () => {
|
||||||
|
if (container) {
|
||||||
|
container.style.opacity = '0';
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startScrollEndTracking = (dom: HTMLElement & { _prevRect?: DOMRect; _timer?: any }, callback: () => void) => {
|
||||||
|
dom._timer = setInterval(() => {
|
||||||
|
const prevRect = dom._prevRect;
|
||||||
|
const currentRect = dom.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (!prevRect || currentRect.top !== prevRect.top) {
|
||||||
|
dom._prevRect = currentRect;
|
||||||
|
} else {
|
||||||
|
clearInterval(dom._timer);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopScrollEndTracking = (dom: HTMLElement & { _timer?: any }) => {
|
||||||
|
if (dom._timer) {
|
||||||
|
clearInterval(dom._timer);
|
||||||
|
dom._timer = null;
|
||||||
|
}
|
||||||
|
}
|
@ -886,5 +886,8 @@
|
|||||||
"Are you sure you want to hide this tab?": "Sind Sie sicher, dass Sie diesen Tab ausblenden möchten?",
|
"Are you sure you want to hide this tab?": "Sind Sie sicher, dass Sie diesen Tab ausblenden möchten?",
|
||||||
"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.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen.",
|
"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.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen.",
|
||||||
"No pages yet, please configure first": "Noch keine Seiten, bitte zuerst konfigurieren",
|
"No pages yet, please configure first": "Noch keine Seiten, bitte zuerst konfigurieren",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Klicken Sie auf das \"UI-Editor\"-Symbol in der oberen rechten Ecke, um den UI-Editor-Modus zu betreten"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Klicken Sie auf das \"UI-Editor\"-Symbol in der oberen rechten Ecke, um den UI-Editor-Modus zu betreten",
|
||||||
|
"Refresh data blocks": "Aktualisieren Sie die Datenblöcke",
|
||||||
|
"Select data blocks to refresh": "Wählen Sie die Datenblöcke aus, die aktualisiert werden sollen.",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Nach erfolgreicher Übermittlung werden die ausgewählten Datenblöcke automatisch aktualisiert."
|
||||||
}
|
}
|
||||||
|
@ -890,5 +890,9 @@
|
|||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode",
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode",
|
||||||
"Deprecated": "Deprecated",
|
"Deprecated": "Deprecated",
|
||||||
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
||||||
"Full permissions": "Full permissions"
|
"Full permissions": "Full permissions",
|
||||||
|
"Refresh data blocks": "Refresh data blocks",
|
||||||
|
"Select data blocks to refresh": "Select data blocks to refresh",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed."
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -807,5 +807,8 @@
|
|||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Haga clic en el icono \"Editor de UI\" en la esquina superior derecha para entrar en el modo de Editor de UI.",
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Haga clic en el icono \"Editor de UI\" en la esquina superior derecha para entrar en el modo de Editor de UI.",
|
||||||
"Deprecated": "Obsoleto",
|
"Deprecated": "Obsoleto",
|
||||||
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
||||||
"Full permissions": "Todos los derechos"
|
"Full permissions": "Todos los derechos",
|
||||||
|
"Refresh data blocks": "Actualizar bloques de datos",
|
||||||
|
"Select data blocks to refresh": "Actualizar bloques de datos",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Después de enviar correctamente, los bloques de datos seleccionados se actualizarán automáticamente."
|
||||||
}
|
}
|
||||||
|
@ -827,5 +827,8 @@
|
|||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||||
"Deprecated": "Déprécié",
|
"Deprecated": "Déprécié",
|
||||||
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
||||||
"Full permissions": "Tous les droits"
|
"Full permissions": "Tous les droits",
|
||||||
|
"Refresh data blocks": "Actualiser les blocs de données",
|
||||||
|
"Select data blocks to refresh": "Actualiser les blocs de données",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Après une soumission réussie, les blocs de données sélectionnés seront automatiquement actualisés."
|
||||||
}
|
}
|
||||||
|
@ -1082,5 +1082,8 @@
|
|||||||
"Are you sure you want to hide this tab?": "Sei sicuro di voler nascondere questa scheda?",
|
"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.",
|
"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.",
|
||||||
"No pages yet, please configure first": "Nessuna pagina ancora, si prega di configurare prima",
|
"No pages yet, please configure first": "Nessuna pagina ancora, si prega di configurare prima",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||||
|
"Refresh data blocks": "Aggiorna blocchi di dati",
|
||||||
|
"Select data blocks to refresh": "Aggiorna blocchi di dati",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Dopo una soumission réussie, les blocs de données sélectionnés seront automatiquement actualisés."
|
||||||
}
|
}
|
||||||
|
@ -1045,5 +1045,8 @@
|
|||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "ユーザーインターフェースエディターモードに入るには、右上隅の「UIエディタ」アイコンをクリックしてください",
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "ユーザーインターフェースエディターモードに入るには、右上隅の「UIエディタ」アイコンをクリックしてください",
|
||||||
"Deprecated": "非推奨",
|
"Deprecated": "非推奨",
|
||||||
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
||||||
"Full permissions": "すべての権限"
|
"Full permissions": "すべての権限",
|
||||||
|
"Refresh data blocks": "データブロックを更新",
|
||||||
|
"Select data blocks to refresh": "データブロックを選択して更新",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "送信後、選択したデータブロックが自動的に更新されます。"
|
||||||
}
|
}
|
||||||
|
@ -918,5 +918,8 @@
|
|||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "사용자 인터페이스 편집기 모드에 들어가려면 오른쪽 상단의 \"UI 편집기\" 아이콘을 클릭하십시오",
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "사용자 인터페이스 편집기 모드에 들어가려면 오른쪽 상단의 \"UI 편집기\" 아이콘을 클릭하십시오",
|
||||||
"Deprecated": "사용 중단됨",
|
"Deprecated": "사용 중단됨",
|
||||||
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
||||||
"Full permissions": "모든 권한"
|
"Full permissions": "모든 권한",
|
||||||
|
"Refresh data blocks": "데이터 블록 새로 고침",
|
||||||
|
"Select data blocks to refresh": "데이터 블록을 선택하여 새로 고침",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "전송 후, 선택한 데이터 블록이 자동으로 새로 고쳐집니다."
|
||||||
}
|
}
|
||||||
|
@ -1054,5 +1054,8 @@
|
|||||||
"Font Size(px)": "Lettergrootte(px)",
|
"Font Size(px)": "Lettergrootte(px)",
|
||||||
"Font Weight": "Letterdikte",
|
"Font Weight": "Letterdikte",
|
||||||
"Font Style": "Letterstijl",
|
"Font Style": "Letterstijl",
|
||||||
"Italic": "Cursief"
|
"Italic": "Cursief",
|
||||||
|
"Refresh data blocks": "Vernieuw gegevensblokken",
|
||||||
|
"Select data blocks to refresh": "Selecteer gegevensblokken om te vernieuwen",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Na succesvolle indiening worden de geselecteerde gegevensblokken automatisch vernieuwd."
|
||||||
}
|
}
|
@ -784,5 +784,8 @@
|
|||||||
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
||||||
"Full permissions": "Todas as permissões",
|
"Full permissions": "Todas as permissões",
|
||||||
"No pages yet, please configure first": "Ainda não há páginas, por favor configure primeiro",
|
"No pages yet, please configure first": "Ainda não há páginas, por favor configure primeiro",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||||
|
"Refresh data blocks": "Atualizar blocos de dados",
|
||||||
|
"Select data blocks to refresh": "Selecionar blocos de dados para atualizar",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Após a atualização em massa bem sucedida."
|
||||||
}
|
}
|
||||||
|
@ -613,5 +613,8 @@
|
|||||||
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
||||||
"Full permissions": "Полные права",
|
"Full permissions": "Полные права",
|
||||||
"No pages yet, please configure first": "Пока нет страниц, пожалуйста, настройте сначала",
|
"No pages yet, please configure first": "Пока нет страниц, пожалуйста, настройте сначала",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Нажмите на значок \"Редактор пользовательского интерфейса\" в правом верхнем углу, чтобы войти в режим редактора пользовательского интерфейса"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Нажмите на значок \"Редактор пользовательского интерфейса\" в правом верхнем углу, чтобы войти в режим редактора пользовательского интерфейса",
|
||||||
|
"Refresh data blocks": "Обновить блоки данных",
|
||||||
|
"Select data blocks to refresh": "Выберите блоки данных для обновления",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "После успешной отправки выбранные блоки данных будут автоматически обновлены."
|
||||||
}
|
}
|
||||||
|
@ -611,5 +611,8 @@
|
|||||||
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
||||||
"Full permissions": "Tüm izinler",
|
"Full permissions": "Tüm izinler",
|
||||||
"No pages yet, please configure first": "Henüz sayfa yok, lütfen önce yapılandırın",
|
"No pages yet, please configure first": "Henüz sayfa yok, lütfen önce yapılandırın",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Kullanıcı arayüzü düzenleyici moduna girmek için sağ üst köşedeki \"Kullanıcı Arayüzü Düzenleyici\" simgesine tıklayın"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Kullanıcı arayüzü düzenleyici moduna girmek için sağ üst köşedeki \"Kullanıcı Arayüzü Düzenleyici\" simgesine tıklayın",
|
||||||
|
"Refresh data blocks": "Yenile veri blokları",
|
||||||
|
"Select data blocks to refresh": "Veri bloklarını yenilemek için seçin",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Başarılı bir şekilde gönderildikten sonra, seçilen veri blokları otomatik olarak yenilenecektir."
|
||||||
}
|
}
|
||||||
|
@ -827,5 +827,8 @@
|
|||||||
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
||||||
"Full permissions": "Повні права",
|
"Full permissions": "Повні права",
|
||||||
"No pages yet, please configure first": "Ще немає сторінок, будь ласка, спочатку налаштуйте",
|
"No pages yet, please configure first": "Ще немає сторінок, будь ласка, спочатку налаштуйте",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Натисніть на значок \"Редактор користувацького інтерфейсу\" в правому верхньому куті, щоб увійти в режим редактора користувацького інтерфейсу."
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Натисніть на значок \"Редактор користувацького інтерфейсу\" в правому верхньому куті, щоб увійти в режим редактора користувацького інтерфейсу.",
|
||||||
|
"Refresh data blocks": "Оновити дані блоків",
|
||||||
|
"Select data blocks to refresh": "Виберіть блоки даних для оновлення",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "Після успішної подачі вибрані блоки даних будуть автоматично оновлені."
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,8 @@
|
|||||||
"Year": "年",
|
"Year": "年",
|
||||||
"QuarterYear": "季度",
|
"QuarterYear": "季度",
|
||||||
"Select grouping field": "选择分组字段",
|
"Select grouping field": "选择分组字段",
|
||||||
|
"Refresh data blocks": "刷新数据区块",
|
||||||
|
"Select data blocks to refresh": "选择要刷新的数据区块",
|
||||||
"Media": "多媒体",
|
"Media": "多媒体",
|
||||||
"Markdown": "Markdown",
|
"Markdown": "Markdown",
|
||||||
"Wysiwyg": "富文本",
|
"Wysiwyg": "富文本",
|
||||||
@ -1099,5 +1101,6 @@
|
|||||||
"Response record":"响应结果记录",
|
"Response record":"响应结果记录",
|
||||||
"Colon":"冒号",
|
"Colon":"冒号",
|
||||||
"No pages yet, please configure first": "暂无页面,请先配置",
|
"No pages yet, please configure first": "暂无页面,请先配置",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。"
|
||||||
}
|
}
|
||||||
|
@ -918,5 +918,8 @@
|
|||||||
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
||||||
"Full permissions": "完全權限",
|
"Full permissions": "完全權限",
|
||||||
"No pages yet, please configure first": "尚未配置頁面,請先配置",
|
"No pages yet, please configure first": "尚未配置頁面,請先配置",
|
||||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "點擊右上角的 \"介面設定\" 圖示進入介面設定模式"
|
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "點擊右上角的 \"介面設定\" 圖示進入介面設定模式",
|
||||||
|
"Refresh data blocks": "刷新數據區塊",
|
||||||
|
"Select data blocks to refresh": "選擇要刷新的數據區塊",
|
||||||
|
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功後,選中的數據區塊將自動刷新。"
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,16 @@
|
|||||||
|
|
||||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
import { ISchema, useField, useFieldSchema } from '@formily/react';
|
||||||
import { isValid, uid } from '@formily/shared';
|
import { isValid, uid } from '@formily/shared';
|
||||||
import { ModalProps } from 'antd';
|
import { ModalProps, Select } from 'antd';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile, useDesignable } from '../..';
|
import { useCompile, useDesignable } from '../..';
|
||||||
import { isInitializersSame, useApp } from '../../../application';
|
import { isInitializersSame, useApp, usePlugin } from '../../../application';
|
||||||
|
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
||||||
import { SchemaSettingOptions, SchemaSettings } from '../../../application/schema-settings';
|
import { SchemaSettingOptions, SchemaSettings } from '../../../application/schema-settings';
|
||||||
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
import { useSchemaToolbar } from '../../../application/schema-toolbar';
|
||||||
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager';
|
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager';
|
||||||
|
import { highlightBlock, startScrollEndTracking, stopScrollEndTracking, unhighlightBlock } from '../../../filter-provider/highlightBlock';
|
||||||
import { FlagProvider } from '../../../flag-provider';
|
import { FlagProvider } from '../../../flag-provider';
|
||||||
import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings';
|
import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings';
|
||||||
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
|
import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider';
|
||||||
@ -32,10 +34,10 @@ import {
|
|||||||
SchemaSettingsSwitchItem,
|
SchemaSettingsSwitchItem,
|
||||||
} from '../../../schema-settings/SchemaSettings';
|
} from '../../../schema-settings/SchemaSettings';
|
||||||
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
|
import { useAllDataBlocks } from '../page/AllDataBlocksProvider';
|
||||||
import { useLinkageAction } from './hooks';
|
import { useLinkageAction } from './hooks';
|
||||||
import { requestSettingsSchema } from './utils';
|
|
||||||
import { useAfterSuccessOptions } from './hooks/useGetAfterSuccessVariablesOptions';
|
import { useAfterSuccessOptions } from './hooks/useGetAfterSuccessVariablesOptions';
|
||||||
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
|
import { requestSettingsSchema } from './utils';
|
||||||
|
|
||||||
const MenuGroup = (props) => {
|
const MenuGroup = (props) => {
|
||||||
return props.children;
|
return props.children;
|
||||||
@ -294,14 +296,92 @@ const useVariableProps = (environmentVariables) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hideDialog = (dialogClassName: string) => {
|
||||||
|
const dialogMask = document.querySelector<HTMLElement>(`.${dialogClassName} > .ant-modal-mask`);
|
||||||
|
const dialogWrap = document.querySelector<HTMLElement>(`.${dialogClassName} > .ant-modal-wrap`);
|
||||||
|
if (dialogMask) {
|
||||||
|
dialogMask.style.opacity = '0';
|
||||||
|
dialogMask.style.transition = 'opacity 0.5s ease';
|
||||||
|
}
|
||||||
|
if (dialogWrap) {
|
||||||
|
dialogWrap.style.opacity = '0';
|
||||||
|
dialogWrap.style.transition = 'opacity 0.5s ease';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDialog = (dialogClassName: string) => {
|
||||||
|
const dialogMask = document.querySelector<HTMLElement>(`.${dialogClassName} > .ant-modal-mask`);
|
||||||
|
const dialogWrap = document.querySelector<HTMLElement>(`.${dialogClassName} > .ant-modal-wrap`);
|
||||||
|
if (dialogMask) {
|
||||||
|
dialogMask.style.opacity = '1';
|
||||||
|
dialogMask.style.transition = 'opacity 0.5s ease';
|
||||||
|
}
|
||||||
|
if (dialogWrap) {
|
||||||
|
dialogWrap.style.opacity = '1';
|
||||||
|
dialogWrap.style.transition = 'opacity 0.5s ease';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BlocksSelector = (props) => {
|
||||||
|
const { getAllDataBlocks } = useAllDataBlocks();
|
||||||
|
const allDataBlocks = getAllDataBlocks();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// 转换 allDataBlocks 为 Select 选项
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return allDataBlocks.map(block => {
|
||||||
|
// 防止列表中出现已关闭的弹窗中的区块
|
||||||
|
if (!block.dom?.isConnected) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
||||||
|
return {
|
||||||
|
label: title,
|
||||||
|
value: block.uid,
|
||||||
|
onMouseEnter() {
|
||||||
|
block.highlightBlock();
|
||||||
|
hideDialog('dialog-after-successful-submission');
|
||||||
|
startScrollEndTracking(block.dom, () => {
|
||||||
|
highlightBlock(block.dom.cloneNode(true) as HTMLElement, block.dom.getBoundingClientRect());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onMouseLeave() {
|
||||||
|
block.unhighlightBlock();
|
||||||
|
showDialog('dialog-after-successful-submission');
|
||||||
|
stopScrollEndTracking(block.dom);
|
||||||
|
unhighlightBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).filter(Boolean);
|
||||||
|
}, [allDataBlocks, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={props.value}
|
||||||
|
mode="multiple"
|
||||||
|
allowClear
|
||||||
|
placeholder={t('Select data blocks to refresh')}
|
||||||
|
options={options}
|
||||||
|
onChange={props.onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AfterSuccess() {
|
export function AfterSuccess() {
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
|
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
|
||||||
const environmentVariables = useGlobalVariable('$env');
|
const environmentVariables = useGlobalVariable('$env');
|
||||||
|
const templatePlugin: any = usePlugin('@nocobase/plugin-block-template');
|
||||||
|
const isInBlockTemplateConfigPage = templatePlugin?.isInBlockTemplateConfigPage?.();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsModalItem
|
<SchemaSettingsModalItem
|
||||||
|
dialogRootClassName='dialog-after-successful-submission'
|
||||||
|
width={700}
|
||||||
title={t('After successful submission')}
|
title={t('After successful submission')}
|
||||||
initialValues={
|
initialValues={
|
||||||
onSuccess
|
onSuccess
|
||||||
@ -382,6 +462,18 @@ export function AfterSuccess() {
|
|||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
'x-use-component-props': () => useVariableProps(environmentVariables),
|
'x-use-component-props': () => useVariableProps(environmentVariables),
|
||||||
},
|
},
|
||||||
|
blocksToRefresh: {
|
||||||
|
type: 'array',
|
||||||
|
title: t('Refresh data blocks'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-use-decorator-props': () => {
|
||||||
|
return {
|
||||||
|
tooltip: t('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'x-component': BlocksSelector,
|
||||||
|
'x-hidden': isInBlockTemplateConfigPage, // 模板配置页面暂不支持该配置
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as ISchema
|
} as ISchema
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,11 @@ import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
|||||||
|
|
||||||
// 这个要放到最下面,否则会导致前端单测失败
|
// 这个要放到最下面,否则会导致前端单测失败
|
||||||
import { useApp } from '../../../application';
|
import { useApp } from '../../../application';
|
||||||
|
import { useAllDataBlocks } from '../page/AllDataBlocksProvider';
|
||||||
|
|
||||||
const useA = () => {
|
const useA = () => {
|
||||||
return {
|
return {
|
||||||
async run() {},
|
async run() { },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,6 +102,8 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
const { getAriaLabel } = useGetAriaLabelOfAction(title);
|
const { getAriaLabel } = useGetAriaLabelOfAction(title);
|
||||||
const parentRecordData = useCollectionParentRecordData();
|
const parentRecordData = useCollectionParentRecordData();
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
|
const { getAllDataBlocks } = useAllDataBlocks();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (field.stateOfLinkageRules) {
|
if (field.stateOfLinkageRules) {
|
||||||
setInitialActionState(field);
|
setInitialActionState(field);
|
||||||
@ -131,6 +134,26 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
[onMouseEnter],
|
[onMouseEnter],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClick = useMemo(() => {
|
||||||
|
return onClick && (async (e, callback) => {
|
||||||
|
await onClick?.(e, callback);
|
||||||
|
|
||||||
|
// 执行完 onClick 之后,刷新数据区块
|
||||||
|
const blocksToRefresh = fieldSchema['x-action-settings']?.onSuccess?.blocksToRefresh || []
|
||||||
|
if (blocksToRefresh.length > 0) {
|
||||||
|
getAllDataBlocks().forEach((block) => {
|
||||||
|
if (blocksToRefresh.includes(block.uid)) {
|
||||||
|
try {
|
||||||
|
block.service?.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to refresh block:', block.uid, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [onClick, fieldSchema, getAllDataBlocks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InternalAction
|
<InternalAction
|
||||||
containerRefKey={containerRefKey}
|
containerRefKey={containerRefKey}
|
||||||
@ -144,7 +167,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
className={className}
|
className={className}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
Designer={Designer}
|
Designer={Designer}
|
||||||
onClick={onClick}
|
onClick={handleClick}
|
||||||
confirm={confirm}
|
confirm={confirm}
|
||||||
confirmTitle={confirmTitle}
|
confirmTitle={confirmTitle}
|
||||||
popover={popover}
|
popover={popover}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import _ from "lodash";
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
import { DataBlock } from "../../../filter-provider/FilterProvider";
|
||||||
|
|
||||||
|
export const AllDataBlocksContext = React.createContext<{
|
||||||
|
getAllDataBlocks: () => DataBlock[];
|
||||||
|
setAllDataBlocks: (
|
||||||
|
value: DataBlock[] | ((prev: DataBlock[]) => DataBlock[])
|
||||||
|
) => void;
|
||||||
|
}>({
|
||||||
|
getAllDataBlocks: () => [],
|
||||||
|
setAllDataBlocks: () => { },
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前页面中所有数据区块的信息(包括弹窗中的)
|
||||||
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const AllDataBlocksProvider: React.FC = (props) => {
|
||||||
|
const dataBlocksRef = React.useRef<DataBlock[]>([]);
|
||||||
|
const setAllDataBlocks = React.useCallback((value) => {
|
||||||
|
if (typeof value === "function") {
|
||||||
|
dataBlocksRef.current = value(dataBlocksRef.current);
|
||||||
|
} else {
|
||||||
|
dataBlocksRef.current = value;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const getAllDataBlocks = React.useCallback(
|
||||||
|
() => dataBlocksRef.current,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const value = React.useMemo(
|
||||||
|
() => ({ getAllDataBlocks, setAllDataBlocks }),
|
||||||
|
[getAllDataBlocks, setAllDataBlocks]
|
||||||
|
);
|
||||||
|
return <AllDataBlocksContext.Provider value={value}>{props.children}</AllDataBlocksContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAllDataBlocks = () => {
|
||||||
|
const ctx = React.useContext(AllDataBlocksContext);
|
||||||
|
|
||||||
|
const getAllDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getAllDataBlocks() || [], [ctx]);
|
||||||
|
|
||||||
|
const recordDataBlocks = useCallback(
|
||||||
|
(block: DataBlock) => {
|
||||||
|
const existingBlock = ctx?.getAllDataBlocks().find((item) => item.uid === block.uid);
|
||||||
|
|
||||||
|
if (existingBlock) {
|
||||||
|
// 这里的值有可能会变化,所以需要更新
|
||||||
|
Object.assign(existingBlock, block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx?.setAllDataBlocks((prev) => [...prev, block]);
|
||||||
|
},
|
||||||
|
[ctx],
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeDataBlock = useCallback(
|
||||||
|
(uid: string) => {
|
||||||
|
if (ctx?.getAllDataBlocks().every((item) => item.uid !== uid)) return;
|
||||||
|
ctx?.setAllDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
||||||
|
},
|
||||||
|
[ctx],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
return {
|
||||||
|
recordDataBlocks: _.noop,
|
||||||
|
removeDataBlock: _.noop,
|
||||||
|
getAllDataBlocks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
recordDataBlocks,
|
||||||
|
removeDataBlock,
|
||||||
|
getAllDataBlocks,
|
||||||
|
};
|
||||||
|
};
|
@ -13,6 +13,7 @@ import { css } from '@emotion/css';
|
|||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
import { SchemaOptionsContext, useFieldSchema } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
|
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||||
import { Button, Tabs } from 'antd';
|
import { Button, Tabs } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { FC, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { FC, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@ -48,10 +49,10 @@ import { useCompile, useDesignable } from '../../hooks';
|
|||||||
import { useToken } from '../__builtins__';
|
import { useToken } from '../__builtins__';
|
||||||
import { ErrorFallback } from '../error-fallback';
|
import { ErrorFallback } from '../error-fallback';
|
||||||
import { useMenuDragEnd, useNocoBaseRoutes } from '../menu/Menu';
|
import { useMenuDragEnd, useNocoBaseRoutes } from '../menu/Menu';
|
||||||
|
import { AllDataBlocksProvider } from './AllDataBlocksProvider';
|
||||||
import { useStyles } from './Page.style';
|
import { useStyles } from './Page.style';
|
||||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||||
import { PopupRouteContextResetter } from './PopupRouteContextResetter';
|
import { PopupRouteContextResetter } from './PopupRouteContextResetter';
|
||||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
|
||||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
@ -128,12 +129,14 @@ export const Page = React.memo((props: PageProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<AllDataBlocksProvider>
|
||||||
<div className={`${componentCls} ${hashId} ${antTableCell}`} style={pageActive ? null : hiddenStyle}>
|
<div className={`${componentCls} ${hashId} ${antTableCell}`} style={pageActive ? null : hiddenStyle}>
|
||||||
{/* Avoid passing values down to improve rendering performance */}
|
{/* Avoid passing values down to improve rendering performance */}
|
||||||
<CurrentTabUidContext.Provider value={''}>
|
<CurrentTabUidContext.Provider value={''}>
|
||||||
<InternalPage currentTabUid={tabUidRef.current} className={props.className} />
|
<InternalPage currentTabUid={tabUidRef.current} className={props.className} />
|
||||||
</CurrentTabUidContext.Provider>
|
</CurrentTabUidContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
|
</AllDataBlocksProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,3 +14,4 @@ export { PagePopups, useCurrentPopupContext } from './PagePopups';
|
|||||||
export { getPopupPathFromParams, getStoredPopupContext, storePopupContext, withSearchParams } from './pagePopupUtils';
|
export { getPopupPathFromParams, getStoredPopupContext, storePopupContext, withSearchParams } from './pagePopupUtils';
|
||||||
export * from './PageTab.Settings';
|
export * from './PageTab.Settings';
|
||||||
export { PopupSettingsProvider, usePopupSettings } from './PopupSettingsProvider';
|
export { PopupSettingsProvider, usePopupSettings } from './PopupSettingsProvider';
|
||||||
|
export * from './AllDataBlocksProvider';
|
||||||
|
@ -96,6 +96,7 @@ import { useRecord } from '../record-provider';
|
|||||||
import { ActionContextProvider } from '../schema-component/antd/action/context';
|
import { ActionContextProvider } from '../schema-component/antd/action/context';
|
||||||
import { SubFormProvider, useSubFormValue } from '../schema-component/antd/association-field/hooks';
|
import { SubFormProvider, useSubFormValue } from '../schema-component/antd/association-field/hooks';
|
||||||
import { FormDialog } from '../schema-component/antd/form-dialog';
|
import { FormDialog } from '../schema-component/antd/form-dialog';
|
||||||
|
import { AllDataBlocksContext } from '../schema-component/antd/page/AllDataBlocksProvider';
|
||||||
import { SchemaComponentContext } from '../schema-component/context';
|
import { SchemaComponentContext } from '../schema-component/context';
|
||||||
import { FormProvider } from '../schema-component/core/FormProvider';
|
import { FormProvider } from '../schema-component/core/FormProvider';
|
||||||
import { RemoteSchemaComponent } from '../schema-component/core/RemoteSchemaComponent';
|
import { RemoteSchemaComponent } from '../schema-component/core/RemoteSchemaComponent';
|
||||||
@ -816,6 +817,7 @@ export interface SchemaSettingsModalItemProps {
|
|||||||
noRecord?: boolean;
|
noRecord?: boolean;
|
||||||
/** 自定义 Modal 上下文 */
|
/** 自定义 Modal 上下文 */
|
||||||
ModalContextProvider?: React.FC;
|
ModalContextProvider?: React.FC;
|
||||||
|
dialogRootClassName?: string;
|
||||||
}
|
}
|
||||||
export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props) => {
|
export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
@ -830,6 +832,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
|||||||
width = 'fit-content',
|
width = 'fit-content',
|
||||||
noRecord = false,
|
noRecord = false,
|
||||||
ModalContextProvider = (props) => <>{props.children}</>,
|
ModalContextProvider = (props) => <>{props.children}</>,
|
||||||
|
dialogRootClassName,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const options = useContext(SchemaOptionsContext);
|
const options = useContext(SchemaOptionsContext);
|
||||||
@ -850,6 +853,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
|||||||
const { getOperators } = useOperators();
|
const { getOperators } = useOperators();
|
||||||
const locationSearch = useLocationSearch();
|
const locationSearch = useLocationSearch();
|
||||||
const variableOptions = useVariables();
|
const variableOptions = useVariables();
|
||||||
|
const allDataBlocks = useContext(AllDataBlocksContext);
|
||||||
|
|
||||||
// 解决变量`当前对象`值在弹窗中丢失的问题
|
// 解决变量`当前对象`值在弹窗中丢失的问题
|
||||||
const { formValue: subFormValue, collection: subFormCollection, parent } = useSubFormValue();
|
const { formValue: subFormValue, collection: subFormCollection, parent } = useSubFormValue();
|
||||||
@ -870,9 +874,10 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
|||||||
const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues;
|
const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues;
|
||||||
const schema = _.isFunction(props.schema) ? props.schema() : props.schema;
|
const schema = _.isFunction(props.schema) ? props.schema() : props.schema;
|
||||||
FormDialog(
|
FormDialog(
|
||||||
{ title: schema.title || title, width },
|
{ title: schema.title || title, width, rootClassName: dialogRootClassName },
|
||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
|
<AllDataBlocksContext.Provider value={allDataBlocks}>
|
||||||
<ModalContextProvider>
|
<ModalContextProvider>
|
||||||
<CollectOperators defaultOperators={getOperators()}>
|
<CollectOperators defaultOperators={getOperators()}>
|
||||||
<VariablesContext.Provider value={variableOptions}>
|
<VariablesContext.Provider value={variableOptions}>
|
||||||
@ -943,6 +948,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
|||||||
</VariablesContext.Provider>
|
</VariablesContext.Provider>
|
||||||
</CollectOperators>
|
</CollectOperators>
|
||||||
</ModalContextProvider>
|
</ModalContextProvider>
|
||||||
|
</AllDataBlocksContext.Provider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
|
@ -52,24 +52,10 @@ export function SchemaSettingsConnectDataBlocks(props) {
|
|||||||
const Content = dataBlocks.map((block) => {
|
const Content = dataBlocks.map((block) => {
|
||||||
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`;
|
||||||
const onHover = () => {
|
const onHover = () => {
|
||||||
const dom = block.dom;
|
block.highlightBlock();
|
||||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
|
||||||
if (designer) {
|
|
||||||
designer.style.display = 'block';
|
|
||||||
}
|
|
||||||
dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
|
||||||
dom.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const onLeave = () => {
|
const onLeave = () => {
|
||||||
const dom = block.dom;
|
block.unhighlightBlock();
|
||||||
const designer = dom.querySelector('.general-schema-designer') as HTMLElement;
|
|
||||||
if (designer) {
|
|
||||||
designer.style.display = null;
|
|
||||||
}
|
|
||||||
dom.style.boxShadow = 'none';
|
|
||||||
};
|
};
|
||||||
if (isSameCollection(block.collection, collection)) {
|
if (isSameCollection(block.collection, collection)) {
|
||||||
return (
|
return (
|
||||||
|
@ -20,6 +20,8 @@ import {
|
|||||||
RefreshDataBlockRequest,
|
RefreshDataBlockRequest,
|
||||||
useAfterSuccessOptions,
|
useAfterSuccessOptions,
|
||||||
useGlobalVariable,
|
useGlobalVariable,
|
||||||
|
BlocksSelector,
|
||||||
|
usePlugin,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -61,13 +63,19 @@ const useVariableProps = (environmentVariables) => {
|
|||||||
fieldNames,
|
fieldNames,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function AfterSuccess() {
|
function AfterSuccess() {
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const environmentVariables = useGlobalVariable('$env');
|
const environmentVariables = useGlobalVariable('$env');
|
||||||
|
const templatePlugin: any = usePlugin('@nocobase/plugin-block-template');
|
||||||
|
const isInBlockTemplateConfigPage = templatePlugin?.isInBlockTemplateConfigPage?.();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsModalItem
|
<SchemaSettingsModalItem
|
||||||
|
dialogRootClassName='dialog-after-successful-submission'
|
||||||
|
width={700}
|
||||||
title={t('After successful submission')}
|
title={t('After successful submission')}
|
||||||
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
||||||
schema={
|
schema={
|
||||||
@ -116,6 +124,18 @@ function AfterSuccess() {
|
|||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
'x-use-component-props': () => useVariableProps(environmentVariables),
|
'x-use-component-props': () => useVariableProps(environmentVariables),
|
||||||
},
|
},
|
||||||
|
blocksToRefresh: {
|
||||||
|
type: 'array',
|
||||||
|
title: t('Refresh data blocks'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-use-decorator-props': () => {
|
||||||
|
return {
|
||||||
|
tooltip: t('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'x-component': BlocksSelector,
|
||||||
|
'x-hidden': isInBlockTemplateConfigPage, // 模板配置页面暂不支持该配置
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as ISchema
|
} as ISchema
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
const localVariables = useLocalVariables();
|
const localVariables = useLocalVariables();
|
||||||
return {
|
return {
|
||||||
async onClick(e, callBack) {
|
async onClick(e, callBack) {
|
||||||
|
return new Promise<void>(async (resolve) => {
|
||||||
const {
|
const {
|
||||||
assignedValues: originalAssignedValues = {},
|
assignedValues: originalAssignedValues = {},
|
||||||
onSuccess,
|
onSuccess,
|
||||||
@ -138,11 +139,16 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
},
|
},
|
||||||
async onCancel() {
|
async onCancel() {
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
|
resolve();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useRequest, RemoteSchemaComponent } from '@nocobase/client';
|
import { useRequest, RemoteSchemaComponent, AllDataBlocksProvider } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useT } from '../locale';
|
import { useT } from '../locale';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
@ -31,7 +31,7 @@ export const BlockTemplatePage = () => {
|
|||||||
const schemaUid = data?.data?.uid;
|
const schemaUid = data?.data?.uid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<AllDataBlocksProvider>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginTop: -token.marginXXL,
|
marginTop: -token.marginXXL,
|
||||||
@ -68,6 +68,6 @@ export const BlockTemplatePage = () => {
|
|||||||
<RemoteSchemaComponent uid={schemaUid} />
|
<RemoteSchemaComponent uid={schemaUid} />
|
||||||
</BlockTemplateInfoContext.Provider>
|
</BlockTemplateInfoContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</AllDataBlocksProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AdminProvider,
|
AdminProvider,
|
||||||
|
AllDataBlocksProvider,
|
||||||
AntdAppProvider,
|
AntdAppProvider,
|
||||||
AssociationFieldMode,
|
AssociationFieldMode,
|
||||||
AssociationFieldModeProvider,
|
AssociationFieldModeProvider,
|
||||||
@ -126,7 +127,9 @@ export const Mobile = () => {
|
|||||||
<AssociationFieldModeProvider modeToComponent={modeToComponent}>
|
<AssociationFieldModeProvider modeToComponent={modeToComponent}>
|
||||||
{/* the z-index of all popups and subpages will be based on this value */}
|
{/* the z-index of all popups and subpages will be based on this value */}
|
||||||
<zIndexContext.Provider value={100}>
|
<zIndexContext.Provider value={100}>
|
||||||
|
<AllDataBlocksProvider>
|
||||||
<MobileRouter />
|
<MobileRouter />
|
||||||
|
</AllDataBlocksProvider>
|
||||||
</zIndexContext.Provider>
|
</zIndexContext.Provider>
|
||||||
</AssociationFieldModeProvider>
|
</AssociationFieldModeProvider>
|
||||||
</ResetSchemaOptionsProvider>
|
</ResetSchemaOptionsProvider>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user