mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
e97197746b
File diff suppressed because it is too large
Load Diff
@ -126,6 +126,10 @@ export const getAllowMultiple = (params?: { title: string }) => {
|
||||
return {
|
||||
name: 'allowMultiple',
|
||||
type: 'switch',
|
||||
useVisible() {
|
||||
const isAssociationField = useIsAssociationField();
|
||||
return isAssociationField;
|
||||
},
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const field = useField<Field>();
|
||||
|
@ -14,6 +14,14 @@ import React, { useCallback, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { SchemaInitializerItem } from '../../application';
|
||||
import {
|
||||
CollectionManagerProvider,
|
||||
useCollectionManager,
|
||||
} from '../../data-source/collection/CollectionManagerProvider';
|
||||
import {
|
||||
DataSourceManagerProvider,
|
||||
useDataSourceManager,
|
||||
} from '../../data-source/data-source/DataSourceManagerProvider';
|
||||
import { useGlobalTheme } from '../../global-theme';
|
||||
import { NocoBaseDesktopRouteType } from '../../route-switch/antd/admin-layout/convertRoutesToSchema';
|
||||
import {
|
||||
@ -34,6 +42,8 @@ export const LinkMenuItem = () => {
|
||||
const { urlSchema, paramsSchema } = useURLAndHTMLSchema();
|
||||
const parentRoute = useParentRoute();
|
||||
const { createRoute } = useNocoBaseRoutes();
|
||||
const dm = useDataSourceManager();
|
||||
const cm = useCollectionManager();
|
||||
|
||||
const handleClick = useCallback(async () => {
|
||||
const values = await FormDialog(
|
||||
@ -41,31 +51,35 @@ export const LinkMenuItem = () => {
|
||||
() => {
|
||||
const history = createMemoryHistory();
|
||||
return (
|
||||
<Router location={history.location} navigator={history}>
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Menu item title'),
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-component': 'IconPicker',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
href: urlSchema,
|
||||
params: paramsSchema,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</Router>
|
||||
<DataSourceManagerProvider dataSourceManager={dm}>
|
||||
<CollectionManagerProvider instance={cm} dataSource={cm?.dataSource?.key}>
|
||||
<Router location={history.location} navigator={history}>
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Menu item title'),
|
||||
required: true,
|
||||
'x-component': 'Input',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
icon: {
|
||||
title: t('Icon'),
|
||||
'x-component': 'IconPicker',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
href: urlSchema,
|
||||
params: paramsSchema,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</Router>
|
||||
</CollectionManagerProvider>
|
||||
</DataSourceManagerProvider>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
|
@ -14,22 +14,22 @@ import { uid } from '@formily/shared';
|
||||
import { Space, message } from 'antd';
|
||||
import { isEqual } from 'lodash';
|
||||
import { isFunction } from 'mathjs';
|
||||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
ClearCollectionFieldContext,
|
||||
NocoBaseRecursionField,
|
||||
RecordProvider,
|
||||
SchemaComponentContext,
|
||||
useAPIClient,
|
||||
useCollectionRecordData,
|
||||
SchemaComponentContext,
|
||||
} from '../../../';
|
||||
import { Action } from '../action';
|
||||
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
import { isVariable } from '../../../variables/utils/isVariable';
|
||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
|
||||
import { Action } from '../action';
|
||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
|
||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
||||
import { VariablePopupRecordProvider } from '../../../modules/variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
export const AssociationFieldAddNewer = (props) => {
|
||||
const schemaComponentCtxValue = useContext(SchemaComponentContext);
|
||||
|
@ -53,6 +53,7 @@ export function useAssociationFieldContext<F extends GeneralField>() {
|
||||
};
|
||||
}
|
||||
|
||||
// 用于获取关系字段请求数据时所需的一些参数
|
||||
export default function useServiceOptions(props) {
|
||||
const { action = 'list', service, useOriginalFilter } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
|
@ -92,7 +92,7 @@ export const useGetFilterFieldOptions = () => {
|
||||
|
||||
const getOptions = (fields, depth, usedInVariable?: boolean) => {
|
||||
const options = [];
|
||||
fields.forEach((field) => {
|
||||
fields?.forEach((field) => {
|
||||
const option = field2option(field, depth, usedInVariable);
|
||||
if (option) {
|
||||
options.push(option);
|
||||
|
@ -1,5 +0,0 @@
|
||||
# Page
|
||||
|
||||
Can be used in conjunction with DocumentTitleProvider to display the page title on document.title.
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
@ -1,5 +0,0 @@
|
||||
# Page
|
||||
|
||||
可以与 DocumentTitleProvider 搭配使用,将 page title 显示在 document.title 上。
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
@ -18,7 +18,6 @@ import { getDataSourceHeaders } from '../data-source/utils';
|
||||
import { useCompile } from '../schema-component';
|
||||
import useBuiltInVariables from './hooks/useBuiltinVariables';
|
||||
import { VariableOption, VariablesContextType } from './types';
|
||||
import { cacheLazyLoadedValues, getCachedLazyLoadedValues } from './utils/cacheLazyLoadedValues';
|
||||
import { filterEmptyValues } from './utils/filterEmptyValues';
|
||||
import { getAction } from './utils/getAction';
|
||||
import { getPath } from './utils/getPath';
|
||||
@ -144,14 +143,13 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
.then((data) => {
|
||||
clearRequested(url);
|
||||
const value = data.data.data;
|
||||
cacheLazyLoadedValues(item, currentVariablePath, value);
|
||||
return value;
|
||||
});
|
||||
stashRequested(url, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return getCachedLazyLoadedValues(item, currentVariablePath) || item?.[key];
|
||||
return item?.[key];
|
||||
});
|
||||
current = removeThroughCollectionFields(_.flatten(await Promise.all(result)), associationField);
|
||||
} else if (
|
||||
@ -180,17 +178,9 @@ const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
}
|
||||
|
||||
const value = data.data.data;
|
||||
if (!getCachedLazyLoadedValues(current, currentVariablePath)) {
|
||||
// Cache the API response data to avoid repeated requests
|
||||
cacheLazyLoadedValues(current, currentVariablePath, value);
|
||||
}
|
||||
|
||||
current = removeThroughCollectionFields(value, associationField);
|
||||
} else {
|
||||
current = removeThroughCollectionFields(
|
||||
getCachedLazyLoadedValues(current, currentVariablePath) || getValuesByPath(current, key),
|
||||
associationField,
|
||||
);
|
||||
current = removeThroughCollectionFields(getValuesByPath(current, key), associationField);
|
||||
}
|
||||
|
||||
if (associationField?.target) {
|
||||
@ -359,13 +349,8 @@ export default VariablesProvider;
|
||||
function shouldToRequest(value, variableCtx: Record<string, any>, variablePath: string) {
|
||||
let result = false;
|
||||
|
||||
if (getCachedLazyLoadedValues(variableCtx, variablePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// value may be a reactive object, using untracked to avoid unexpected autorun
|
||||
untracked(() => {
|
||||
// fix https://nocobase.height.app/T-2502
|
||||
// Compatible with `xxx to many` and `xxx to one` subform fields and subtable fields
|
||||
if (JSON.stringify(value) === '[{}]' || JSON.stringify(value) === '{}') {
|
||||
result = true;
|
||||
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const cache = new Map<Record<string, any>, any>();
|
||||
|
||||
export const cacheLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string, value: any) => {
|
||||
const cachedValue = cache.get(variableCtx);
|
||||
|
||||
if (cachedValue) {
|
||||
cachedValue[variablePath] = value;
|
||||
} else {
|
||||
cache.set(variableCtx, { [variablePath]: value });
|
||||
}
|
||||
};
|
||||
|
||||
export const getCachedLazyLoadedValues = (variableCtx: Record<string, any>, variablePath: string) => {
|
||||
const cachedValue = cache.get(variableCtx);
|
||||
return cachedValue?.[variablePath];
|
||||
};
|
@ -67,10 +67,13 @@ export const useCustomizeRequestActionProps = () => {
|
||||
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
||||
});
|
||||
if (res.headers['content-disposition']) {
|
||||
const regex = /attachment;\s*filename="([^"]+)"/;
|
||||
const match = res.headers['content-disposition'].match(regex);
|
||||
if (match?.[1]) {
|
||||
saveAs(res.data, match[1]);
|
||||
const contentDisposition = res.headers['content-disposition'];
|
||||
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
|
||||
const asciiMatch = contentDisposition.match(/filename="([^"]+)"/i);
|
||||
if (utf8Match) {
|
||||
saveAs(res.data, decodeURIComponent(utf8Match[1]));
|
||||
} else if (asciiMatch) {
|
||||
saveAs(res.data, asciiMatch[1]);
|
||||
}
|
||||
}
|
||||
actionField.data.loading = false;
|
||||
|
@ -31,6 +31,8 @@ export default class extends Migration {
|
||||
await this.db.sequelize.transaction(async (transaction) => {
|
||||
const treeCollections = await this.getTreeCollections({ transaction });
|
||||
for (const treeCollection of treeCollections) {
|
||||
await treeCollection.load({ transaction });
|
||||
|
||||
const name = `main_${treeCollection.name}_path`;
|
||||
const collectionOptions = {
|
||||
name,
|
||||
@ -47,35 +49,16 @@ export default class extends Migration {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const collectionInstance = this.db.getCollection(treeCollection.name);
|
||||
const treeCollectionSchema = collectionInstance.collectionSchema();
|
||||
|
||||
if (this.app.db.inDialect('postgres') && treeCollectionSchema != this.app.db.options.schema) {
|
||||
collectionOptions['schema'] = treeCollectionSchema;
|
||||
}
|
||||
|
||||
this.app.db.collection(collectionOptions);
|
||||
|
||||
const treeExistsInDb = await this.app.db.getCollection(name).existsInDb({ transaction });
|
||||
|
||||
if (!treeExistsInDb) {
|
||||
await this.app.db.getCollection(name).sync({ transaction } as SyncOptions);
|
||||
const opts = {
|
||||
name: treeCollection.name,
|
||||
autoGenId: false,
|
||||
timestamps: false,
|
||||
fields: [
|
||||
{ type: 'integer', name: 'id' },
|
||||
{ type: 'integer', name: 'parentId' },
|
||||
],
|
||||
};
|
||||
|
||||
if (treeCollectionSchema != this.app.db.options.schema) {
|
||||
opts['schema'] = treeCollectionSchema;
|
||||
}
|
||||
|
||||
this.app.db.collection(opts);
|
||||
const chunkSize = 1000;
|
||||
await this.app.db.getRepository(treeCollection.name).chunk({
|
||||
chunkSize: chunkSize,
|
||||
|
Loading…
x
Reference in New Issue
Block a user