feat: improve code

This commit is contained in:
chenos 2022-03-02 18:35:49 +08:00
parent aa1c84e72a
commit afab15327e
22 changed files with 135 additions and 109 deletions

View File

@ -1,5 +1,5 @@
import { mockServer } from './index';
import { registerActions } from '@nocobase/actions'; import { registerActions } from '@nocobase/actions';
import { mockServer } from './index';
describe('list action', () => { describe('list action', () => {
let app; let app;
@ -112,7 +112,7 @@ describe('list action', () => {
const response = await app const response = await app
.agent() .agent()
.resource('posts.tags', 1) .resource('posts.tags', 1)
.list({ fields: ['id', 'posts_tags.createdAt'], sort: ['id'] }); .list({ fields: ['id', 'postsTags.createdAt'], sort: ['id'] });
const body = response.body; const body = response.body;
expect(body.count).toEqual(2); expect(body.count).toEqual(2);

View File

@ -101,13 +101,13 @@ export class APIClient {
} else { } else {
config['method'] = 'post'; config['method'] = 'post';
} }
return (params?: ActionParams) => { return async (params?: ActionParams) => {
const { values, ...others } = params || {}; const { values, ...others } = params || {};
config['params'] = others; config['params'] = others;
if (config.method !== 'get') { if (config.method !== 'get') {
config['data'] = values || {}; config['data'] = values || {};
} }
return this.request(config); return await this.request(config);
}; };
}, },
}; };

View File

@ -11,7 +11,7 @@ export const updatedAt: IField = {
sortable: true, sortable: true,
default: { default: {
type: 'date', type: 'date',
field: 'updated_at', field: 'updatedAt',
// name, // name,
uiSchema: { uiSchema: {
type: 'datetime', type: 'datetime',

View File

@ -34,7 +34,7 @@ const InternalAdminLayout = (props: any) => {
return ( return (
<Layout> <Layout>
<Layout.Header style={{ height: 46, lineHeight: '46px', position: 'relative', paddingLeft: 0 }}> <Layout.Header style={{ height: 46, lineHeight: '46px', position: 'relative', paddingLeft: 0 }}>
<div style={{ display: 'flex' }}> <div style={{ display: 'flex', height: '100%' }}>
<div style={{ width: 200, display: 'inline-flex', color: '#fff', padding: '0', alignItems: 'center' }}> <div style={{ width: 200, display: 'inline-flex', color: '#fff', padding: '0', alignItems: 'center' }}>
<img <img
className={css` className={css`

View File

@ -11,6 +11,7 @@ export const LinkToFieldInitializer = (props) => {
item.find, item.find,
item.remove, item.remove,
); );
const targetKey = item.field.targetKey || 'id';
return ( return (
<SchemaInitializer.Item <SchemaInitializer.Item
onClick={() => { onClick={() => {
@ -20,17 +21,17 @@ export const LinkToFieldInitializer = (props) => {
} }
insert({ insert({
...item.schema, ...item.schema,
default: [ // default: [
{ id: 1, name: 'name1' }, // { id: 1, name: 'name1' },
{ id: 2, name: 'name2' }, // { id: 2, name: 'name2' },
], // ],
// 'x-decorator': 'FormItem', // 'x-decorator': 'FormItem',
'x-component': 'CollectionField', 'x-component': 'CollectionField',
'x-component-props': { 'x-component-props': {
mode: 'tags', mode: 'tags',
fieldNames: { fieldNames: {
label: 'name', label: targetKey,
value: 'id', value: targetKey,
}, },
}, },
properties: { properties: {
@ -59,22 +60,22 @@ export const LinkToFieldInitializer = (props) => {
'x-designer': 'Table.Void.Designer', 'x-designer': 'Table.Void.Designer',
'x-component': 'CardItem', 'x-component': 'CardItem',
properties: { properties: {
actions: { // actions: {
type: 'void', // type: 'void',
'x-initializer': 'TableActionInitializers', // 'x-initializer': 'TableActionInitializers',
'x-component': 'ActionBar', // 'x-component': 'ActionBar',
'x-component-props': { // 'x-component-props': {
style: { // style: {
marginBottom: 16, // marginBottom: 16,
}, // },
}, // },
properties: {}, // properties: {},
}, // },
table: { table: {
// type: 'void', // type: 'void',
'x-component': 'Table.RowSelection', 'x-component': 'Table.RowSelection',
'x-component-props': { 'x-component-props': {
rowKey: 'id', rowKey: targetKey,
objectValue: true, objectValue: true,
rowSelection: { rowSelection: {
type: 'checkbox', type: 'checkbox',
@ -83,62 +84,31 @@ export const LinkToFieldInitializer = (props) => {
}, },
'x-initializer': 'TableColumnInitializers', 'x-initializer': 'TableColumnInitializers',
properties: { properties: {
actions: { // actions: {
type: 'void',
title: '{{ t("Actions") }}',
'x-decorator': 'Table.Column.ActionBar',
'x-component': 'Table.Column',
'x-designer': 'Table.RowActionDesigner',
'x-initializer': 'TableRecordActionInitializers',
properties: {
actions: {
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {},
},
},
},
},
},
},
},
// input: {
// type: 'array',
// title: `编辑模式`,
// 'x-component': 'Table.RowSelection',
// 'x-component-props': {
// rowKey: 'id',
// objectValue: true,
// rowSelection: {
// type: 'checkbox',
// },
// dataSource: [
// { id: 1, name: 'Name1' },
// { id: 2, name: 'Name2' },
// { id: 3, name: 'Name3' },
// ],
// },
// properties: {
// column1: {
// type: 'void', // type: 'void',
// title: 'Name', // title: '{{ t("Actions") }}',
// 'x-decorator': 'Table.Column.ActionBar',
// 'x-component': 'Table.Column', // 'x-component': 'Table.Column',
// 'x-designer': 'Table.RowActionDesigner',
// 'x-initializer': 'TableRecordActionInitializers',
// properties: { // properties: {
// name: { // actions: {
// type: 'string', // type: 'void',
// 'x-component': 'Input', // 'x-decorator': 'DndContext',
// 'x-read-pretty': true, // 'x-component': 'Space',
// }, // 'x-component-props': {
// }, // split: '|',
// },
// properties: {},
// }, // },
// }, // },
// }, // },
}, },
}, },
},
},
},
},
item: { item: {
'x-component': 'RecordPicker.SelectedItem', 'x-component': 'RecordPicker.SelectedItem',
properties: { properties: {

View File

@ -4,7 +4,7 @@ import { useField, useFieldSchema } from '@formily/react';
import { Space } from 'antd'; import { Space } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { DragHandler, useDesignable } from '../schema-component'; import { DragHandler, useCompile, useDesignable } from '../schema-component';
import { SchemaSettings } from './SchemaSettings'; import { SchemaSettings } from './SchemaSettings';
const titleCss = css` const titleCss = css`
@ -27,6 +27,7 @@ export const GeneralSchemaDesigner = (props: any) => {
const { dn, designable } = useDesignable(); const { dn, designable } = useDesignable();
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const compile = useCompile();
const schemaSettingsProps = { const schemaSettingsProps = {
dn, dn,
field, field,
@ -37,7 +38,7 @@ export const GeneralSchemaDesigner = (props: any) => {
} }
return ( return (
<div className={'general-schema-designer'}> <div className={'general-schema-designer'}>
{title && <div className={classNames('general-schema-designer-title', titleCss)}>{title}</div>} {title && <div className={classNames('general-schema-designer-title', titleCss)}>{compile(title)}</div>}
<div className={'general-schema-designer-icons'}> <div className={'general-schema-designer-icons'}>
<Space size={2} align={'center'}> <Space size={2} align={'center'}>
<DragHandler> <DragHandler>

View File

@ -91,15 +91,13 @@ const useSignup = () => {
return { return {
async run() { async run() {
await form.submit(); await form.submit();
const response = await api.resource('users').signup({ await api.resource('users').signup({
values: form.values, values: form.values,
}); });
if (response?.data) {
message.success('注册成功,即将跳转登录页'); message.success('注册成功,即将跳转登录页');
setTimeout(() => { setTimeout(() => {
history.push('/signin'); history.push('/signin');
}, 2000); }, 2000);
}
}, },
}; };
}; };

View File

@ -184,7 +184,7 @@ describe('collection sync', () => {
const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}posts`); const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}posts`);
expect(tableFields['user_id']).toBeUndefined(); expect(tableFields['userId']).toBeUndefined();
}); });
test('sync with association', async () => { test('sync with association', async () => {
@ -211,7 +211,7 @@ describe('collection sync', () => {
const model = collection.model; const model = collection.model;
await collection.sync(); await collection.sync();
const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}posts_tags`); const tableFields = await (<any>model).queryInterface.describeTable(`${db.getTablePrefix()}postsTags`);
expect(tableFields['postId']).toBeDefined(); expect(tableFields['postId']).toBeDefined();
expect(tableFields['tagId']).toBeDefined(); expect(tableFields['tagId']).toBeDefined();
}); });

View File

@ -1,5 +1,5 @@
import { Database } from '../../database';
import { mockDatabase } from '../'; import { mockDatabase } from '../';
import { Database } from '../../database';
describe('belongs to many field', () => { describe('belongs to many field', () => {
let db: Database; let db: Database;
@ -22,14 +22,14 @@ describe('belongs to many field', () => {
}); });
expect(Post.model.associations.tags).toBeUndefined(); expect(Post.model.associations.tags).toBeUndefined();
expect(db.getCollection('posts_tags')).toBeUndefined(); expect(db.getCollection('postsTags')).toBeUndefined();
const Tag = db.collection({ const Tag = db.collection({
name: 'tags', name: 'tags',
fields: [{ type: 'string', name: 'name' }], fields: [{ type: 'string', name: 'name' }],
}); });
expect(Post.model.associations.tags).toBeDefined(); expect(Post.model.associations.tags).toBeDefined();
const Through = db.getCollection('posts_tags'); const Through = db.getCollection('postsTags');
expect(Through).toBeDefined(); expect(Through).toBeDefined();
expect(Through.model.rawAttributes['postId']).toBeDefined(); expect(Through.model.rawAttributes['postId']).toBeDefined();
@ -46,14 +46,14 @@ describe('belongs to many field', () => {
}); });
expect(Post.model.associations.tags).toBeUndefined(); expect(Post.model.associations.tags).toBeUndefined();
expect(db.getCollection('posts_tags')).toBeUndefined(); expect(db.getCollection('postsTags')).toBeUndefined();
const Tag = db.collection({ const Tag = db.collection({
name: 'tags', name: 'tags',
fields: [{ type: 'string', name: 'name' }], fields: [{ type: 'string', name: 'name' }],
}); });
const PostTag = db.collection({ name: 'posts_tags' }); const PostTag = db.collection({ name: 'postsTags' });
expect(PostTag.model.rawAttributes['postId']).toBeDefined(); expect(PostTag.model.rawAttributes['postId']).toBeDefined();
expect(PostTag.model.rawAttributes['tagId']).toBeDefined(); expect(PostTag.model.rawAttributes['tagId']).toBeDefined();

View File

@ -159,7 +159,7 @@ export class Collection<
collection: this, collection: this,
}, },
); );
this.removeField(name);
this.fields.set(name, field); this.fields.set(name, field);
this.emit('field.afterAdd', field); this.emit('field.afterAdd', field);
return field; return field;
@ -187,6 +187,9 @@ export class Collection<
} }
removeField(name) { removeField(name) {
if (!this.fields.has(name)) {
return;
}
const field = this.fields.get(name); const field = this.fields.get(name);
const bool = this.fields.delete(name); const bool = this.fields.delete(name);
if (bool) { if (bool) {

View File

@ -158,10 +158,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
getRepository<R extends RelationRepository>(name: string, relationId?: string | number): Repository | R { getRepository<R extends RelationRepository>(name: string, relationId?: string | number): Repository | R {
if (relationId) { if (relationId) {
const [collection, relation] = name.split('.'); const [collection, relation] = name.split('.');
return this.getRepository(collection).relation(relation).of(relationId) as R; return this.getRepository(collection)?.relation(relation)?.of(relationId) as R;
} }
return this.getCollection(name).repository; return this.getCollection(name)?.repository;
} }
addPendingField(field: RelationField) { addPendingField(field: RelationField) {

View File

@ -1,16 +1,18 @@
import { omit } from 'lodash'; import { omit } from 'lodash';
import { BelongsToManyOptions as SequelizeBelongsToManyOptions } from 'sequelize'; import { BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
import { Collection } from '../collection'; import { Collection } from '../collection';
import { BaseRelationFieldOptions, MultipleRelationFieldOptions, RelationField } from './relation-field'; import { MultipleRelationFieldOptions, RelationField } from './relation-field';
export class BelongsToManyField extends RelationField { export class BelongsToManyField extends RelationField {
get through() { get through() {
return ( return (
this.options.through || this.options.through ||
Utils.camelize(
[this.context.collection.model.name, this.target] [this.context.collection.model.name, this.target]
.map((name) => name.toLowerCase()) .map((name) => name.toLowerCase())
.sort() .sort()
.join('_') .join('_'),
)
); );
} }
@ -49,6 +51,12 @@ export class BelongsToManyField extends RelationField {
if (!this.options.sourceKey) { if (!this.options.sourceKey) {
this.options.sourceKey = association.sourceKey; this.options.sourceKey = association.sourceKey;
} }
if (!this.options.otherKey) {
this.options.otherKey = association.otherKey;
}
if (!this.options.through) {
this.options.through = this.through;
}
return true; return true;
} }

View File

@ -1,6 +1,6 @@
import { mockServer } from '@nocobase/test';
import PluginUiSchema from '@nocobase/plugin-ui-schema-storage';
import PluginCollectionManager from '@nocobase/plugin-collection-manager'; import PluginCollectionManager from '@nocobase/plugin-collection-manager';
import PluginUiSchema from '@nocobase/plugin-ui-schema-storage';
import { mockServer } from '@nocobase/test';
import PluginACL from '../server'; import PluginACL from '../server';
let mockRole: string = 'admin'; let mockRole: string = 'admin';
@ -33,5 +33,7 @@ export async function prepareApp() {
app.plugin(PluginACL); app.plugin(PluginACL);
await app.loadAndInstall(); await app.loadAndInstall();
await app.db.sync();
return app; return app;
} }

View File

@ -1,7 +1,7 @@
import { MockServer } from '@nocobase/test';
import { Database, Model } from '@nocobase/database'; import { Database, Model } from '@nocobase/database';
import { prepareApp } from './prepare';
import { CollectionRepository } from '@nocobase/plugin-collection-manager'; import { CollectionRepository } from '@nocobase/plugin-collection-manager';
import { MockServer } from '@nocobase/test';
import { prepareApp } from './prepare';
describe('role resource api', () => { describe('role resource api', () => {
let app: MockServer; let app: MockServer;
@ -54,6 +54,9 @@ describe('role resource api', () => {
.agent() .agent()
.resource('roles.collections', 'admin') .resource('roles.collections', 'admin')
.list({ .list({
filter: {
$or: [{ name: 'c1' }, { name: 'c2' }],
},
sort: ['sort'], sort: ['sort'],
}); });

View File

@ -12,7 +12,9 @@ const roleCollectionsResource = {
const collectionRepository = db.getRepository('collections'); const collectionRepository = db.getRepository('collections');
// all collections // all collections
const collections = await collectionRepository.find(); const collections = await collectionRepository.find({
filter: ctx.action.params.filter,
});
// role collections // role collections
const roleResources = await db.getRepository('rolesResources').find({ const roleResources = await db.getRepository('rolesResources').find({

View File

@ -2,6 +2,7 @@ import { CollectionOptions } from '@nocobase/database';
export default { export default {
name: 'roles', name: 'roles',
title: '{{t("Roles")}}',
autoGenId: false, autoGenId: false,
model: 'RoleModel', model: 'RoleModel',
fields: [ fields: [
@ -10,11 +11,21 @@ export default {
name: 'name', name: 'name',
prefix: 'r_', prefix: 'r_',
primaryKey: true, primaryKey: true,
uiSchema: {
type: 'string',
title: '{{t("Role UID")}}',
'x-component': 'Input',
},
}, },
{ {
type: 'string', type: 'string',
name: 'title', name: 'title',
unique: true, unique: true,
uiSchema: {
type: 'string',
title: '{{t("Role name")}}',
'x-component': 'Input',
},
}, },
{ {
type: 'boolean', type: 'boolean',
@ -45,6 +56,7 @@ export default {
type: 'belongsToMany', type: 'belongsToMany',
name: 'menuUiSchemas', name: 'menuUiSchemas',
target: 'uiSchemas', target: 'uiSchemas',
targetKey: 'x-uid',
}, },
{ {
type: 'hasMany', type: 'hasMany',

View File

@ -259,6 +259,13 @@ export class PluginACL extends Plugin {
}); });
} }
async install() {
const repo = this.db.getRepository<any>('collections');
if (repo) {
await repo.db2cm('roles');
}
}
async load() { async load() {
await this.app.db.import({ await this.app.db.import({
directory: resolve(__dirname, 'collections'), directory: resolve(__dirname, 'collections'),

View File

@ -14,4 +14,22 @@ export class CollectionRepository extends Repository {
await instance.load({ skipExist }); await instance.load({ skipExist });
} }
} }
async db2cm(collectionName: string) {
const collection = this.database.getCollection(collectionName);
const options = collection.options;
const fields = [];
for (const [name, field] of collection.fields) {
fields.push({
name,
...field.options,
});
}
await this.create({
values: {
...options,
fields,
},
});
}
} }

View File

@ -6,7 +6,7 @@ export default {
sortable: 'sort', sortable: 'sort',
fields: [ fields: [
{ {
interface: 'string', interface: 'input',
type: 'string', type: 'string',
name: 'nickname', name: 'nickname',
uiSchema: { uiSchema: {
@ -47,10 +47,11 @@ export default {
otherKey: 'roleName', otherKey: 'roleName',
sourceKey: 'id', sourceKey: 'id',
targetKey: 'name', targetKey: 'name',
through: 'rolesUsers',
uiSchema: { uiSchema: {
type: 'array', type: 'array',
title: '{{t("Roles")}}', title: '{{t("Roles")}}',
'x-component': 'Select.Drawer', 'x-component': 'RecordPicker',
'x-component-props': { 'x-component-props': {
multiple: true, multiple: true,
fieldNames: { fieldNames: {
@ -58,12 +59,9 @@ export default {
value: 'name', value: 'name',
}, },
}, },
'x-decorator': 'FormItem',
'x-designable-bar': 'Select.Drawer.DesignableBar',
}, },
}, },
{ {
interface: 'select',
type: 'string', type: 'string',
name: 'appLang', name: 'appLang',
}, },

View File

@ -83,5 +83,9 @@ export default class UsersPlugin extends Plugin {
roles: ['admin'], roles: ['admin'],
}, },
}); });
const repo = this.db.getRepository<any>('collections');
if (repo) {
await repo.db2cm('users');
}
} }
} }