fix(plugin-workflow): optimize mobile style (#7040)

* fix(plugin-workflow): optimize mobile style

* fix(plugin-workflow): fix mobile style

* feat(plugin-workflow): add tasks to mobile menu initializer

* refactor(plugin-workflow): adjust tasks center api

* fix(ActionDrawer): apply zIndex style to ActionDrawer component

* fix(plugin-workflow-manual): fix manual style under mobile

* fix(plugin-workflow): fix styles

* fix(plugin-workflow): fix mobile layout check

* fix(plugin-workflow): adjust pagination footer style

* fix(plugin-workflow): fix icon only props

* fix(plugin-workflow-manual): fix todo item title

* revert(plugin-field-sort): revert mistaken commit

---------

Co-authored-by: Zeke Zhang <958414905@qq.com>
This commit is contained in:
Junyi 2025-06-24 22:18:02 +08:00 committed by GitHub
parent e527e4d6ba
commit a9bd6f7844
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 553 additions and 167 deletions

View File

@ -48,11 +48,11 @@ const useStyles = genStyleHook('nb-list', (token) => {
width: '100%',
flexDirection: 'column',
'&:not(:first-child)': {
paddingTop: token.paddingContentVertical,
marginTop: token.paddingContentVertical,
},
'&:not(:last-child)': {
paddingBottom: token.paddingContentVertical,
marginBottom: token.paddingContentVertical,
borderBottom: `1px solid ${token.colorBorderSecondary}`,
},
},

View File

@ -244,7 +244,7 @@ function FinallyButton({
inheritsCollections,
linkageFromForm,
allowAddToCurrent,
props,
props: { onlyIcon, ...props },
componentType,
menu,
onClick,
@ -362,7 +362,7 @@ function FinallyButton({
...buttonStyle,
}}
>
{props.onlyIcon ? props?.children?.[1] : props?.children}
{onlyIcon ? props?.children?.[1] : props?.children}
</Button>
);
}

View File

@ -53,7 +53,11 @@ const useLocalVariables = (props?: Props) => {
dataSource: parentPopupDataSource,
defaultValue: defaultValueOfParentPopupRecord,
} = useParentPopupVariableContext();
const { urlSearchParamsCtx, shouldDisplay: shouldDisplayURLSearchParams, defaultValue: defaultValueOfURLSearchParams } = useURLSearchParamsVariable();
const {
urlSearchParamsCtx,
shouldDisplay: shouldDisplayURLSearchParams,
defaultValue: defaultValueOfURLSearchParams,
} = useURLSearchParamsVariable();
const { datetimeCtx } = useDatetimeVariableContext();
const { currentFormCtx } = useCurrentFormContext({ form: props?.currentForm });
const { name: currentCollectionName } = useCollection_deprecated();

View File

@ -84,8 +84,8 @@ export const actionDesignerCss = css`
`;
export const DuplicateAction = observer(
(props: any) => {
const { children, onlyIcon, icon, title, ...others } = props;
({ onlyIcon, ...props }: any) => {
const { children, icon, title, ...others } = props;
const { message } = App.useApp();
const field = useField();
const fieldSchema = useFieldSchema();

View File

@ -10,9 +10,14 @@
import { useToken } from '@nocobase/client';
import _ from 'lodash';
import React, { FC, useEffect } from 'react';
import classnames from 'classnames';
import { PageBackgroundColor } from '../../../constants';
export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ children, hideTabBar }) => {
export const MobilePageContentContainer: FC<{ hideTabBar?: boolean; className?: string }> = ({
children,
hideTabBar,
className,
}) => {
const [mobileTabBarHeight, setMobileTabBarHeight] = React.useState(0);
const [mobilePageHeader, setMobilePageHeader] = React.useState(0);
const { token } = useToken();
@ -31,7 +36,7 @@ export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ child
<>
{mobilePageHeader ? <div style={{ height: mobilePageHeader }}></div> : null}
<div
className="mobile-page-content"
className={classnames('mobile-page-content', className)}
data-testid="mobile-page-content"
style={{
height: `calc(100% - ${(mobileTabBarHeight || 0) + (mobilePageHeader || 0)}px)`,

View File

@ -40,6 +40,7 @@ import {
ActionContextProvider,
useRequest,
CollectionRecordProvider,
useMobileLayout,
} from '@nocobase/client';
import WorkflowPlugin, {
DetailsBlockProvider,
@ -56,6 +57,7 @@ import { NAMESPACE, useLang } from '../locale';
import { FormBlockProvider } from './instruction/FormBlockProvider';
import { ManualFormType, manualFormTypes } from './instruction/SchemaConfig';
import { TaskStatusOptionsMap, TASK_STATUS } from '../common/constants';
import { useMobilePage } from '@nocobase/plugin-mobile/client';
function TaskStatusColumn(props) {
const recordData = useCollectionRecordData();
@ -390,7 +392,7 @@ function FlowContextProvider(props) {
}}
schema={{
type: 'void',
name: 'tabs',
name: `manual-${id}}`,
'x-component': 'Tabs',
properties: node.config?.schema,
}}
@ -429,16 +431,23 @@ function useDetailsBlockProps() {
}
function FooterStatus() {
const { isMobileLayout } = useMobileLayout();
const mobilePage = useMobilePage();
const compile = useCompile();
const { status, updatedAt } = useCollectionRecordData() || {};
const statusOption = TaskStatusOptionsMap[status];
const isMobile = Boolean(mobilePage || isMobileLayout);
return status ? (
<Space
className={css`
margin-bottom: 1em;
padding: ${isMobileLayout ? '0 1em' : '0'};
margin-bottom: ${isMobile ? '0' : '1em'};
time {
margin-right: 0.5em;
}
.ant-tag {
margin-right: 0;
}
`}
>
<time>{dayjs(updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
@ -449,9 +458,10 @@ function FooterStatus() {
function Drawer() {
const ctx = useContext(SchemaComponentContext);
const { id, node, workflow, status } = useCollectionRecordData() || {};
const record = useCollectionRecordData();
const { id, node, workflow, status } = record || {};
return (
return record ? (
<SchemaComponentContext.Provider value={{ ...ctx, reset() {}, designable: false }}>
<SchemaComponent
components={{
@ -460,7 +470,7 @@ function Drawer() {
}}
schema={{
type: 'void',
name: `drawer-${id}-${status}`,
name: `manual-detail-drawer-${id}-${status}`,
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
@ -485,7 +495,7 @@ function Drawer() {
}}
/>
</SchemaComponentContext.Provider>
);
) : null;
}
function Decorator(props) {
@ -667,6 +677,8 @@ function useTodoActionParams(status) {
return {
filter,
appends: [
'node.id',
'node.title',
'job.id',
'job.status',
'job.result',
@ -676,10 +688,11 @@ function useTodoActionParams(status) {
'execution.id',
'execution.status',
],
except: ['node.config', 'workflow.config', 'workflow.options'],
};
}
function TodoExtraActions() {
function TodoExtraActions(props) {
return (
<SchemaComponent
schema={{
@ -694,6 +707,7 @@ function TodoExtraActions() {
'x-use-component-props': 'useRefreshActionProps',
'x-component-props': {
icon: 'ReloadOutlined',
...props,
},
},
filter: {
@ -703,6 +717,7 @@ function TodoExtraActions() {
'x-use-component-props': 'useFilterActionProps',
'x-component-props': {
icon: 'FilterOutlined',
...props,
},
default: {
$and: [{ title: { $includes: '' } }, { 'workflow.title': { $includes: '' } }],

View File

@ -40,6 +40,7 @@
"@nocobase/logger": "1.x",
"@nocobase/plugin-data-source-main": "1.x",
"@nocobase/plugin-error-handler": "1.x",
"@nocobase/plugin-mobile": "1.x",
"@nocobase/plugin-users": "1.x",
"@nocobase/resourcer": "1.x",
"@nocobase/server": "1.x",

View File

@ -6,12 +6,14 @@
* 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 { CheckCircleOutlined } from '@ant-design/icons';
import { CheckCircleOutlined, EllipsisOutlined } from '@ant-design/icons';
import { PageHeader } from '@ant-design/pro-layout';
import { Badge, Button, Layout, Menu, Tabs, Tooltip } from 'antd';
import { Badge, Button, Flex, Layout, Menu, Popover, Segmented, Tabs, theme, Tooltip } from 'antd';
import classnames from 'classnames';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { NavBar, Toast } from 'antd-mobile';
import { observer } from '@formily/react';
import {
ActionContextProvider,
@ -26,11 +28,25 @@ import {
useCompile,
useDocumentTitle,
useIsLoggedIn,
useMobileLayout,
usePlugin,
useRequest,
useToken,
SchemaInitializerItemType,
APIClient,
} from '@nocobase/client';
import {
MobilePageContentContainer,
MobilePageHeader,
MobilePageNavigationBar,
MobilePageProvider,
MobileRouteItem,
MobileTabBarItem,
useMobilePage,
useMobileRoutes,
} from '@nocobase/plugin-mobile/client';
import PluginWorkflowClient from '.';
import { lang, NAMESPACE } from './locale';
@ -39,11 +55,6 @@ const layoutClass = css`
overflow: hidden;
`;
const contentClass = css`
min-height: 280px;
overflow: auto;
`;
export interface TaskTypeOptions {
title: string;
collection: string;
@ -52,6 +63,7 @@ export interface TaskTypeOptions {
Actions?: React.ComponentType;
Item: React.ComponentType;
Detail: React.ComponentType;
getPopupRecord?: (apiClient: APIClient, { params }: { params: any }) => Promise<any>;
// children?: TaskTypeOptions[];
alwaysShow?: boolean;
}
@ -70,12 +82,18 @@ function MenuLink({ type }: any) {
const { title } = workflowPlugin.taskTypes.get(type);
const { counts } = useContext(TasksCountsContext);
const typeTitle = compile(title);
const mobilePage = useMobilePage();
return (
<Link
to={`/admin/workflow/tasks/${type}/${TASK_STATUS.PENDING}`}
to={
mobilePage
? `/page/workflow/tasks/${type}/${TASK_STATUS.PENDING}`
: `/admin/workflow/tasks/${type}/${TASK_STATUS.PENDING}`
}
className={css`
display: flex;
gap: 0.5em;
align-items: center;
justify-content: space-between;
width: 100%;
@ -103,13 +121,42 @@ function StatusTabs() {
const navigate = useNavigate();
const { taskType, status = TASK_STATUS.PENDING } = useParams();
const type = useCurrentTaskType();
const { isMobileLayout } = useMobileLayout();
const mobilePage = useMobilePage();
const onSwitchTab = useCallback(
(key: string) => {
navigate(mobilePage ? `/page/workflow/tasks/${taskType}/${key}` : `/admin/workflow/tasks/${taskType}/${key}`);
},
[navigate, taskType, mobilePage],
);
const isMobile = Boolean(mobilePage || isMobileLayout);
const { Actions } = type;
return (
return isMobile ? (
<Flex justify="space-between">
<Segmented
defaultValue={status}
options={[
{
value: TASK_STATUS.PENDING,
label: lang('Pending'),
},
{
value: TASK_STATUS.COMPLETED,
label: lang('Completed'),
},
{
value: TASK_STATUS.ALL,
label: lang('All'),
},
]}
onChange={onSwitchTab}
/>
<Actions onlyIcon={isMobile} />
</Flex>
) : (
<Tabs
activeKey={status}
onChange={(activeKey) => {
navigate(`/admin/workflow/tasks/${taskType}/${activeKey}`);
}}
onChange={onSwitchTab}
className={css`
&.ant-tabs-top > .ant-tabs-nav {
margin-bottom: 0;
@ -142,8 +189,8 @@ function StatusTabs() {
function useTaskTypeItems() {
const workflowPlugin = usePlugin(PluginWorkflowClient);
const { counts } = useContext(TasksCountsContext);
const types = workflowPlugin.taskTypes.getKeys();
const { counts } = useContext(TasksCountsContext);
return useMemo(
() =>
@ -173,26 +220,30 @@ function PopupContext(props: any) {
const { taskType, status = TASK_STATUS.PENDING, popupId } = useParams();
const { record } = usePopupRecordContext();
const navigate = useNavigate();
const mobilePage = useMobilePage();
const setVisible = useCallback(
(visible: boolean) => {
if (!visible) {
if (window.history.state.idx) {
navigate(-1);
} else {
navigate(
mobilePage ? `/page/workflow/tasks/${taskType}/${status}` : `/admin/workflow/tasks/${taskType}/${status}`,
);
}
}
},
[mobilePage, navigate, status, taskType],
);
if (!popupId) {
return null;
}
return (
<ActionContextProvider
visible={Boolean(popupId)}
setVisible={(visible) => {
if (!visible) {
if (window.history.state.idx) {
navigate(-1);
} else {
navigate(`/admin/workflow/tasks/${taskType}/${status}`);
}
}
}}
openMode="modal"
>
return record ? (
<ActionContextProvider visible={Boolean(popupId)} setVisible={setVisible} openMode="modal" openSize="large">
<CollectionRecordProvider record={record}>{props.children}</CollectionRecordProvider>
</ActionContextProvider>
);
) : null;
}
const PopupRecordContext = createContext<any>({ record: null, setRecord: (record) => {} });
@ -200,18 +251,229 @@ export function usePopupRecordContext() {
return useContext(PopupRecordContext);
}
function TaskPageContent() {
const navigate = useNavigate();
const apiClient = useAPIClient();
const { taskType, status = TASK_STATUS.PENDING, popupId } = useParams();
const mobilePage = useMobilePage();
const [currentRecord, setCurrentRecord] = useState<any>(null);
const { token } = theme.useToken();
const items = useTaskTypeItems();
const { title, collection, action = 'list', useActionParams, Item, Detail, getPopupRecord } = useCurrentTaskType();
const params = useActionParams(status);
// useEffect(() => {
// setTitle?.(`${lang('Workflow todos')}${title ? `: ${compile(title)}` : ''}`);
// }, [taskType, status, setTitle, title, compile]);
useEffect(() => {
if (!taskType) {
navigate(
mobilePage
? `/page/workflow/tasks/${items[0].key}/${status}`
: `/admin/workflow/tasks/${items[0].key}/${status}`,
{ replace: true },
);
}
}, [items, mobilePage, navigate, status, taskType]);
useEffect(() => {
if (popupId && !currentRecord) {
let load;
if (getPopupRecord) {
load = getPopupRecord(apiClient, { params: { ...params, filterByTk: popupId } });
} else {
load = apiClient.resource(collection).get({
...params,
filterByTk: popupId,
});
}
load
.then((res) => {
if (res.data?.data) {
setCurrentRecord(res.data.data);
}
})
.catch((err) => {
console.error(err);
});
}
}, [popupId, collection, currentRecord, apiClient, getPopupRecord]);
useEffect(() => {
if (!taskType) {
navigate(
mobilePage
? `/page/workflow/tasks/${items[0].key}/${status}`
: `/admin/workflow/tasks/${items[0].key}/${status}`,
{ replace: true },
);
}
}, [items, mobilePage, navigate, status, taskType]);
const typeKey = taskType ?? items[0].key;
const { isMobileLayout } = useMobileLayout();
const isMobile = mobilePage || isMobileLayout;
const contentClass = css`
height: 100%;
overflow: hidden;
padding: 0;
.nb-list {
height: 100%;
overflow: hidden;
.nb-list-container {
height: 100%;
overflow: hidden;
.ant-formily-layout {
height: 100%;
overflow: hidden;
.ant-list {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
.ant-spin-nested-loading {
flex-grow: 1;
overflow: hidden;
.ant-spin-container {
height: 100%;
overflow: auto;
padding: ${isMobile ? '0.5em' : `${token.paddingContentHorizontalLG}px`};
}
}
.itemCss:not(:last-child) {
border-bottom: none;
}
}
.ant-list-pagination {
margin-top: 0;
padding: ${isMobile
? '0.5em'
: `${token.paddingContentHorizontal}px ${token.paddingContentHorizontalLG}px`};
border-top: 1px solid ${token.colorBorderSecondary};
}
}
}
}
`;
return (
<PopupRecordContext.Provider
value={{
record: currentRecord,
setRecord: setCurrentRecord,
}}
>
<SchemaComponentContext.Provider value={{ designable: false }}>
<SchemaComponent
components={{
Layout,
PageHeader,
StatusTabs,
}}
schema={{
name: `${taskType}-${status}`,
type: 'void',
'x-decorator': 'List.Decorator',
'x-decorator-props': {
collection,
action,
params: {
pageSize: 20,
sort: ['-createdAt'],
...params,
},
},
properties: {
header: {
type: 'void',
'x-component': 'PageHeader',
'x-component-props': {
className: classnames(
'pageHeaderCss',
css`
.ant-page-header-content {
padding-top: 0;
}
`,
),
style: {
position: 'sticky',
background: token.colorBgContainer,
padding: isMobile
? '8px'
: `${token.paddingContentVertical}px ${token.paddingContentHorizontalLG}px 0 ${token.paddingContentHorizontalLG}px`,
borderBottom: isMobile ? `1px solid ${token.colorBorderSecondary}` : null,
},
title: isMobile ? null : title,
},
properties: {
tabs: {
type: 'void',
'x-component': 'StatusTabs',
},
},
},
content: {
type: 'void',
'x-component': 'Layout.Content',
'x-component-props': {
className: contentClass,
},
properties: {
list: {
type: 'array',
'x-component': 'List',
'x-component-props': {
locale: {
emptyText: `{{ t("No data yet", { ns: "${NAMESPACE}" }) }}`,
},
},
properties: {
item: {
type: 'object',
'x-decorator': 'List.Item',
'x-component': Item,
'x-read-pretty': true,
},
},
},
},
},
popup: {
type: 'void',
'x-decorator': PopupContext,
'x-component': Detail,
},
},
}}
/>
</SchemaComponentContext.Provider>
</PopupRecordContext.Provider>
);
}
export function WorkflowTasks() {
const compile = useCompile();
const { setTitle } = useDocumentTitle();
const navigate = useNavigate();
const { taskType, status = TASK_STATUS.PENDING, popupId } = useParams();
const { taskType, status = TASK_STATUS.PENDING } = useParams();
const { token } = useToken();
const [currentRecord, setCurrentRecord] = useState<any>(null);
const items = useTaskTypeItems();
const { title, collection, action = 'list', useActionParams, Item, Detail } = useCurrentTaskType();
const params = useActionParams(status);
const { title } = useCurrentTaskType();
useEffect(() => {
setTitle?.(`${lang('Workflow todos')}${title ? `: ${compile(title)}` : ''}`);
@ -223,19 +485,21 @@ export function WorkflowTasks() {
}
}, [items, navigate, status, taskType]);
useEffect(() => {
if (popupId && !currentRecord) {
setCurrentRecord({ id: popupId });
}
}, [popupId, currentRecord]);
const typeKey = taskType ?? items[0].key;
const { isMobileLayout } = useMobileLayout();
return (
<Layout className={layoutClass}>
<Layout.Sider theme="light" breakpoint="md" collapsedWidth="0" zeroWidthTriggerStyle={{ top: 24 }}>
<Menu mode="inline" selectedKeys={[typeKey]} items={items} style={{ height: '100%' }} />
</Layout.Sider>
{isMobileLayout ? (
<Layout.Header style={{ background: token.colorBgContainer, padding: 0, height: '3em', lineHeight: '3em' }}>
<Menu mode="horizontal" selectedKeys={[typeKey]} items={items} />
</Layout.Header>
) : (
<Layout.Sider theme="light" breakpoint="md" collapsedWidth="0" zeroWidthTriggerStyle={{ top: 24 }}>
<Menu mode="inline" selectedKeys={[typeKey]} items={items} style={{ height: '100%' }} />
</Layout.Sider>
)}
<Layout
className={css`
> div {
@ -254,95 +518,7 @@ export function WorkflowTasks() {
}
`}
>
<PopupRecordContext.Provider
value={{
record: currentRecord,
setRecord: setCurrentRecord,
}}
>
<SchemaComponentContext.Provider value={{ designable: false }}>
<SchemaComponent
components={{
Layout,
PageHeader,
StatusTabs,
}}
schema={{
name: `${taskType}-${status}`,
type: 'void',
'x-decorator': 'List.Decorator',
'x-decorator-props': {
collection,
action,
params: {
pageSize: 20,
sort: ['-createdAt'],
...params,
},
},
properties: {
header: {
type: 'void',
'x-component': 'PageHeader',
'x-component-props': {
className: classnames('pageHeaderCss'),
style: {
background: token.colorBgContainer,
padding: '12px 24px 0 24px',
},
title,
},
properties: {
tabs: {
type: 'void',
'x-component': 'StatusTabs',
},
},
},
content: {
type: 'void',
'x-component': 'Layout.Content',
'x-component-props': {
className: contentClass,
style: {
padding: `${token.paddingPageVertical}px ${token.paddingPageHorizontal}px`,
},
},
properties: {
list: {
type: 'array',
'x-component': 'List',
'x-component-props': {
className: css`
> .itemCss:not(:last-child) {
border-bottom: none;
}
`,
locale: {
emptyText: `{{ t("No data yet", { ns: "${NAMESPACE}" }) }}`,
},
},
properties: {
item: {
type: 'object',
'x-decorator': 'List.Item',
'x-component': Item,
'x-read-pretty': true,
},
},
},
},
},
popup: {
type: 'void',
'x-decorator': PopupContext,
'x-component': Detail,
},
},
}}
/>
</SchemaComponentContext.Provider>
</PopupRecordContext.Provider>
<TaskPageContent />
</Layout>
</Layout>
);
@ -439,3 +615,168 @@ export function TasksProvider(props: any) {
return isLoggedIn ? <TasksCountsProvider>{content}</TasksCountsProvider> : content;
}
export const tasksSchemaInitializerItem: SchemaInitializerItemType = {
name: 'workflow-tasks-center',
type: 'item',
useComponentProps() {
const { resource, refresh, schemaResource } = useMobileRoutes();
const items = useTaskTypeItems();
return {
isItem: true,
title: lang('Workflow Tasks'),
badge: 10,
async onClick(values) {
const res = await resource.list();
if (Array.isArray(res?.data?.data)) {
const findIndex = res?.data?.data.findIndex((route) => route?.options?.url === `/page/workflow/tasks`);
if (findIndex > -1) {
Toast.show({
icon: 'fail',
content: lang('The workflow tasks page has already been created.'),
});
return;
}
}
const { data } = await resource.create({
values: {
type: 'page',
title: lang('Workflow Tasks'),
icon: 'CheckCircleOutlined',
schemaUid: 'workflow/tasks',
options: {
url: `/page/workflow/tasks`,
schema: {
'x-component': 'MobileTabBarWorkflowTasksItem',
},
},
// children: [
// {
// type: 'page',
// title: lang('Workflow tasks'),
// icon: 'CheckCircleOutlined',
// schemaUid: 'workflow-tasks',
// options: {
// url: `/page/workflow/tasks`,
// itemSchema: {
// name: uid(),
// 'x-decorator': 'BlockItem',
// 'x-settings': `mobile:tab-bar:page`,
// 'x-component': 'MobileTabBarWorkflowTasksItem',
// 'x-toolbar-props': {
// showBorder: false,
// showBackground: true,
// },
// },
// },
// },
// ],
} as MobileRouteItem,
});
// const parentId = data.data.id;
refresh();
},
};
},
};
export const MobileTabBarWorkflowTasksItem = observer(
(props: any) => {
const navigate = useNavigate();
const location = useLocation();
const items = useTaskTypeItems();
const onClick = useCallback(() => {
navigate(`/page/workflow/tasks/${items[0].key}/${TASK_STATUS.PENDING}`);
}, [items, navigate]);
const { total } = useContext(TasksCountsContext);
const selected = props.url && location.pathname.startsWith(props.url);
return (
<MobileTabBarItem
{...{
...props,
onClick,
badge: total > 0 ? total : undefined,
selected,
}}
/>
);
},
{
displayName: 'MobileTabBarWorkflowTasksItem',
},
);
export function WorkflowTasksMobile() {
const items = useTaskTypeItems();
const { token } = useToken();
const navigate = useNavigate();
return (
<MobilePageProvider>
<MobilePageHeader>
<NavBar className="nb-workflow-tasks-back-action" onBack={() => navigate(-1)}>
{lang('Workflow tasks')}
</NavBar>
<Tabs
className={css({
padding: `0 ${token.paddingPageHorizontal}px`,
'.adm-tabs-header': {
borderBottomWidth: 0,
},
'.adm-tabs-tab': {
height: 49,
padding: '10px 0 10px',
},
'> .ant-tabs-nav': {
marginBottom: 0,
'&::before': {
borderBottom: 'none',
},
},
'.ant-tabs-tab+.ant-tabs-tab': {
marginLeft: '2em',
},
})}
items={items}
/>
</MobilePageHeader>
<MobilePageContentContainer
className={css`
padding: 0 !important;
> div {
height: 100%;
overflow: hidden;
> .ant-formily-layout {
height: 100%;
overflow: hidden;
> div {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
}
}
.ant-nb-list {
.itemCss:not(:last-child) {
padding-bottom: 0;
margin-bottom: 0.5em;
}
.itemCss:not(:first-child) {
padding-top: 0;
margin-top: 0.5em;
}
}
`}
>
<TaskPageContent />
</MobilePageContentContainer>
</MobilePageProvider>
);
}

View File

@ -7,13 +7,13 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { PagePopups, Plugin, useCompile } from '@nocobase/client';
import { PagePopups, Plugin, useCompile, lazy } from '@nocobase/client';
import { Registry } from '@nocobase/utils/client';
import MobileManager from '@nocobase/plugin-mobile/client';
// import { ExecutionPage } from './ExecutionPage';
// import { WorkflowPage } from './WorkflowPage';
// import { WorkflowPane } from './WorkflowPane';
import { lazy } from '@nocobase/client';
const { ExecutionPage } = lazy(() => import('./ExecutionPage'), 'ExecutionPage');
const { WorkflowPage } = lazy(() => import('./WorkflowPage'), 'WorkflowPage');
const { WorkflowPane } = lazy(() => import('./WorkflowPane'), 'WorkflowPane');
@ -33,8 +33,16 @@ import CollectionTrigger from './triggers/collection';
import ScheduleTrigger from './triggers/schedule';
import { getWorkflowDetailPath, getWorkflowExecutionsPath } from './utils';
import { VariableOption } from './variable';
import { TasksProvider, TaskTypeOptions, WorkflowTasks } from './WorkflowTasks';
import {
MobileTabBarWorkflowTasksItem,
TasksProvider,
tasksSchemaInitializerItem,
TaskTypeOptions,
WorkflowTasks,
WorkflowTasksMobile,
} from './WorkflowTasks';
import { WorkflowCollectionsProvider } from './WorkflowCollectionsProvider';
import { observer } from '@formily/react';
const workflowConfigSettings = {
Component: BindWorkflowConfig,
@ -111,6 +119,22 @@ export default class PluginWorkflowClient extends Plugin {
async load() {
this.app.addProvider(WorkflowCollectionsProvider);
this.app.addProvider(TasksProvider);
this.app.pluginSettingsManager.add(NAMESPACE, {
icon: 'PartitionOutlined',
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
Component: WorkflowPane,
aclSnippet: 'pm.workflow.workflows',
});
this.app.schemaSettingsManager.addItem('actionSettings:submit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:createSubmit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:updateSubmit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:saveRecord', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:updateRecord', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:delete', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:bulkEditSubmit', 'workflowConfig', workflowConfigSettings);
this.router.add('admin.workflow.workflows.id', {
path: getWorkflowDetailPath(':id'),
@ -127,22 +151,18 @@ export default class PluginWorkflowClient extends Plugin {
Component: WorkflowTasks,
});
this.app.pluginSettingsManager.add(NAMESPACE, {
icon: 'PartitionOutlined',
title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`,
Component: WorkflowPane,
aclSnippet: 'pm.workflow.workflows',
});
this.app.use(TasksProvider);
this.app.schemaSettingsManager.addItem('actionSettings:submit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:createSubmit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:updateSubmit', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:saveRecord', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:updateRecord', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:delete', 'workflowConfig', workflowConfigSettings);
this.app.schemaSettingsManager.addItem('actionSettings:bulkEditSubmit', 'workflowConfig', workflowConfigSettings);
const mobileManager = this.pm.get(MobileManager);
this.app.schemaInitializerManager.addItem('mobile:tab-bar', 'workflow-tasks', tasksSchemaInitializerItem);
this.app.addComponents({ MobileTabBarWorkflowTasksItem });
if (mobileManager.mobileRouter) {
mobileManager.mobileRouter.add('mobile.page.workflow', {
path: '/page/workflow',
});
mobileManager.mobileRouter.add('mobile.page.workflow.tasks', {
path: '/page/workflow/tasks/:taskType/:status/:popupId?',
Component: observer(WorkflowTasksMobile, { displayName: 'WorkflowTasksMobile' }),
});
}
this.registerInstructionGroup('control', { key: 'control', label: `{{t("Control", { ns: "${NAMESPACE}" })}}` });
this.registerInstructionGroup('calculation', {