mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:25:15 +00:00
Merge branch 'next' into feat/subquery-field
This commit is contained in:
commit
29ab715daa
@ -18,9 +18,16 @@ function writeToExclude() {
|
||||
const excludePath = resolve(process.cwd(), '.git', 'info', 'exclude');
|
||||
const content = 'packages/pro-plugins/\n';
|
||||
const dirPath = dirname(excludePath);
|
||||
|
||||
if (!existsSync(dirPath)) {
|
||||
mkdirSync(dirPath, { recursive: true });
|
||||
try {
|
||||
mkdirSync(dirPath, { recursive: true });
|
||||
} catch (e) {
|
||||
console.log(`${e.message}, ignore write to git exclude`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let fileContent = '';
|
||||
if (existsSync(excludePath)) {
|
||||
fileContent = readFileSync(excludePath, 'utf-8');
|
||||
|
@ -83,7 +83,7 @@ export const SchemaSettingsChildren: FC<SchemaSettingsChildrenProps> = (props) =
|
||||
// 此时如果使用 item.name 作为 key,会导致 React 认为其前后是同一个组件;因为 SchemaSettingsChild 的某些 hooks 是通过 props 传入的,
|
||||
// 两次渲染之间 props 可能发生变化,就可能报 hooks 调用顺序的错误。所以这里使用 fieldComponentName 和 item.name 拼成
|
||||
// 一个不会重复的 key,保证每次渲染都是新的组件。
|
||||
const key = `${fieldComponentName ? fieldComponentName + '-' : ''}${item.name}`;
|
||||
const key = `${fieldComponentName ? fieldComponentName + '-' : ''}${item?.name}`;
|
||||
return (
|
||||
<ErrorBoundary
|
||||
key={key}
|
||||
|
@ -323,6 +323,37 @@ export const linkageRules = {
|
||||
},
|
||||
};
|
||||
|
||||
export const recordPerPage = {
|
||||
name: 'recordsPerPage',
|
||||
type: 'select',
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
const { dn } = useDesignable();
|
||||
const pageSizeOptions = [10, 20, 50, 100];
|
||||
|
||||
return {
|
||||
title: t('Records per page'),
|
||||
value: field.componentProps?.pageSize || 10,
|
||||
options: pageSizeOptions.map((v) => ({ value: v })),
|
||||
onChange: (pageSize) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
field.componentProps = field.componentProps || {};
|
||||
field.componentProps.pageSize = pageSize;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].pageSize = pageSize;
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const subTablePopoverComponentFieldSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:SubTable',
|
||||
items: [
|
||||
@ -332,5 +363,6 @@ export const subTablePopoverComponentFieldSettings = new SchemaSettings({
|
||||
allowDisassociation,
|
||||
setDefaultSortingRules,
|
||||
linkageRules,
|
||||
recordPerPage,
|
||||
],
|
||||
});
|
||||
|
@ -63,6 +63,21 @@ export const InternalSubTable = observer(
|
||||
max-height: 100% !important;
|
||||
min-height: 100% !important;
|
||||
}
|
||||
|
||||
// configure columns
|
||||
.ant-table-thead
|
||||
button[aria-label*='schema-initializer-AssociationField.SubTable-table:configureColumns']
|
||||
> span:last-child {
|
||||
display: none !important;
|
||||
}
|
||||
.ant-table-thead
|
||||
button[aria-label*='schema-initializer-AssociationField.SubTable-table:configureColumns']
|
||||
> .ant-btn-icon {
|
||||
margin: 0px;
|
||||
}
|
||||
.ant-table-tbody .nb-column-initializer {
|
||||
min-width: 40px !important;
|
||||
}
|
||||
`}
|
||||
layout={'vertical'}
|
||||
bordered={false}
|
||||
|
@ -14,7 +14,7 @@ import { observer, RecursionField, useFieldSchema } from '@formily/react';
|
||||
import { action } from '@formily/reactive';
|
||||
import { isArr } from '@formily/shared';
|
||||
import { Button } from 'antd';
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FormProvider,
|
||||
@ -157,6 +157,8 @@ export const SubTable: any = observer(
|
||||
field.initialValue = field.value;
|
||||
setSelectedRows([]);
|
||||
setVisible(false);
|
||||
const totalPages = Math.ceil(field.value.length / (field.componentProps?.pageSize || 10));
|
||||
setCurrentPage(totalPages);
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -166,6 +168,28 @@ export const SubTable: any = observer(
|
||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||
return filter;
|
||||
};
|
||||
//分页
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(field.componentProps?.pageSize || 10); // 每页条数
|
||||
useEffect(() => {
|
||||
setPageSize(field.componentProps?.pageSize);
|
||||
}, [field.componentProps?.pageSize]);
|
||||
|
||||
const paginationConfig = useMemo(() => {
|
||||
return {
|
||||
current: currentPage,
|
||||
pageSize: pageSize || 10,
|
||||
total: field?.value?.length,
|
||||
onChange: (page, pageSize) => {
|
||||
setCurrentPage(page);
|
||||
setPageSize(pageSize);
|
||||
field.onInput(field.value);
|
||||
},
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
hideOnSinglePage: false,
|
||||
};
|
||||
}, [field.value?.length, pageSize, currentPage]);
|
||||
return (
|
||||
<div className={subTableContainer}>
|
||||
<FlagProvider isInSubTable>
|
||||
@ -193,7 +217,7 @@ export const SubTable: any = observer(
|
||||
}
|
||||
: false
|
||||
}
|
||||
pagination={false}
|
||||
pagination={paginationConfig}
|
||||
rowSelection={{ type: 'none', hideSelectAll: true }}
|
||||
footer={() =>
|
||||
field.editable && (
|
||||
@ -206,6 +230,9 @@ export const SubTable: any = observer(
|
||||
onClick={() => {
|
||||
field.value = field.value || [];
|
||||
field.value.push(markRecordAsNew({}));
|
||||
// 计算总页数,并跳转到最后一页
|
||||
const totalPages = Math.ceil(field.value.length / (field.componentProps?.pageSize || 10));
|
||||
setCurrentPage(totalPages);
|
||||
}}
|
||||
>
|
||||
{t('Add new')}
|
||||
|
@ -549,7 +549,7 @@ Grid.Col = observer(
|
||||
</GridColContext.Provider>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Grid.Row' },
|
||||
{ displayName: 'Grid.Col' },
|
||||
);
|
||||
|
||||
Grid.wrap = (schema: ISchema) => {
|
||||
|
@ -22,6 +22,7 @@ export abstract class DataSource extends EventEmitter {
|
||||
public resourceManager: ResourceManager;
|
||||
public acl: ACL;
|
||||
logger: Logger;
|
||||
_sqlLogger: Logger;
|
||||
|
||||
constructor(protected options: DataSourceOptions) {
|
||||
super();
|
||||
@ -32,6 +33,14 @@ export abstract class DataSource extends EventEmitter {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
setSqlLogger(logger: Logger) {
|
||||
this._sqlLogger = logger;
|
||||
}
|
||||
|
||||
get sqlLogger() {
|
||||
return this._sqlLogger || this.logger;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.options.name;
|
||||
}
|
||||
|
@ -225,7 +225,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
private _maintainingCommandStatus: MaintainingCommandStatus;
|
||||
private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null;
|
||||
private _actionCommand: Command;
|
||||
private sqlLogger: Logger;
|
||||
|
||||
constructor(public options: ApplicationOptions) {
|
||||
super();
|
||||
@ -238,6 +237,18 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
}
|
||||
}
|
||||
|
||||
private _sqlLogger: Logger;
|
||||
|
||||
get sqlLogger() {
|
||||
return this._sqlLogger;
|
||||
}
|
||||
|
||||
protected _logger: SystemLogger;
|
||||
|
||||
get logger() {
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
protected _started: Date | null = null;
|
||||
|
||||
/**
|
||||
@ -247,12 +258,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
return this._started;
|
||||
}
|
||||
|
||||
protected _logger: SystemLogger;
|
||||
|
||||
get logger() {
|
||||
return this._logger;
|
||||
}
|
||||
|
||||
get log() {
|
||||
return this._logger;
|
||||
}
|
||||
@ -1084,12 +1089,14 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
// Due to the use of custom log levels,
|
||||
// we have to use any type here until Winston updates the type definitions.
|
||||
}) as any;
|
||||
|
||||
this.requestLogger = createLogger({
|
||||
dirname: getLoggerFilePath(this.name),
|
||||
filename: 'request',
|
||||
...(options?.request || {}),
|
||||
});
|
||||
this.sqlLogger = this.createLogger({
|
||||
|
||||
this._sqlLogger = this.createLogger({
|
||||
filename: 'sql',
|
||||
level: 'debug',
|
||||
});
|
||||
@ -1098,7 +1105,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
protected closeLogger() {
|
||||
this.log?.close();
|
||||
this.requestLogger?.close();
|
||||
this.sqlLogger?.close();
|
||||
this._sqlLogger?.close();
|
||||
}
|
||||
|
||||
protected init() {
|
||||
@ -1210,7 +1217,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
||||
if (msg.includes('INSERT INTO')) {
|
||||
msg = msg.substring(0, 2000) + '...';
|
||||
}
|
||||
this.sqlLogger.debug({ message: msg, app: this.name, reqId: this.context.reqId });
|
||||
this._sqlLogger.debug({ message: msg, app: this.name, reqId: this.context.reqId });
|
||||
};
|
||||
const dbOptions = options.database instanceof Database ? options.database.options : options.database;
|
||||
const db = new Database({
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
useProps,
|
||||
useToken,
|
||||
withDynamicSchemaProps,
|
||||
useACLRoleContext,
|
||||
} from '@nocobase/client';
|
||||
import { parseExpression } from 'cron-parser';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
@ -214,6 +215,18 @@ const useEvents = (
|
||||
]);
|
||||
};
|
||||
|
||||
function findCreateSchema(schema): Schema {
|
||||
return schema.reduceProperties((buf, current) => {
|
||||
if (current['x-component'].endsWith('Action') && current['x-action'] === 'create') {
|
||||
return current;
|
||||
}
|
||||
if (current['x-component'].endsWith('.ActionBar')) {
|
||||
return findCreateSchema(current);
|
||||
}
|
||||
return buf;
|
||||
}, null);
|
||||
}
|
||||
|
||||
export const Calendar: any = withDynamicSchemaProps(
|
||||
observer(
|
||||
(props: any) => {
|
||||
@ -233,6 +246,13 @@ export const Calendar: any = withDynamicSchemaProps(
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { token } = useToken();
|
||||
//nint deal with slot select to show create popup
|
||||
const { parseAction } = useACLRoleContext();
|
||||
const collection = useCollection();
|
||||
const canCreate = parseAction(`${collection.name}:create`);
|
||||
const createActionSchema: Schema = useMemo(() => findCreateSchema(fieldSchema), [fieldSchema]);
|
||||
const startFieldName = fieldNames?.start?.[0];
|
||||
const endFieldName = fieldNames?.end?.[0];
|
||||
|
||||
const components = useMemo(() => {
|
||||
return {
|
||||
@ -302,7 +322,16 @@ export const Calendar: any = withDynamicSchemaProps(
|
||||
onNavigate={setDate}
|
||||
onView={setView}
|
||||
onSelectSlot={(slotInfo) => {
|
||||
console.log('onSelectSlot', slotInfo);
|
||||
//nint show create popup
|
||||
if (canCreate && createActionSchema) {
|
||||
const record = {};
|
||||
record[startFieldName] = slotInfo.start;
|
||||
record[endFieldName] = slotInfo.end;
|
||||
openPopup({
|
||||
recordData: record,
|
||||
customActionSchema: createActionSchema,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onDoubleClickEvent={() => {
|
||||
console.log('onDoubleClickEvent');
|
||||
|
@ -96,6 +96,7 @@ export class DataSourceModel extends Model {
|
||||
...createOptions,
|
||||
name: dataSourceKey,
|
||||
logger: app.logger.child({ dataSourceKey }),
|
||||
sqlLogger: app.sqlLogger.child({ dataSourceKey }),
|
||||
});
|
||||
|
||||
if (loadAtAfterStart) {
|
||||
|
@ -61,7 +61,7 @@ const defaultSubAppUpgradeHandle: SubAppUpgradeHandler = async (mainApp: Applica
|
||||
|
||||
const defaultDbCreator = async (app: Application) => {
|
||||
const databaseOptions = app.options.database as any;
|
||||
const { host, port, username, password, dialect, database } = databaseOptions;
|
||||
const { host, port, username, password, dialect, database, schema } = databaseOptions;
|
||||
|
||||
if (dialect === 'mysql') {
|
||||
const mysql = require('mysql2/promise');
|
||||
@ -91,7 +91,11 @@ const defaultDbCreator = async (app: Application) => {
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.query(`CREATE DATABASE "${database}"`);
|
||||
if (process.env.USE_DB_SCHEMA_IN_SUBAPP === 'true') {
|
||||
await client.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
||||
} else {
|
||||
await client.query(`CREATE DATABASE "${database}"`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@ -109,6 +113,8 @@ const defaultAppOptionsFactory = (appName: string, mainApp: Application) => {
|
||||
const mainStorageDir = path.dirname(mainAppStorage);
|
||||
rawDatabaseOptions.storage = path.join(mainStorageDir, `${appName}.sqlite`);
|
||||
}
|
||||
} else if (process.env.USE_DB_SCHEMA_IN_SUBAPP === 'true' && rawDatabaseOptions.dialect === 'postgres') {
|
||||
rawDatabaseOptions.schema = appName;
|
||||
} else {
|
||||
rawDatabaseOptions.database = appName;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user