Merge branch 'next' into feat/subquery-field

This commit is contained in:
Chareice 2024-10-22 17:26:05 +08:00
commit 29ab715daa
No known key found for this signature in database
11 changed files with 151 additions and 18 deletions

View File

@ -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');

View File

@ -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}

View File

@ -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,
],
});

View File

@ -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}

View File

@ -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')}

View File

@ -549,7 +549,7 @@ Grid.Col = observer(
</GridColContext.Provider>
);
},
{ displayName: 'Grid.Row' },
{ displayName: 'Grid.Col' },
);
Grid.wrap = (schema: ISchema) => {

View File

@ -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;
}

View File

@ -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({

View File

@ -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');

View File

@ -96,6 +96,7 @@ export class DataSourceModel extends Model {
...createOptions,
name: dataSourceKey,
logger: app.logger.child({ dataSourceKey }),
sqlLogger: app.sqlLogger.child({ dataSourceKey }),
});
if (loadAtAfterStart) {

View File

@ -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;
}