2025-03-22 15:12:42 +00:00

250 lines
7.7 KiB
TypeScript

/**
* 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 { css, cx } from '@emotion/css';
import { observer, useField, useFieldSchema } from '@formily/react';
import {
ActionContextProvider,
CollectionProvider_deprecated,
FormBlockContext,
NocoBaseRecursionField,
PopupSettingsProvider,
RecordProvider,
RefreshComponentProvider,
TabsContextProvider,
fetchTemplateData,
useACLActionParamsContext,
useAPIClient,
useActionContext,
useBlockRequestContext,
useCollectionManager_deprecated,
useCollectionParentRecordData,
useCollection_deprecated,
useDesignable,
useFormBlockContext,
useRecord,
useCollection,
useDataSourceHeaders,
useDataSourceKey,
Icon,
} from '@nocobase/client';
import { App, Button, Tooltip } from 'antd';
import _ from 'lodash';
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
export const actionDesignerCss = css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
.general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: var(--colorBgSettingsHover);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
'&.nb-in-template': {
background: 'var(--colorTemplateBgSettingsHover)';
}
,
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
align-self: stretch;
}
}
}
`;
export const DuplicateAction = observer(
(props: any) => {
const { children, onlyIcon, icon, title } = props;
const { message } = App.useApp();
const field = useField();
const fieldSchema = useFieldSchema();
const api = useAPIClient();
const disabled: boolean = field.disabled || props.disabled;
const { designable } = useDesignable();
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const { service, __parent, block, resource } = useBlockRequestContext();
const { duplicateFields, duplicateMode = 'quickDulicate', duplicateCollection } = fieldSchema['x-component-props'];
const record = useRecord();
const parentRecordData: any = useCollectionParentRecordData();
const collection = useCollection();
const { id, __collection } = record;
const ctx = useActionContext();
const { name } = useCollection_deprecated();
const { getCollectionFields } = useCollectionManager_deprecated();
const { t } = useTranslation();
const collectionFields = getCollectionFields(__collection || name);
const formctx = useFormBlockContext();
const aclCtx = useACLActionParamsContext();
const dataSource = useDataSourceKey();
const headers = useDataSourceHeaders(dataSource);
const dataId = Array.isArray(collection.filterTargetKey)
? Object.assign(
{},
...collection.filterTargetKey.map((v) => {
return { [v]: record[v] };
}),
)
: record[collection.filterTargetKey] || id;
const template = {
key: 'duplicate',
dataId,
default: true,
fields:
duplicateFields?.filter((v) => {
return collectionFields.find((k) => v.includes(k.name));
}) || [],
collection: __collection || name,
};
const isLinkBtn = fieldSchema['x-component'] === 'Action.Link';
const handelQuickDuplicate = async () => {
setLoading(true);
try {
const data = await fetchTemplateData(api, template, headers);
await resource['create']({
values: {
...data,
},
});
message.success(t('Saved successfully'));
if (block === 'form') {
__parent?.service?.refresh?.();
} else {
await service?.refresh?.();
}
setLoading(false);
} catch (error) {
setLoading(false);
console.error(error); // Handle or log the error appropriately
}
};
const handelDuplicate = () => {
if (!disabled && !loading && aclCtx) {
if (duplicateFields?.length > 0) {
if (duplicateMode === 'quickDulicate') {
handelQuickDuplicate();
} else {
setVisible(true);
}
} else {
message.error(t('Please configure the duplicate fields'));
}
}
};
return (
<div
className={cx(actionDesignerCss, {
[css`
.general-schema-designer {
top: -10px;
bottom: -10px;
left: -10px;
right: -10px;
}
`]: isLinkBtn,
})}
>
<FormBlockContext.Provider
value={{
...formctx,
duplicateData: {
display: false,
enabled: true,
defaultTemplate: template,
},
}}
>
<div>
{isLinkBtn ? (
<a
className="nb-action-link"
role={props.role}
aria-label={props['aria-label']}
//@ts-ignore
disabled={disabled}
style={{
opacity: designable && field?.data?.hidden && 0.1,
cursor: loading ? 'not-allowed' : 'pointer',
position: 'relative',
}}
onClick={handelDuplicate}
>
<Tooltip title={title}>
<span style={{ marginRight: 3 }}>
{icon && typeof icon === 'string' ? <Icon type={icon} /> : icon}
</span>
</Tooltip>
{onlyIcon ? children[1] : loading ? t('Duplicating') : children || t('Duplicate')}
</a>
) : (
<Button
role={props.role}
aria-label={props['aria-label']}
disabled={disabled}
style={{
opacity: designable && field?.data?.hidden && 0.1,
}}
{...props}
onClick={handelDuplicate}
>
{loading ? t('Duplicating') : children || t('Duplicate')}
</Button>
)}
{/* Clear the context of Tabs to avoid affecting the Tabs of the upper-level popup */}
<TabsContextProvider>
<CollectionProvider_deprecated name={duplicateCollection || name}>
{/* 这里的 record 就是弹窗中创建表单的 sourceRecord */}
<RecordProvider record={{ ...parentRecordData, __collection: duplicateCollection || __collection }}>
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
<PopupSettingsProvider enableURL={false}>
<RefreshComponentProvider refresh={_.noop}>
<NocoBaseRecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
</RefreshComponentProvider>
</PopupSettingsProvider>
</ActionContextProvider>
</RecordProvider>
</CollectionProvider_deprecated>
</TabsContextProvider>
</div>
</FormBlockContext.Provider>
</div>
);
},
{
displayName: 'DuplicateAction',
},
);