mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
f03ac173d0
3
.github/workflows/build-pro-image.yml
vendored
3
.github/workflows/build-pro-image.yml
vendored
@ -34,8 +34,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
3
.github/workflows/get-nocobase-app-token.yml
vendored
3
.github/workflows/get-nocobase-app-token.yml
vendored
@ -30,8 +30,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
APP_TOKEN=${{ steps.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
ENCRYPTED_SECRET=$(echo -n "$BINARY_ENCRYPTED_SECRET" | base64 -w 0);
|
||||
ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}" | base64 -w 0);
|
||||
echo "token=$ENCRYPTED_SECRET" >> $GITHUB_OUTPUT
|
||||
- name: Get GitHub App User ID
|
||||
id: get-user-id
|
||||
|
3
.github/workflows/manual-build-pro-image.yml
vendored
3
.github/workflows/manual-build-pro-image.yml
vendored
@ -34,8 +34,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
6
.github/workflows/manual-release.yml
vendored
6
.github/workflows/manual-release.yml
vendored
@ -30,8 +30,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -60,8 +59,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
3
.github/workflows/release-next.yml
vendored
3
.github/workflows/release-next.yml
vendored
@ -21,8 +21,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -23,8 +23,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
ENCRYPTED_SECRET=${{ needs.app-token.outputs.token }};
|
||||
BINARY_ENCRYPTED_SECRET=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode);
|
||||
APP_TOKEN=$(echo -n "$BINARY_ENCRYPTED_SECRET" | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
APP_TOKEN=$(echo -n "$ENCRYPTED_SECRET" | base64 --decode | openssl enc -aes-256-cbc -pbkdf2 -d -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}");
|
||||
echo "token=$APP_TOKEN" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
## [v1.3.4-beta](https://github.com/nocobase/nocobase/compare/v1.3.3-beta...v1.3.4-beta) - 2024-08-27
|
||||
|
||||
### Merged
|
||||
|
||||
- refactor: set remainsTheSame as the default value for field editing in bulk editing action [`#5124`](https://github.com/nocobase/nocobase/pull/5124)
|
||||
|
||||
### Commits
|
||||
|
||||
- chore(versions): 😊 publish v1.3.4-beta [`a011748`](https://github.com/nocobase/nocobase/commit/a0117480e037e48a23f59921110003047a1a174b)
|
||||
- chore: update changelog [`3403e8d`](https://github.com/nocobase/nocobase/commit/3403e8d76684950d6962a6110a4440eb95856a35)
|
||||
|
||||
## [v1.3.3-beta](https://github.com/nocobase/nocobase/compare/v1.3.2-beta...v1.3.3-beta) - 2024-08-27
|
||||
|
||||
### Merged
|
||||
|
@ -141,6 +141,7 @@ export const useDetailsBlockProps = () => {
|
||||
ctx.form
|
||||
.reset()
|
||||
.then(() => {
|
||||
ctx.form.setInitialValues(data || {});
|
||||
ctx.form.setValues(data || {});
|
||||
})
|
||||
.catch(console.error);
|
||||
|
@ -87,7 +87,7 @@ const InternalFormBlockProvider = (props) => {
|
||||
updateAssociationValues,
|
||||
]);
|
||||
|
||||
if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
|
||||
if (service.loading && Object.keys(form?.initialValues || {})?.length === 0 && action) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
@ -151,9 +151,10 @@ export const useFormBlockContext = () => {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const useFormBlockProps = (shouldClearInitialValues = false) => {
|
||||
export const useFormBlockProps = () => {
|
||||
const ctx = useFormBlockContext();
|
||||
const treeParentRecord = useTreeParentRecord();
|
||||
|
||||
useEffect(() => {
|
||||
if (treeParentRecord) {
|
||||
ctx.form?.query('parent').take((field) => {
|
||||
@ -164,19 +165,14 @@ export const useFormBlockProps = (shouldClearInitialValues = false) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!ctx?.service?.loading) {
|
||||
const form: Form = ctx.form;
|
||||
if (form) {
|
||||
// form 字段中可能一开始就存在一些默认值(比如设置了字段默认值的模板区块)。在编辑表单中,
|
||||
// 这些默认值是不需要的,需要清除掉,不然会导致一些问题。比如:https://github.com/nocobase/nocobase/issues/4868
|
||||
if (shouldClearInitialValues === true) {
|
||||
form.initialValues = {};
|
||||
form.reset();
|
||||
}
|
||||
form.setInitialValues(ctx.service?.data?.data);
|
||||
}
|
||||
const form: Form = ctx.form;
|
||||
|
||||
if (!form || ctx.service?.loading) {
|
||||
return;
|
||||
}
|
||||
}, [ctx?.service?.loading]);
|
||||
|
||||
form.setInitialValues(ctx.service?.data?.data);
|
||||
}, [ctx.form, ctx.service?.data?.data, ctx.service?.loading]);
|
||||
return {
|
||||
form: ctx.form,
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ describe('parseVariablesAndChangeParamsToQueryString', () => {
|
||||
{ name: 'param3', value: 'value3' },
|
||||
];
|
||||
const variables: any = {
|
||||
parseVariable: vi.fn().mockResolvedValue('parsedValue'),
|
||||
parseVariable: vi.fn().mockResolvedValue({ value: 'parsedValue' }),
|
||||
};
|
||||
const localVariables: any = [
|
||||
{ name: '$var1', ctx: { value: 'localValue1' } },
|
||||
|
@ -163,9 +163,9 @@ export function useCollectValuesToSubmit() {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
@ -324,9 +324,9 @@ export const useAssociationCreateActionProps = () => {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
@ -582,9 +582,9 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
@ -680,9 +680,9 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
@ -888,9 +888,9 @@ export const useUpdateActionProps = () => {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {};
|
||||
if (parsedValue) {
|
||||
assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField });
|
||||
}
|
||||
} else if (value != null && value !== '') {
|
||||
assignedValues[key] = value;
|
||||
@ -1661,8 +1661,8 @@ export async function parseVariablesAndChangeParamsToQueryString({
|
||||
searchParams.map(async ({ name, value }) => {
|
||||
if (typeof value === 'string') {
|
||||
if (isVariable(value)) {
|
||||
const result = await variables.parseVariable(value, localVariables);
|
||||
return { name, value: result };
|
||||
const { value: parsedValue } = (await variables.parseVariable(value, localVariables)) || {};
|
||||
return { name, value: parsedValue };
|
||||
}
|
||||
const result = await replaceVariableValue(value, variables, localVariables);
|
||||
return { name, value: result };
|
||||
|
@ -26,7 +26,10 @@ test.describe('bulk edit form', () => {
|
||||
// 2. 打开弹窗,显示出批量编辑表单
|
||||
await page.getByLabel('action-Action-Bulk edit-').click();
|
||||
|
||||
// 默认为 "Changed to" 模式,此时应该显示字段是必填的
|
||||
await page.getByLabel('block-item-BulkEditField-').click();
|
||||
await page.getByRole('option', { name: 'Changed to' }).click();
|
||||
|
||||
// "Changed to" 模式,此时应该显示字段是必填的
|
||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||
|
||||
// 3. 输入值,点击提交
|
||||
@ -45,7 +48,10 @@ test.describe('bulk edit form', () => {
|
||||
// 1. 打开弹窗,显示出批量编辑表单
|
||||
await page.getByLabel('action-Action-Bulk edit-').click();
|
||||
|
||||
// 默认为 "Changed to" 模式,此时应该显示字段是必填的
|
||||
await page.getByLabel('block-item-BulkEditField-').click();
|
||||
await page.getByRole('option', { name: 'Changed to' }).click();
|
||||
|
||||
// "Changed to" 模式,此时应该显示字段是必填的
|
||||
await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible();
|
||||
|
||||
// 2. 切换为其它模式,此时应该不显示字段是必填的
|
||||
|
@ -10,5 +10,5 @@
|
||||
import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider';
|
||||
|
||||
export function useEditFormBlockProps() {
|
||||
return useFormBlockProps(true);
|
||||
return useFormBlockProps();
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ import { PopupVisibleProvider, PopupVisibleProviderContext } from '../../schema-
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const PopupContextProvider: React.FC = (props) => {
|
||||
export const PopupContextProvider: React.FC<{
|
||||
visible?: boolean;
|
||||
setVisible?: (visible: boolean) => void;
|
||||
}> = (props) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { visible: visibleWithURL, setVisible: setVisibleWithURL } = useContext(PopupVisibleProviderContext) || {
|
||||
visible: false,
|
||||
@ -26,10 +29,11 @@ export const PopupContextProvider: React.FC = (props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const _setVisible = useCallback(
|
||||
(value: boolean): void => {
|
||||
props.setVisible?.(value);
|
||||
setVisible?.(value);
|
||||
setVisibleWithURL?.(value);
|
||||
},
|
||||
[setVisibleWithURL],
|
||||
[props, setVisibleWithURL],
|
||||
);
|
||||
const openMode = fieldSchema['x-component-props']?.['openMode'] || 'drawer';
|
||||
const openSize = fieldSchema['x-component-props']?.['openSize'];
|
||||
@ -37,7 +41,7 @@ export const PopupContextProvider: React.FC = (props) => {
|
||||
return (
|
||||
<PopupVisibleProvider visible={false}>
|
||||
<ActionContextProvider
|
||||
visible={visible || visibleWithURL}
|
||||
visible={props.visible || visible || visibleWithURL}
|
||||
setVisible={_setVisible}
|
||||
openMode={openMode}
|
||||
openSize={openSize}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { Field } from '@formily/core';
|
||||
import { useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { untracked } from '@formily/reactive';
|
||||
import { nextTick } from '@nocobase/utils/client';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
@ -20,7 +21,6 @@ import { useVariables } from '../../../../variables';
|
||||
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
|
||||
import { useSubFormValue } from '../../association-field/hooks';
|
||||
import { isDisplayField } from '../utils';
|
||||
import { untracked } from '@formily/reactive';
|
||||
|
||||
/**
|
||||
* 用于懒加载 Form 区块中只用于展示的关联字段的值
|
||||
@ -97,7 +97,7 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
|
||||
|
||||
variables
|
||||
.parseVariable(variableString, formVariable, { appends })
|
||||
.then((value) => {
|
||||
.then(({ value }) => {
|
||||
nextTick(() => {
|
||||
const result = transformVariableValue(value, { targetCollectionField: collectionFieldRef.current });
|
||||
// fix https://nocobase.height.app/T-2608
|
||||
|
@ -14,6 +14,7 @@ import { getValuesByPath } from '@nocobase/utils/client';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useRecordIndex } from '../../../../../src/record-provider';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { useCollectionRecord } from '../../../../data-source/collection-record/CollectionRecordProvider';
|
||||
import { useFlag } from '../../../../flag-provider';
|
||||
@ -39,6 +40,7 @@ const useParseDefaultValue = () => {
|
||||
const { getField } = useCollection_deprecated();
|
||||
const { isSpecialCase, setDefaultValue } = useSpecialCase();
|
||||
const index = useRecordIndex();
|
||||
const { type, form } = useFormBlockContext();
|
||||
|
||||
/**
|
||||
* name: 如 $user
|
||||
@ -55,6 +57,13 @@ const useParseDefaultValue = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// fix https://github.com/nocobase/nocobase/issues/4868
|
||||
// fix http://localhost:12000/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/182
|
||||
// to clear the default value of the field
|
||||
if (type === 'update' && fieldSchema.default && field.form === form) {
|
||||
field.setValue?.(record?.data?.[fieldSchema.name]);
|
||||
}
|
||||
|
||||
if (
|
||||
fieldSchema.default == null ||
|
||||
isInSetDefaultValueDialog ||
|
||||
@ -86,7 +95,18 @@ const useParseDefaultValue = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const value = transformVariableValue(await variables.parseVariable(fieldSchema.default, localVariables), {
|
||||
const { value: parsedValue, collectionName: collectionNameOfVariable } = await variables.parseVariable(
|
||||
fieldSchema.default,
|
||||
localVariables,
|
||||
);
|
||||
|
||||
// fix https://tasks.aliyun.nocobase.com/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/199
|
||||
if (collectionField.target && collectionField.target !== collectionNameOfVariable) {
|
||||
field.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const value = transformVariableValue(parsedValue, {
|
||||
targetCollectionField: collectionField,
|
||||
});
|
||||
|
||||
@ -154,7 +174,7 @@ const useParseDefaultValue = () => {
|
||||
// 解决子表格(或子表单)中新增一行数据时,默认值不生效的问题
|
||||
field.setValue(fieldSchema.default);
|
||||
}
|
||||
}, [fieldSchema.default, localVariables]);
|
||||
}, [fieldSchema.default, localVariables, type]);
|
||||
};
|
||||
|
||||
export default useParseDefaultValue;
|
||||
|
@ -156,14 +156,14 @@ export async function replaceVariables(
|
||||
}
|
||||
|
||||
const waitForParsing = value.match(REGEX_OF_VARIABLE_IN_EXPRESSION)?.map(async (item) => {
|
||||
const result = await variables.parseVariable(item, localVariables);
|
||||
const { value: parsedValue } = await variables.parseVariable(item, localVariables);
|
||||
|
||||
// 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错
|
||||
const id = `_${uid()}`;
|
||||
|
||||
scope[id] = result;
|
||||
scope[id] = parsedValue;
|
||||
store[item] = id;
|
||||
return result;
|
||||
return parsedValue;
|
||||
});
|
||||
|
||||
if (waitForParsing) {
|
||||
|
@ -33,7 +33,7 @@ interface PopupsVisibleProviderProps {
|
||||
setVisible?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
interface PopupProps {
|
||||
export interface PopupProps {
|
||||
params: PopupParams;
|
||||
context: PopupContext;
|
||||
/**
|
||||
|
@ -7,13 +7,31 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
|
||||
const PopupSettingsContext = React.createContext({
|
||||
enableURL: true,
|
||||
});
|
||||
|
||||
export const PopupSettingsProvider: FC<{
|
||||
/**
|
||||
* @default true
|
||||
*/
|
||||
enableURL?: boolean;
|
||||
}> = (props) => {
|
||||
const { enableURL = true } = props;
|
||||
const value = useMemo(() => ({ enableURL }), [enableURL]);
|
||||
|
||||
return <PopupSettingsContext.Provider value={value}>{props.children}</PopupSettingsContext.Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for accessing the popup settings.
|
||||
* @returns The popup settings.
|
||||
*/
|
||||
export const usePopupSettings = () => {
|
||||
const { enableURL } = React.useContext(PopupSettingsContext);
|
||||
|
||||
const isPopupVisibleControlledByURL = useCallback(() => {
|
||||
const pathname = window.location.pathname;
|
||||
const hash = window.location.hash;
|
||||
@ -21,8 +39,8 @@ export const usePopupSettings = () => {
|
||||
const isNewMobileMode = pathname?.includes('/m/');
|
||||
const isPCMode = pathname?.includes('/admin/');
|
||||
|
||||
return (isPCMode || isNewMobileMode) && !isOldMobileMode;
|
||||
}, []);
|
||||
return (isPCMode || isNewMobileMode) && !isOldMobileMode && enableURL;
|
||||
}, [enableURL]);
|
||||
|
||||
return {
|
||||
/** 弹窗窗口的显隐是否由 URL 控制 */
|
||||
|
@ -15,3 +15,4 @@ export * from './Page.Settings';
|
||||
export { PagePopups } from './PagePopups';
|
||||
export { storePopupContext } from './pagePopupUtils';
|
||||
export * from './PageTab.Settings';
|
||||
export { PopupSettingsProvider } from './PopupSettingsProvider';
|
||||
|
@ -127,7 +127,16 @@ export const getPopupPathFromParams = (params: PopupParams) => {
|
||||
* Note: use this hook in a plugin is not recommended
|
||||
* @returns
|
||||
*/
|
||||
export const usePopupUtils = () => {
|
||||
export const usePopupUtils = (
|
||||
options: {
|
||||
/**
|
||||
* when the popup does not support opening via URL, you can control the display status of the popup through this method
|
||||
* @param visible
|
||||
* @returns
|
||||
*/
|
||||
setVisible?: (visible: boolean) => void;
|
||||
} = {},
|
||||
) => {
|
||||
const navigate = useNavigateNoUpdate();
|
||||
const location = useLocationNoUpdate();
|
||||
const fieldSchema = useFieldSchema();
|
||||
@ -141,14 +150,16 @@ export const usePopupUtils = () => {
|
||||
const { params: popupParams } = useCurrentPopupContext();
|
||||
const service = useDataBlockRequest();
|
||||
const { isPopupVisibleControlledByURL } = usePopupSettings();
|
||||
const { setVisible: setVisibleFromAction } = useContext(ActionContext);
|
||||
const { setVisible: _setVisibleFromAction } = useContext(ActionContext);
|
||||
const { updatePopupContext } = usePopupContextInActionOrAssociationField();
|
||||
const currentPopupContext = useCurrentPopupContext();
|
||||
const getSourceId = useCallback(
|
||||
(_parentRecordData?: Record<string, any>) =>
|
||||
(_parentRecordData || parentRecord?.data)?.[cm.getSourceKeyByAssociation(association)],
|
||||
[parentRecord, association],
|
||||
);
|
||||
const currentPopupUidWithoutOpened = fieldSchema?.['x-uid'];
|
||||
|
||||
const setVisibleFromAction = options.setVisible || _setVisibleFromAction;
|
||||
|
||||
const getNewPathname = useCallback(
|
||||
({
|
||||
@ -199,6 +210,7 @@ export const usePopupUtils = () => {
|
||||
parentRecordData,
|
||||
collectionNameUsedInURL,
|
||||
popupUidUsedInURL,
|
||||
customActionSchema,
|
||||
}: {
|
||||
recordData?: Record<string, any>;
|
||||
parentRecordData?: Record<string, any>;
|
||||
@ -206,11 +218,13 @@ export const usePopupUtils = () => {
|
||||
collectionNameUsedInURL?: string;
|
||||
/** if this value exists, it will be saved in the URL */
|
||||
popupUidUsedInURL?: string;
|
||||
customActionSchema?: ISchema;
|
||||
} = {}) => {
|
||||
if (!isPopupVisibleControlledByURL()) {
|
||||
return setVisibleFromAction?.(true);
|
||||
}
|
||||
|
||||
const currentPopupUidWithoutOpened = customActionSchema?.['x-uid'] || fieldSchema?.['x-uid'];
|
||||
const sourceId = getSourceId(parentRecordData);
|
||||
|
||||
recordData = recordData || record?.data;
|
||||
@ -227,7 +241,7 @@ export const usePopupUtils = () => {
|
||||
}
|
||||
|
||||
storePopupContext(currentPopupUidWithoutOpened, {
|
||||
schema: fieldSchema,
|
||||
schema: customActionSchema || fieldSchema,
|
||||
record: new CollectionRecord({ isNew: false, data: recordData }),
|
||||
parentRecord: parentRecordData ? new CollectionRecord({ isNew: false, data: parentRecordData }) : parentRecord,
|
||||
service,
|
||||
@ -237,7 +251,7 @@ export const usePopupUtils = () => {
|
||||
sourceId,
|
||||
});
|
||||
|
||||
updatePopupContext(getPopupContext());
|
||||
updatePopupContext(getPopupContext(), customActionSchema);
|
||||
|
||||
navigate(withSearchParams(`${url}${pathname}`));
|
||||
},
|
||||
@ -256,7 +270,6 @@ export const usePopupUtils = () => {
|
||||
isPopupVisibleControlledByURL,
|
||||
getSourceId,
|
||||
getPopupContext,
|
||||
currentPopupUidWithoutOpened,
|
||||
],
|
||||
);
|
||||
|
||||
@ -317,6 +330,7 @@ export const usePopupUtils = () => {
|
||||
closePopup,
|
||||
savePopupSchemaToSchema,
|
||||
getPopupSchemaFromSchema,
|
||||
context: currentPopupContext,
|
||||
/**
|
||||
* @deprecated
|
||||
* TODO: remove this
|
||||
|
@ -29,18 +29,19 @@ export const usePopupContextInActionOrAssociationField = () => {
|
||||
const { dn } = useDesignable();
|
||||
|
||||
const updatePopupContext = useCallback(
|
||||
(context: PopupContext) => {
|
||||
(context: PopupContext, customSchema?: ISchema) => {
|
||||
customSchema = customSchema || fieldSchema;
|
||||
context = _.omitBy(context, _.isNil) as PopupContext;
|
||||
|
||||
if (_.isEqual(context, getPopupContextFromActionOrAssociationFieldSchema(fieldSchema))) {
|
||||
if (_.isEqual(context, getPopupContextFromActionOrAssociationFieldSchema(customSchema))) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldSchema[CONTEXT_SCHEMA_KEY] = context;
|
||||
customSchema[CONTEXT_SCHEMA_KEY] = context;
|
||||
|
||||
return dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
'x-uid': customSchema['x-uid'],
|
||||
[CONTEXT_SCHEMA_KEY]: context,
|
||||
},
|
||||
});
|
||||
|
@ -7,13 +7,13 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import _, { every, findIndex, some } from 'lodash';
|
||||
import Handlebars from 'handlebars';
|
||||
import _, { every, findIndex, some } from 'lodash';
|
||||
import { replaceVariableValue } from '../../../block-provider/hooks';
|
||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
|
||||
import { isVariable } from '../../../variables/utils/isVariable';
|
||||
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
|
||||
import { getJsonLogic } from '../../common/utils/logic';
|
||||
import { replaceVariableValue } from '../../../block-provider/hooks';
|
||||
|
||||
type VariablesCtx = {
|
||||
/** 当前登录的用户 */
|
||||
@ -102,12 +102,14 @@ export const conditionAnalyses = async ({
|
||||
}
|
||||
|
||||
const targetVariableName = targetFieldToVariableString(getTargetField(condition));
|
||||
const targetValue = variables.parseVariable(targetVariableName, localVariables, {
|
||||
doNotRequest: true,
|
||||
});
|
||||
const targetValue = variables
|
||||
.parseVariable(targetVariableName, localVariables, {
|
||||
doNotRequest: true,
|
||||
})
|
||||
.then(({ value }) => value);
|
||||
|
||||
const parsingResult = isVariable(jsonlogic?.value)
|
||||
? [variables.parseVariable(jsonlogic?.value, localVariables), targetValue]
|
||||
? [variables.parseVariable(jsonlogic?.value, localVariables).then(({ value }) => value), targetValue]
|
||||
: [jsonlogic?.value, targetValue];
|
||||
|
||||
try {
|
||||
|
@ -55,7 +55,7 @@ const useParseDataScopeFilter = ({ exclude = defaultExclude }: Props = {}) => {
|
||||
if (exclude.includes(getVariableName(value))) {
|
||||
return value;
|
||||
}
|
||||
const result = variables?.parseVariable(value, localVariables);
|
||||
const result = variables?.parseVariable(value, localVariables).then(({ value }) => value);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
@ -69,7 +69,7 @@ const VariablesProvider = ({ children }) => {
|
||||
* 2. 如果某个 `key` 不存在,且 `key` 是一个关联字段,则从 api 中获取数据,并缓存到 `ctx` 中
|
||||
* 3. 如果某个 `key` 不存在,且 `key` 不是一个关联字段,则返回当前值
|
||||
*/
|
||||
const getValue = useCallback(
|
||||
const getResult = useCallback(
|
||||
async (
|
||||
variablePath: string,
|
||||
localVariables?: VariableOption[],
|
||||
@ -87,13 +87,23 @@ const VariablesProvider = ({ children }) => {
|
||||
const { fieldPath, dataSource, variableOption } = getFieldPath(variableName, _variableToCollectionName);
|
||||
let collectionName = fieldPath;
|
||||
|
||||
const { fieldPath: fieldPathOfVariable } = getFieldPath(variablePath, _variableToCollectionName);
|
||||
const collectionNameOfVariable =
|
||||
list.length === 1
|
||||
? variableOption.collectionName
|
||||
: getCollectionJoinField(fieldPathOfVariable, dataSource)?.target;
|
||||
|
||||
if (!(variableName in current)) {
|
||||
throw new Error(`VariablesProvider: ${variableName} is not found`);
|
||||
}
|
||||
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
if (current == null) {
|
||||
return current === undefined ? variableOption.defaultValue : current;
|
||||
return {
|
||||
value: current === undefined ? variableOption.defaultValue : current,
|
||||
dataSource,
|
||||
collectionName: collectionNameOfVariable,
|
||||
};
|
||||
}
|
||||
|
||||
const key = list[index];
|
||||
@ -171,8 +181,12 @@ const VariablesProvider = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = compile(_.isFunction(current) ? current() : current);
|
||||
return result === undefined ? variableOption.defaultValue : result;
|
||||
const _value = compile(_.isFunction(current) ? current() : current);
|
||||
return {
|
||||
value: _value === undefined ? variableOption.defaultValue : _value,
|
||||
dataSource,
|
||||
collectionName: collectionNameOfVariable,
|
||||
};
|
||||
},
|
||||
[getCollectionJoinField],
|
||||
);
|
||||
@ -248,11 +262,14 @@ const VariablesProvider = ({ children }) => {
|
||||
}
|
||||
|
||||
const path = getPath(str);
|
||||
const value = await getValue(path, localVariables as VariableOption[], options);
|
||||
const result = await getResult(path, localVariables as VariableOption[], options);
|
||||
|
||||
return uniq(filterEmptyValues(value));
|
||||
return {
|
||||
...result,
|
||||
value: uniq(filterEmptyValues(result.value)),
|
||||
};
|
||||
},
|
||||
[getValue],
|
||||
[getResult],
|
||||
);
|
||||
|
||||
const getCollectionField = useCallback(
|
||||
|
@ -276,7 +276,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('test');
|
||||
expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
@ -286,7 +286,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.nickname }}')).toBe('from request');
|
||||
expect(await result.current.parseVariable('{{ $user.nickname }}').then(({ value }) => value)).toBe(
|
||||
'from request',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -296,7 +298,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToField }}')).toEqual({
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToField }}').then(({ value }) => value)).toEqual({
|
||||
id: 0,
|
||||
name: '$user.belongsToField',
|
||||
});
|
||||
@ -309,9 +311,11 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })).toBe(
|
||||
null,
|
||||
);
|
||||
expect(
|
||||
await result.current
|
||||
.parseVariable('{{ $user.belongsToField }}', undefined, { doNotRequest: true })
|
||||
.then(({ value }) => value),
|
||||
).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@ -321,7 +325,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToField.name }}')).toBe('$user.belongsToField');
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToField.name }}').then(({ value }) => value)).toBe(
|
||||
'$user.belongsToField',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -331,7 +337,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.hasManyField',
|
||||
@ -340,7 +346,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField.name }}')).toEqual(['$user.hasManyField']);
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField.name }}').then(({ value }) => value)).toEqual([
|
||||
'$user.hasManyField',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -350,7 +358,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
|
||||
).toEqual([
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.hasManyField.hasManyField',
|
||||
@ -359,15 +369,34 @@ describe('useVariables', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('$user.hasManyField', async () => {
|
||||
const { result } = renderHook(() => useVariables(), {
|
||||
wrapper: Providers,
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual({
|
||||
collectionName: 'test',
|
||||
dataSource: 'main',
|
||||
value: [
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.hasManyField',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('$user.hasManyField.hasManyField.name', async () => {
|
||||
const { result } = renderHook(() => useVariables(), {
|
||||
wrapper: Providers,
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}')).toEqual([
|
||||
'$user.hasManyField.hasManyField',
|
||||
]);
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $user.hasManyField.hasManyField.name }}').then(({ value }) => value),
|
||||
).toEqual(['$user.hasManyField.hasManyField']);
|
||||
});
|
||||
});
|
||||
|
||||
@ -393,7 +422,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField }}')).toEqual([
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField }}').then(({ value }) => value)).toEqual([
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.hasManyField',
|
||||
@ -402,7 +431,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}')).toEqual([
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $user.hasManyField.hasManyField }}').then(({ value }) => value),
|
||||
).toEqual([
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.hasManyField.hasManyField',
|
||||
@ -417,7 +448,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToManyField }}')).toEqual([
|
||||
expect(await result.current.parseVariable('{{ $user.belongsToManyField }}').then(({ value }) => value)).toEqual([
|
||||
{
|
||||
id: 0,
|
||||
name: '$user.belongsToManyField',
|
||||
@ -608,7 +639,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.name }}')).toBe('new variable');
|
||||
expect(await result.current.parseVariable('{{ $new.name }}').then(({ value }) => value)).toBe('new variable');
|
||||
});
|
||||
});
|
||||
|
||||
@ -627,7 +658,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(null);
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@ -647,7 +678,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe('default value');
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe('default value');
|
||||
});
|
||||
});
|
||||
|
||||
@ -667,7 +698,7 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}')).toBe(undefined);
|
||||
expect(await result.current.parseVariable('{{ $new.noExist }}').then(({ value }) => value)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@ -686,8 +717,30 @@ describe('useVariables', () => {
|
||||
ctx: {
|
||||
name: 'local variable',
|
||||
},
|
||||
collectionName: 'local',
|
||||
dataSource: 'local',
|
||||
}),
|
||||
).toBe('local variable');
|
||||
).toEqual({
|
||||
value: 'local variable',
|
||||
dataSource: 'local',
|
||||
});
|
||||
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $local }}', {
|
||||
name: '$local',
|
||||
ctx: {
|
||||
name: 'local variable',
|
||||
},
|
||||
collectionName: 'local',
|
||||
dataSource: 'local',
|
||||
}),
|
||||
).toEqual({
|
||||
value: {
|
||||
name: 'local variable',
|
||||
},
|
||||
collectionName: 'local',
|
||||
dataSource: 'local',
|
||||
});
|
||||
|
||||
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
|
||||
expect(result.current.getVariable('$local')).toBe(null);
|
||||
@ -703,14 +756,16 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $local.name }}', [
|
||||
{
|
||||
name: '$local',
|
||||
ctx: {
|
||||
name: 'local variable',
|
||||
await result.current
|
||||
.parseVariable('{{ $local.name }}', [
|
||||
{
|
||||
name: '$local',
|
||||
ctx: {
|
||||
name: 'local variable',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
])
|
||||
.then(({ value }) => value),
|
||||
).toBe('local variable');
|
||||
|
||||
// 由于 $local 是一个局部变量,所以不会被缓存到 ctx 中
|
||||
@ -764,7 +819,9 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toEqual({
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
|
||||
).toEqual({
|
||||
id: 0,
|
||||
name: '$some.belongsToField.belongsToField',
|
||||
});
|
||||
@ -783,7 +840,9 @@ describe('useVariables', () => {
|
||||
|
||||
await waitFor(async () => {
|
||||
// 只有解析后的值是 undefined 才会使用默认值
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe(null);
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
|
||||
).toBe(null);
|
||||
});
|
||||
|
||||
// 会覆盖之前的 $some
|
||||
@ -798,7 +857,9 @@ describe('useVariables', () => {
|
||||
|
||||
await waitFor(async () => {
|
||||
// 解析后的值是 undefined 所以会返回上面设置的默认值
|
||||
expect(await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}')).toBe('default value');
|
||||
expect(
|
||||
await result.current.parseVariable('{{ $some.belongsToField.belongsToField }}').then(({ value }) => value),
|
||||
).toBe('default value');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -47,7 +47,14 @@ export interface VariablesContextType {
|
||||
/** do not request when the association field is empty */
|
||||
doNotRequest?: boolean;
|
||||
},
|
||||
) => Promise<any>;
|
||||
) => Promise<{
|
||||
value: any;
|
||||
/**
|
||||
* 当前变量所对应的数据表的名称,如果为空,则表示当前变量是一个普通类型的变量(字符串、数字等)
|
||||
*/
|
||||
collectionName?: string;
|
||||
dataSource?: string;
|
||||
}>;
|
||||
/**
|
||||
* 注册变量
|
||||
* @param variableOption 新变量的配置
|
||||
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 { ACLActionProvider, PopupSettingsProvider } from '@nocobase/client';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export const BulkEditActionDecorator: FC = (props) => {
|
||||
return (
|
||||
<PopupSettingsProvider enableURL={false}>
|
||||
<ACLActionProvider>{props.children}</ACLActionProvider>
|
||||
</PopupSettingsProvider>
|
||||
);
|
||||
};
|
@ -95,7 +95,7 @@ export const BulkEditField = (props: any) => {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField<Field>();
|
||||
const [type, setType] = useState<number>(BulkEditFormItemValueType.ChangedTo);
|
||||
const [type, setType] = useState<number>(BulkEditFormItemValueType.RemainsTheSame);
|
||||
const [value, setValue] = useState(null);
|
||||
const { getField } = useCollection_deprecated();
|
||||
const collectionField = getField(fieldSchema.name) || {};
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import { bulkEditActionSettings, deprecatedBulkEditActionSettings } from './BulkEditAction.Settings';
|
||||
import { BulkEditActionDecorator } from './BulkEditActionDecorator';
|
||||
import { BulkEditActionInitializer } from './BulkEditActionInitializer';
|
||||
import {
|
||||
BulkEditBlockInitializers_deprecated,
|
||||
@ -25,7 +26,7 @@ import { BulkEditField } from './component/BulkEditField';
|
||||
import { useCustomizeBulkEditActionProps } from './utils';
|
||||
export class PluginActionBulkEditClient extends Plugin {
|
||||
async load() {
|
||||
this.app.addComponents({ BulkEditField });
|
||||
this.app.addComponents({ BulkEditField, BulkEditActionDecorator });
|
||||
this.app.addScopes({ useCustomizeBulkEditActionProps });
|
||||
this.app.schemaSettingsManager.add(deprecatedBulkEditActionSettings);
|
||||
this.app.schemaSettingsManager.add(bulkEditActionSettings);
|
||||
@ -45,7 +46,7 @@ export class PluginActionBulkEditClient extends Plugin {
|
||||
Component: BulkEditActionInitializer,
|
||||
schema: {
|
||||
'x-align': 'right',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-decorator': 'BulkEditActionDecorator',
|
||||
'x-action': 'customize:bulkEdit',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:bulkEdit',
|
||||
|
@ -64,7 +64,7 @@ export const useCustomizeBulkUpdateActionProps = () => {
|
||||
}
|
||||
|
||||
if (isVariable(value)) {
|
||||
const result = await variables?.parseVariable(value, localVariables);
|
||||
const result = await variables?.parseVariable(value, localVariables).then(({ value }) => value);
|
||||
if (result) {
|
||||
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
ActionContextProvider,
|
||||
CollectionProvider_deprecated,
|
||||
FormBlockContext,
|
||||
PopupSettingsProvider,
|
||||
RecordProvider,
|
||||
fetchTemplateData,
|
||||
useACLActionParamsContext,
|
||||
@ -203,7 +204,9 @@ export const DuplicateAction = observer(
|
||||
{/* 这里的 record 就是弹窗中创建表单的 sourceRecord */}
|
||||
<RecordProvider record={{ ...parentRecordData, __collection: duplicateCollection || __collection }}>
|
||||
<ActionContextProvider value={{ ...ctx, visible, setVisible }}>
|
||||
<RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
|
||||
<PopupSettingsProvider enableURL={false}>
|
||||
<RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
|
||||
</PopupSettingsProvider>
|
||||
</ActionContextProvider>
|
||||
</RecordProvider>
|
||||
</CollectionProvider_deprecated>
|
||||
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 { ACLActionProvider } from '@nocobase/client';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export const DuplicateActionDecorator: FC = (props) => {
|
||||
return <ACLActionProvider>{props.children}</ACLActionProvider>;
|
||||
};
|
@ -19,7 +19,7 @@ export const DuplicateActionInitializer = (props) => {
|
||||
'x-acl-action': 'create',
|
||||
title: '{{ t("Duplicate") }}',
|
||||
'x-component': 'Action.Link',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-decorator': 'DuplicateActionDecorator',
|
||||
'x-component-props': {
|
||||
openMode: defaultOpenMode,
|
||||
component: 'DuplicateAction',
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { Plugin, useActionAvailable } from '@nocobase/client';
|
||||
import { DuplicateAction } from './DuplicateAction';
|
||||
import { deprecatedDuplicateActionSettings, duplicateActionSettings } from './DuplicateAction.Settings';
|
||||
import { DuplicateActionDecorator } from './DuplicateActionDecorator';
|
||||
import { DuplicateActionInitializer } from './DuplicateActionInitializer';
|
||||
import { DuplicatePluginProvider } from './DuplicatePluginProvider';
|
||||
|
||||
@ -19,6 +20,7 @@ export class PluginActionDuplicateClient extends Plugin {
|
||||
this.app.addComponents({
|
||||
DuplicateActionInitializer,
|
||||
DuplicateAction,
|
||||
DuplicateActionDecorator,
|
||||
});
|
||||
this.app.schemaSettingsManager.add(deprecatedDuplicateActionSettings);
|
||||
this.app.schemaSettingsManager.add(duplicateActionSettings);
|
||||
@ -31,7 +33,7 @@ export class PluginActionDuplicateClient extends Plugin {
|
||||
'x-action': 'duplicate',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:duplicate',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-decorator': 'DuplicateActionDecorator',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
},
|
||||
|
@ -10,12 +10,12 @@
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { RecursionField, Schema, observer, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
PopupContextProvider,
|
||||
RecordProvider,
|
||||
VariablePopupRecordProvider,
|
||||
getLabelFormatValue,
|
||||
useCollection,
|
||||
useCollectionParentRecordData,
|
||||
usePopupUtils,
|
||||
useProps,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
@ -23,10 +23,11 @@ import { parseExpression } from 'cron-parser';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import get from 'lodash/get';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Calendar as BigCalendar, View, dayjsLocalizer } from 'react-big-calendar';
|
||||
import * as dates from 'react-big-calendar/lib/utils/dates';
|
||||
import { i18nt, useTranslation } from '../../locale';
|
||||
import { CalendarRecordViewer, findEventSchema } from './CalendarRecordViewer';
|
||||
import Header from './components/Header';
|
||||
import { CalendarToolbarContext } from './context';
|
||||
import GlobalStyle from './global.style';
|
||||
@ -160,54 +161,24 @@ const useEvents = (dataSource: any, fieldNames: any, date: Date, view: (typeof W
|
||||
}, [dataSource, fieldNames.start, fieldNames.end, fieldNames.id, fieldNames.title, date, view, t]);
|
||||
};
|
||||
|
||||
const CalendarRecordViewer = (props) => {
|
||||
const { visible, setVisible, record } = props;
|
||||
const { t } = useTranslation();
|
||||
const collection = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const eventSchema: Schema = useMemo(
|
||||
() =>
|
||||
fieldSchema.reduceProperties((buf, current) => {
|
||||
if (current['x-component'].endsWith('.Event')) {
|
||||
return current;
|
||||
}
|
||||
return buf;
|
||||
}, null),
|
||||
[],
|
||||
);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
eventSchema && (
|
||||
<DeleteEventContext.Provider value={{ close }}>
|
||||
<ActionContextProvider value={{ visible, setVisible }}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<VariablePopupRecordProvider recordData={record} collection={collection}>
|
||||
<RecursionField schema={eventSchema} name={eventSchema.name} />
|
||||
</VariablePopupRecordProvider>
|
||||
</RecordProvider>
|
||||
</ActionContextProvider>
|
||||
</DeleteEventContext.Provider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const Calendar: any = withDynamicSchemaProps(
|
||||
observer(
|
||||
(props: any) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { openPopup } = usePopupUtils({
|
||||
setVisible,
|
||||
});
|
||||
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { dataSource, fieldNames, showLunar } = useProps(props);
|
||||
const height = useCalenderHeight();
|
||||
const [date, setDate] = useState<Date>(new Date());
|
||||
const [view, setView] = useState<View>('month');
|
||||
const events = useEvents(dataSource, fieldNames, date, view);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [record, setRecord] = useState<any>({});
|
||||
const { wrapSSR, hashId, componentCls: containerClassName } = useStyle();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
|
||||
const components = useMemo(() => {
|
||||
return {
|
||||
@ -247,50 +218,57 @@ export const Calendar: any = withDynamicSchemaProps(
|
||||
};
|
||||
return wrapSSR(
|
||||
<div className={`${hashId} ${containerClassName}`} style={{ height: height || 700 }}>
|
||||
<GlobalStyle />
|
||||
<CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<BigCalendar
|
||||
popup
|
||||
selectable
|
||||
events={events}
|
||||
view={view}
|
||||
views={Weeks}
|
||||
date={date}
|
||||
step={60}
|
||||
showMultiDayTimes
|
||||
messages={messages}
|
||||
onNavigate={setDate}
|
||||
onView={setView}
|
||||
onSelectSlot={(slotInfo) => {
|
||||
console.log('onSelectSlot', slotInfo);
|
||||
}}
|
||||
onDoubleClickEvent={() => {
|
||||
console.log('onDoubleClickEvent');
|
||||
}}
|
||||
onSelectEvent={(event) => {
|
||||
const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
|
||||
|
||||
setRecord(record);
|
||||
setVisible(true);
|
||||
}}
|
||||
formats={{
|
||||
monthHeaderFormat: 'YYYY-M',
|
||||
agendaDateFormat: 'M-DD',
|
||||
dayHeaderFormat: 'YYYY-M-DD',
|
||||
dayRangeHeaderFormat: ({ start, end }, culture, local) => {
|
||||
if (dates.eq(start, end, 'month')) {
|
||||
return local.format(start, 'YYYY-M', culture);
|
||||
<PopupContextProvider visible={visible} setVisible={setVisible}>
|
||||
<GlobalStyle />
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<CalendarRecordViewer />
|
||||
</RecordProvider>
|
||||
<BigCalendar
|
||||
popup
|
||||
selectable
|
||||
events={events}
|
||||
view={view}
|
||||
views={Weeks}
|
||||
date={date}
|
||||
step={60}
|
||||
showMultiDayTimes
|
||||
messages={messages}
|
||||
onNavigate={setDate}
|
||||
onView={setView}
|
||||
onSelectSlot={(slotInfo) => {
|
||||
console.log('onSelectSlot', slotInfo);
|
||||
}}
|
||||
onDoubleClickEvent={() => {
|
||||
console.log('onDoubleClickEvent');
|
||||
}}
|
||||
onSelectEvent={(event) => {
|
||||
const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`;
|
||||
},
|
||||
}}
|
||||
components={components}
|
||||
localizer={localizer}
|
||||
/>
|
||||
record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
|
||||
|
||||
setRecord(record);
|
||||
openPopup({
|
||||
recordData: record,
|
||||
customActionSchema: findEventSchema(fieldSchema),
|
||||
});
|
||||
}}
|
||||
formats={{
|
||||
monthHeaderFormat: 'YYYY-M',
|
||||
agendaDateFormat: 'M-DD',
|
||||
dayHeaderFormat: 'YYYY-M-DD',
|
||||
dayRangeHeaderFormat: ({ start, end }, culture, local) => {
|
||||
if (dates.eq(start, end, 'month')) {
|
||||
return local.format(start, 'YYYY-M', culture);
|
||||
}
|
||||
return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`;
|
||||
},
|
||||
}}
|
||||
components={components}
|
||||
localizer={localizer}
|
||||
/>
|
||||
</PopupContextProvider>
|
||||
</div>,
|
||||
);
|
||||
},
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 { RecursionField, Schema, useFieldSchema } from '@formily/react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
export const CalendarRecordViewer: FC = (props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const eventSchema: Schema = useMemo(() => findEventSchema(fieldSchema), [fieldSchema]);
|
||||
|
||||
if (!eventSchema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RecursionField schema={eventSchema} name={eventSchema.name} />;
|
||||
};
|
||||
|
||||
export function findEventSchema(schema: Schema) {
|
||||
if (schema['x-component'].endsWith('.Event')) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
return schema.reduceProperties((buf, current) => {
|
||||
if (current['x-component'].endsWith('.Event')) {
|
||||
return current;
|
||||
}
|
||||
return buf;
|
||||
}, null);
|
||||
}
|
@ -8,11 +8,35 @@
|
||||
*/
|
||||
|
||||
import { observer } from '@formily/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
PopupContextProvider,
|
||||
useActionContext,
|
||||
useCollection,
|
||||
useCollectionRecordData,
|
||||
VariablePopupRecordProvider,
|
||||
} from '@nocobase/client';
|
||||
import React, { useCallback } from 'react';
|
||||
import { DeleteEventContext } from './Calendar';
|
||||
|
||||
export const Event = observer(
|
||||
(props) => {
|
||||
return <>{props.children}</>;
|
||||
const { visible, setVisible } = useActionContext();
|
||||
const recordData = useCollectionRecordData();
|
||||
const collection = useCollection();
|
||||
|
||||
const close = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
return (
|
||||
<PopupContextProvider visible={visible} setVisible={setVisible}>
|
||||
<DeleteEventContext.Provider value={{ close }}>
|
||||
<VariablePopupRecordProvider recordData={recordData} collection={collection}>
|
||||
{props.children}
|
||||
</VariablePopupRecordProvider>
|
||||
</DeleteEventContext.Provider>
|
||||
</PopupContextProvider>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Event' },
|
||||
);
|
||||
|
@ -7,10 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Schema } from '@formily/react';
|
||||
import {
|
||||
Collection,
|
||||
CollectionFieldInterfaceManager,
|
||||
CollectionFieldOptions,
|
||||
CollectionManager,
|
||||
SchemaInitializerItemType,
|
||||
i18n,
|
||||
@ -19,18 +19,16 @@ import {
|
||||
useDataSourceManager,
|
||||
useVariables,
|
||||
} from '@nocobase/client';
|
||||
import { flatten, parse, unflatten } from '@nocobase/utils/client';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import deepmerge from 'deepmerge';
|
||||
import { default as _, default as lodash } from 'lodash';
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||
import { Schema } from '@formily/react';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { ChartFilterContext } from '../filter/FilterProvider';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { flatten, parse, unflatten } from '@nocobase/utils/client';
|
||||
import lodash from 'lodash';
|
||||
import { getFormulaComponent, getValuesByPath } from '../utils';
|
||||
import deepmerge from 'deepmerge';
|
||||
import { findSchema, getFilterFieldPrefix, parseFilterFieldName } from '../filter/utils';
|
||||
import _ from 'lodash';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { getFormulaComponent, getValuesByPath } from '../utils';
|
||||
|
||||
export const useCustomFieldInterface = () => {
|
||||
const { getInterface } = useCollectionManager_deprecated();
|
||||
@ -420,7 +418,7 @@ export const useChartFilter = () => {
|
||||
if (['$user', '$date', '$nDate', '$nRole', '$nFilter'].some((n) => value.includes(n))) {
|
||||
return value;
|
||||
}
|
||||
const result = variables?.parseVariable(value);
|
||||
const result = variables?.parseVariable(value).then(({ value }) => value);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
@ -8,11 +8,28 @@
|
||||
*/
|
||||
|
||||
import { observer } from '@formily/react';
|
||||
import {
|
||||
PopupContextProvider,
|
||||
useActionContext,
|
||||
useCollection,
|
||||
useCollectionRecordData,
|
||||
VariablePopupRecordProvider,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
export const Event = observer(
|
||||
(props) => {
|
||||
return <>{props.children}</>;
|
||||
const { visible, setVisible } = useActionContext();
|
||||
const recordData = useCollectionRecordData();
|
||||
const collection = useCollection();
|
||||
|
||||
return (
|
||||
<PopupContextProvider visible={visible} setVisible={setVisible}>
|
||||
<VariablePopupRecordProvider recordData={recordData} collection={collection}>
|
||||
{props.children}
|
||||
</VariablePopupRecordProvider>
|
||||
</PopupContextProvider>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Event' },
|
||||
);
|
||||
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { RecursionField, useFieldSchema } from '@formily/react';
|
||||
import { Schema } from '@nocobase/utils';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export const GanttRecordViewer: FC = (props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const eventSchema: Schema = fieldSchema.properties.detail;
|
||||
|
||||
if (!eventSchema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <RecursionField schema={eventSchema} name={eventSchema.name} />;
|
||||
};
|
@ -8,17 +8,16 @@
|
||||
*/
|
||||
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { RecursionField, Schema, useFieldSchema } from '@formily/react';
|
||||
import { RecursionField, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
PopupContextProvider,
|
||||
RecordProvider,
|
||||
VariablePopupRecordProvider,
|
||||
useAPIClient,
|
||||
useBlockRequestContext,
|
||||
useCollection,
|
||||
useCollectionParentRecordData,
|
||||
useCurrentAppInfo,
|
||||
useDesignable,
|
||||
usePopupUtils,
|
||||
useProps,
|
||||
useTableBlockContext,
|
||||
useToken,
|
||||
@ -26,7 +25,7 @@ import {
|
||||
} from '@nocobase/client';
|
||||
import { Spin, message } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGanttBlockContext } from '../../GanttBlockProvider';
|
||||
import { convertToBarTasks } from '../../helpers/bar-helper';
|
||||
@ -41,6 +40,7 @@ import { GridProps } from '../grid/grid';
|
||||
import { HorizontalScroll } from '../other/horizontal-scroll';
|
||||
import { StandardTooltipContent, Tooltip } from '../other/tooltip';
|
||||
import { VerticalScroll } from '../other/vertical-scroll';
|
||||
import { GanttRecordViewer } from './GanttRecordViewer';
|
||||
import useStyles from './style';
|
||||
import { TaskGantt } from './task-gantt';
|
||||
import { TaskGanttContentProps } from './task-gantt-content';
|
||||
@ -49,34 +49,7 @@ const getColumnWidth = (dataSetLength: any, clientWidth: any) => {
|
||||
const columnWidth = clientWidth / dataSetLength > 50 ? Math.floor(clientWidth / dataSetLength) + 20 : 50;
|
||||
return columnWidth;
|
||||
};
|
||||
export const DeleteEventContext = React.createContext({
|
||||
close: () => {},
|
||||
});
|
||||
const GanttRecordViewer = (props) => {
|
||||
const { visible, setVisible, record } = props;
|
||||
const { t } = useTranslation();
|
||||
const collection = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const eventSchema: Schema = fieldSchema.properties.detail;
|
||||
const close = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
eventSchema && (
|
||||
<DeleteEventContext.Provider value={{ close }}>
|
||||
<ActionContextProvider value={{ visible, setVisible }}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<VariablePopupRecordProvider recordData={record} collection={collection}>
|
||||
<RecursionField schema={eventSchema} name={eventSchema.name} />
|
||||
</VariablePopupRecordProvider>
|
||||
</RecordProvider>
|
||||
</ActionContextProvider>
|
||||
</DeleteEventContext.Provider>
|
||||
)
|
||||
);
|
||||
};
|
||||
const debounceHandleTaskChange = debounce(async (task: Task, resource, fieldNames, service, t) => {
|
||||
await resource.update({
|
||||
filterByTk: task.id,
|
||||
@ -160,7 +133,11 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
|
||||
});
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { openPopup } = usePopupUtils({
|
||||
setVisible,
|
||||
});
|
||||
const [record, setRecord] = useState<any>({});
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const [currentViewDate, setCurrentViewDate] = useState<Date | undefined>(undefined);
|
||||
const [taskListWidth, setTaskListWidth] = useState(0);
|
||||
const [svgContainerWidth, setSvgContainerWidth] = useState(0);
|
||||
@ -473,7 +450,10 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
return;
|
||||
}
|
||||
setRecord(recordData);
|
||||
setVisible(true);
|
||||
openPopup({
|
||||
recordData,
|
||||
customActionSchema: fieldSchema.properties.detail,
|
||||
});
|
||||
};
|
||||
const gridProps: GridProps = {
|
||||
columnWidth,
|
||||
@ -536,55 +516,59 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
`)}
|
||||
ref={ganttRef}
|
||||
>
|
||||
<GanttRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
|
||||
<RecursionField name={'table'} schema={fieldSchema.properties.table} />
|
||||
<div className={styles.wrapper} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>
|
||||
<TaskGantt
|
||||
gridProps={gridProps}
|
||||
calendarProps={calendarProps}
|
||||
barProps={barProps}
|
||||
ganttHeight={ganttHeight}
|
||||
scrollY={scrollY}
|
||||
scrollX={scrollX}
|
||||
ref={verticalGanttContainerRef}
|
||||
/>
|
||||
{ganttEvent.changedTask && (
|
||||
<Tooltip
|
||||
arrowIndent={arrowIndent}
|
||||
rowHeight={rowHeight}
|
||||
svgContainerHeight={svgContainerHeight}
|
||||
svgContainerWidth={svgContainerWidth}
|
||||
fontFamily={fontFamily}
|
||||
fontSize={fontSize}
|
||||
scrollX={scrollX}
|
||||
<PopupContextProvider visible={visible} setVisible={setVisible}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<GanttRecordViewer />
|
||||
</RecordProvider>
|
||||
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
|
||||
<RecursionField name={'table'} schema={fieldSchema.properties.table} />
|
||||
<div className={styles.wrapper} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>
|
||||
<TaskGantt
|
||||
gridProps={gridProps}
|
||||
calendarProps={calendarProps}
|
||||
barProps={barProps}
|
||||
ganttHeight={ganttHeight}
|
||||
scrollY={scrollY}
|
||||
task={ganttEvent.changedTask}
|
||||
scrollX={scrollX}
|
||||
ref={verticalGanttContainerRef}
|
||||
/>
|
||||
{ganttEvent.changedTask && (
|
||||
<Tooltip
|
||||
arrowIndent={arrowIndent}
|
||||
rowHeight={rowHeight}
|
||||
svgContainerHeight={svgContainerHeight}
|
||||
svgContainerWidth={svgContainerWidth}
|
||||
fontFamily={fontFamily}
|
||||
fontSize={fontSize}
|
||||
scrollX={scrollX}
|
||||
scrollY={scrollY}
|
||||
task={ganttEvent.changedTask}
|
||||
headerHeight={headerHeight}
|
||||
taskListWidth={taskListWidth}
|
||||
TooltipContent={TooltipContent}
|
||||
rtl={rtl}
|
||||
svgWidth={svgWidth}
|
||||
/>
|
||||
)}
|
||||
<VerticalScroll
|
||||
ganttFullHeight={ganttFullHeight}
|
||||
ganttHeight={ganttHeight}
|
||||
headerHeight={headerHeight}
|
||||
taskListWidth={taskListWidth}
|
||||
TooltipContent={TooltipContent}
|
||||
scroll={scrollY}
|
||||
onScroll={handleScrollY}
|
||||
rtl={rtl}
|
||||
svgWidth={svgWidth}
|
||||
/>
|
||||
)}
|
||||
<VerticalScroll
|
||||
ganttFullHeight={ganttFullHeight}
|
||||
ganttHeight={ganttHeight}
|
||||
headerHeight={headerHeight}
|
||||
scroll={scrollY}
|
||||
onScroll={handleScrollY}
|
||||
rtl={rtl}
|
||||
/>
|
||||
<Spin spinning={loading} style={{ visibility: 'hidden' }}>
|
||||
<HorizontalScroll
|
||||
svgWidth={svgWidth}
|
||||
taskListWidth={taskListWidth}
|
||||
scroll={scrollX}
|
||||
rtl={rtl}
|
||||
onScroll={handleScrollX}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
<Spin spinning={loading} style={{ visibility: 'hidden' }}>
|
||||
<HorizontalScroll
|
||||
svgWidth={svgWidth}
|
||||
taskListWidth={taskListWidth}
|
||||
scroll={scrollX}
|
||||
rtl={rtl}
|
||||
onScroll={handleScrollX}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
</PopupContextProvider>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -8,15 +8,29 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
PopupContextProvider,
|
||||
useCollection_deprecated,
|
||||
useCollectionManager_deprecated,
|
||||
usePopupUtils,
|
||||
useProps,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
import React, { useMemo } from 'react';
|
||||
import { MapBlockComponent } from '../components';
|
||||
import { MapBlockDrawer } from '../components/MapBlockDrawer';
|
||||
|
||||
export const MapBlock = withDynamicSchemaProps((props) => {
|
||||
const { context } = usePopupUtils();
|
||||
|
||||
// only render the popup
|
||||
if (context.currentLevel) {
|
||||
return (
|
||||
<PopupContextProvider>
|
||||
<MapBlockDrawer />
|
||||
</PopupContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { fieldNames } = useProps(props);
|
||||
|
||||
|
@ -8,11 +8,8 @@
|
||||
*/
|
||||
|
||||
import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/icons';
|
||||
import { RecursionField, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
RecordProvider,
|
||||
VariablePopupRecordProvider,
|
||||
css,
|
||||
getLabelFormatValue,
|
||||
useCollection,
|
||||
@ -21,14 +18,16 @@ import {
|
||||
useCollection_deprecated,
|
||||
useCompile,
|
||||
useFilterAPI,
|
||||
usePopupUtils,
|
||||
useProps,
|
||||
} from '@nocobase/client';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, Space } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { defaultImage, selectedImage } from '../../constants';
|
||||
import { useMapTranslation } from '../../locale';
|
||||
import { getSource } from '../../utils';
|
||||
import { MapBlockDrawer } from '../MapBlockDrawer';
|
||||
import { AMapComponent, AMapForwardedRefProps } from './Map';
|
||||
|
||||
export const AMapBlock = (props) => {
|
||||
@ -50,6 +49,9 @@ export const AMapBlock = (props) => {
|
||||
const selectingModeRef = useRef(selectingMode);
|
||||
selectingModeRef.current = selectingMode;
|
||||
const { fields } = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const { openPopup } = usePopupUtils();
|
||||
|
||||
const labelUiSchema = fields.find((v) => v.name === fieldNames?.marker)?.uiSchema;
|
||||
const setOverlayOptions = (overlay: AMap.Polygon | AMap.Marker, state?: boolean) => {
|
||||
const extData = overlay.getExtData();
|
||||
@ -199,6 +201,9 @@ export const AMapBlock = (props) => {
|
||||
|
||||
if (data) {
|
||||
setRecord(data);
|
||||
openPopup({
|
||||
recordData: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
o.on('click', onClick);
|
||||
@ -244,7 +249,17 @@ export const AMapBlock = (props) => {
|
||||
});
|
||||
events.forEach((e) => e());
|
||||
};
|
||||
}, [dataSource, isMapInitialization, fieldNames, name, primaryKey, collectionField.type, isConnected, lineSort]);
|
||||
}, [
|
||||
dataSource,
|
||||
isMapInitialization,
|
||||
fieldNames,
|
||||
name,
|
||||
primaryKey,
|
||||
collectionField.type,
|
||||
isConnected,
|
||||
lineSort,
|
||||
openPopup,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
@ -307,7 +322,9 @@ export const AMapBlock = (props) => {
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
<MapBlockDrawer record={record} setVisible={setRecord} />
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<MapBlockDrawer />
|
||||
</RecordProvider>
|
||||
<AMapComponent
|
||||
{...collectionField?.uiSchema?.['x-component-props']}
|
||||
ref={mapRefCallback}
|
||||
@ -324,35 +341,6 @@ export const AMapBlock = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MapBlockDrawer = (props) => {
|
||||
const { setVisible, record } = props;
|
||||
const collection = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const schema = useMemo(
|
||||
() =>
|
||||
fieldSchema.reduceProperties((buf, current) => {
|
||||
if (current.name === 'drawer') {
|
||||
return current;
|
||||
}
|
||||
return buf;
|
||||
}, null),
|
||||
[fieldSchema],
|
||||
);
|
||||
|
||||
return (
|
||||
schema && (
|
||||
<ActionContextProvider value={{ visible: !!record, setVisible }}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<VariablePopupRecordProvider recordData={record} collection={collection}>
|
||||
<RecursionField schema={schema} name={schema.name} />
|
||||
</VariablePopupRecordProvider>
|
||||
</RecordProvider>
|
||||
</ActionContextProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
function clearSelected(marker: AMap.Marker | AMap.Polygon | AMap.Polyline | AMap.Circle) {
|
||||
if ((marker as AMap.Marker).dom) {
|
||||
(marker as AMap.Marker).dom.style.filter = 'none';
|
||||
|
@ -8,11 +8,8 @@
|
||||
*/
|
||||
|
||||
import { CheckOutlined, EnvironmentOutlined, ExpandOutlined } from '@ant-design/icons';
|
||||
import { RecursionField, Schema, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
RecordProvider,
|
||||
VariablePopupRecordProvider,
|
||||
css,
|
||||
getLabelFormatValue,
|
||||
useCollection,
|
||||
@ -21,14 +18,16 @@ import {
|
||||
useCollection_deprecated,
|
||||
useCompile,
|
||||
useFilterAPI,
|
||||
usePopupUtils,
|
||||
useProps,
|
||||
} from '@nocobase/client';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Button, Space } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { defaultImage, selectedImage } from '../../constants';
|
||||
import { useMapTranslation } from '../../locale';
|
||||
import { getSource } from '../../utils';
|
||||
import { MapBlockDrawer } from '../MapBlockDrawer';
|
||||
import { GoogleMapForwardedRefProps, GoogleMapsComponent, OverlayOptions } from './Map';
|
||||
import { getIcon } from './utils';
|
||||
|
||||
@ -69,8 +68,10 @@ export const GoogleMapsBlock = (props) => {
|
||||
const overlaysRef = useRef<google.maps.MVCObject[]>([]);
|
||||
selectingModeRef.current = selectingMode;
|
||||
const { fields } = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const labelUiSchema = fields.find((v) => v.name === fieldNames?.marker)?.uiSchema;
|
||||
const { getCollectionJoinField } = useCollectionManager_deprecated();
|
||||
const { openPopup } = usePopupUtils();
|
||||
|
||||
const setOverlayOptions = (overlay: google.maps.MVCObject, state?: boolean) => {
|
||||
const selected = typeof state !== 'undefined' ? !state : overlay.get(OVERLAY_SELECtED);
|
||||
@ -234,6 +235,9 @@ export const GoogleMapsBlock = (props) => {
|
||||
|
||||
if (data) {
|
||||
setRecord(data);
|
||||
openPopup({
|
||||
recordData: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
o.addListener('click', onClick);
|
||||
@ -291,7 +295,7 @@ export const GoogleMapsBlock = (props) => {
|
||||
});
|
||||
events.forEach((e) => e());
|
||||
};
|
||||
}, [dataSource, isMapInitialization, markerName, collectionField.type, isConnected]);
|
||||
}, [dataSource, isMapInitialization, markerName, collectionField.type, isConnected, openPopup]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
@ -354,7 +358,9 @@ export const GoogleMapsBlock = (props) => {
|
||||
) : null}
|
||||
</Space>
|
||||
</div>
|
||||
<MapBlockDrawer record={record} setVisible={setRecord} />
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<MapBlockDrawer />
|
||||
</RecordProvider>
|
||||
</>
|
||||
)}
|
||||
<GoogleMapsComponent
|
||||
@ -373,35 +379,6 @@ export const GoogleMapsBlock = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MapBlockDrawer = (props) => {
|
||||
const { setVisible, record } = props;
|
||||
const collection = useCollection();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const schema: Schema = useMemo(
|
||||
() =>
|
||||
fieldSchema.reduceProperties((buf, current) => {
|
||||
if (current.name === 'drawer') {
|
||||
return current;
|
||||
}
|
||||
return buf;
|
||||
}, null),
|
||||
[fieldSchema],
|
||||
);
|
||||
|
||||
return (
|
||||
schema && (
|
||||
<ActionContextProvider value={{ visible: !!record, setVisible }}>
|
||||
<RecordProvider record={record} parent={parentRecordData}>
|
||||
<VariablePopupRecordProvider recordData={record} collection={collection}>
|
||||
<RecursionField schema={schema} name={schema.name} />
|
||||
</VariablePopupRecordProvider>
|
||||
</RecordProvider>
|
||||
</ActionContextProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
function clearSelected(target: google.maps.Polygon) {
|
||||
if (target instanceof google.maps.Marker) {
|
||||
return target.setIcon(getIcon(defaultImage));
|
||||
|
@ -7,6 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { PopupContextProvider } from '@nocobase/client';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMapTranslation } from '../locale';
|
||||
import { AMapBlock } from './AMap';
|
||||
@ -29,5 +30,9 @@ export const MapBlockComponent: React.FC<any> = (props) => {
|
||||
return <div>{t(`The ${mapType} cannot found`)}</div>;
|
||||
}
|
||||
|
||||
return <Component {...props} />;
|
||||
return (
|
||||
<PopupContextProvider>
|
||||
<Component {...props} />
|
||||
</PopupContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 { RecursionField, useFieldSchema } from '@formily/react';
|
||||
import { useCollection, useCollectionRecordData, VariablePopupRecordProvider } from '@nocobase/client';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
export const MapBlockDrawer: FC = (props) => {
|
||||
const recordData = useCollectionRecordData();
|
||||
const collection = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const schema = useMemo(
|
||||
() =>
|
||||
fieldSchema.reduceProperties((buf, current) => {
|
||||
if (current.name === 'drawer') {
|
||||
return current;
|
||||
}
|
||||
return buf;
|
||||
}, null),
|
||||
[fieldSchema],
|
||||
);
|
||||
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VariablePopupRecordProvider recordData={recordData} collection={collection}>
|
||||
<RecursionField schema={schema} name={schema.name} />
|
||||
</VariablePopupRecordProvider>
|
||||
);
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 { devices, expect, test } from '@nocobase/test/e2e';
|
||||
|
||||
test.use({
|
||||
...devices['Galaxy S9+'],
|
||||
});
|
||||
|
||||
test.describe('redirect to other page from mobile', () => {
|
||||
test('redirect to signin page', async ({ page }) => {
|
||||
const baseURL = process.env.APP_BASE_URL || `http://localhost:${process.env.APP_PORT || 20000}`;
|
||||
|
||||
await page.goto('/m/signin');
|
||||
await page.waitForURL(`${baseURL}/signin`);
|
||||
expect(page.url()).toBe(`${baseURL}/signin`);
|
||||
|
||||
// do not redirect to mobile page
|
||||
await page.waitForTimeout(5000);
|
||||
expect(page.url()).toBe(`${baseURL}/signin`);
|
||||
});
|
||||
|
||||
test('redirect to admin page', async ({ page }) => {
|
||||
const baseURL = process.env.APP_BASE_URL || `http://localhost:${process.env.APP_PORT || 20000}`;
|
||||
|
||||
await page.goto('/m/admin/settings/@nocobase/plugin-api-keys');
|
||||
await page.waitForURL(`${baseURL}/admin/settings/@nocobase/plugin-api-keys`);
|
||||
expect(page.url()).toBe(`${baseURL}/admin/settings/@nocobase/plugin-api-keys`);
|
||||
});
|
||||
});
|
@ -179,22 +179,26 @@ export class PluginMobileClient extends Plugin {
|
||||
Component: 'MobileHomePage',
|
||||
});
|
||||
|
||||
// 跳转到主应用的登录页
|
||||
// redirect to main app signin page
|
||||
// e.g. /m/signin => /signin
|
||||
this.mobileRouter.add('signin', {
|
||||
path: '/signin',
|
||||
Component: () => {
|
||||
window.location.href = window.location.href
|
||||
.replace(this.mobilePath, '')
|
||||
.replace(this.mobilePath + '/', '/')
|
||||
.replace('redirect=', `redirect=${this.mobilePath}`);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
// 跳转到主应用的页面
|
||||
// redirect to main app admin page
|
||||
// e.g. /m/admin/xxx => /admin/xxx
|
||||
this.mobileRouter.add('admin', {
|
||||
path: `/admin/*`,
|
||||
Component: () => {
|
||||
window.location.replace(window.location.href.replace(this.mobilePath, ''));
|
||||
if (window.location.pathname.startsWith(this.mobilePath + '/')) {
|
||||
window.location.replace(window.location.href.replace(this.mobilePath + '/', '/'));
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
@ -13,9 +13,9 @@ import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { message } from 'antd';
|
||||
|
||||
import { useActionContext, useRecord, useResourceActionContext, useResourceContext } from '@nocobase/client';
|
||||
import { useActionContext, useResourceActionContext, useResourceContext } from '@nocobase/client';
|
||||
|
||||
import { ExecutionStatusOptions } from '../constants';
|
||||
import { ExecutionStatusOptions, EXECUTION_STATUS } from '../constants';
|
||||
import { NAMESPACE } from '../locale';
|
||||
import { getWorkflowDetailPath } from '../utils';
|
||||
|
||||
@ -116,6 +116,19 @@ export const executionSchema = {
|
||||
icon: 'ReloadOutlined',
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
type: 'void',
|
||||
title: '{{t("Delete")}}',
|
||||
'x-component': 'Action',
|
||||
'x-component-props': {
|
||||
icon: 'DeleteOutlined',
|
||||
useAction: '{{ cm.useBulkDestroyAction }}',
|
||||
confirm: {
|
||||
title: "{{t('Delete record')}}",
|
||||
content: "{{t('Are you sure you want to delete it?')}}",
|
||||
},
|
||||
},
|
||||
},
|
||||
clear: {
|
||||
type: 'void',
|
||||
title: '{{t("Clear")}}',
|
||||
@ -148,6 +161,9 @@ export const executionSchema = {
|
||||
'x-component': 'Table.Void',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
useDataSource: '{{ cm.useDataSourceFromRAC }}',
|
||||
},
|
||||
properties: {
|
||||
@ -220,6 +236,28 @@ export const executionSchema = {
|
||||
type: 'void',
|
||||
'x-component': 'ExecutionLink',
|
||||
},
|
||||
delete: {
|
||||
type: 'void',
|
||||
title: '{{ t("Delete") }}',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
confirm: {
|
||||
title: "{{t('Delete record')}}",
|
||||
content: "{{t('Are you sure you want to delete it?')}}",
|
||||
},
|
||||
useAction: '{{ cm.useDestroyActionAndRefreshCM }}',
|
||||
},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['..status'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: `{{ $deps[0] !== ${EXECUTION_STATUS.STARTED} }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
39
yarn.lock
39
yarn.lock
@ -13231,9 +13231,9 @@ eslint-plugin-import@^2.27.5:
|
||||
tsconfig-paths "^3.15.0"
|
||||
|
||||
eslint-plugin-jest-dom@^5.0.1:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmmirror.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-5.1.0.tgz#b285cd1cc71c084c8ee897f0f85599758d7cb933"
|
||||
integrity sha512-JIXZp+E/h/aGlP/rQc4tuOejiHlZXg65qw8JAJMIJA5VsdjOkss/SYcRSqBrQuEOytEM8JvngUjcz31d1RrCrA==
|
||||
version "5.4.0"
|
||||
resolved "https://registry.npmmirror.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-5.4.0.tgz#03a5ea600f8af63f4fcd5de49ae83dc0e6aca325"
|
||||
integrity sha512-yBqvFsnpS5Sybjoq61cJiUsenRkC9K32hYQBFS9doBR7nbQZZ5FyO+X7MlmfM1C48Ejx/qTuOCgukDUNyzKZ7A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.16.3"
|
||||
requireindex "^1.2.0"
|
||||
@ -17016,7 +17016,7 @@ json5@^1.0.1, json5@^1.0.2:
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
json5@^2.1.2, json5@^2.2.3:
|
||||
json5@^2.1.2, json5@^2.2.2, json5@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
@ -24512,7 +24512,7 @@ string-convert@^0.2.0:
|
||||
resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
||||
integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -24530,6 +24530,15 @@ string-width@^1.0.1, string-width@^1.0.2:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
@ -24625,7 +24634,7 @@ stringify-entities@^4.0.0:
|
||||
character-entities-html4 "^2.0.0"
|
||||
character-entities-legacy "^3.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -24653,6 +24662,13 @@ strip-ansi@^5.1.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@ -26965,7 +26981,7 @@ wordwrap@^1.0.0:
|
||||
resolved "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -26991,6 +27007,15 @@ wrap-ansi@^6.0.1:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
Loading…
x
Reference in New Issue
Block a user