From 3e3cb416b6257f0b298b7130df0d78dc9df0bdf8 Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 4 Dec 2020 21:09:39 +0800 Subject: [PATCH] feat: improve collection hooks/fields/actions/views... (#30) * feat: add onFinish callback * fix: update json attribute unsaved after query * refactor: collection hooks * feat: add migrate options --- packages/app/src/api/index.ts | 2 +- .../app/src/components/actions/Create.tsx | 6 +- .../app/src/components/actions/Update.tsx | 1 + packages/app/src/components/views/Details.tsx | 6 +- .../src/components/views/Form/DrawerForm.tsx | 24 ++++++- .../app/src/components/views/SimpleTable.tsx | 10 ++- packages/app/src/components/views/Table.tsx | 9 ++- packages/app/src/components/views/index.tsx | 2 + .../src/__tests__/base-model.test.ts | 67 +++++++++++++++++++ .../src/__tests__/collections.test.ts | 2 +- .../src/collections/fields.ts | 2 +- .../src/hooks/collections-after-create.ts | 7 +- .../src/hooks/collections-before-validate.ts | 4 +- .../src/hooks/fields-after-create.ts | 8 ++- .../src/hooks/fields-before-validate.ts | 26 ++----- .../src/interfaces/types.ts | 1 + .../plugin-collections/src/models/base.ts | 20 ++++-- .../src/models/collection.ts | 50 ++++++++------ .../plugin-collections/src/models/field.ts | 24 ++++++- 19 files changed, 198 insertions(+), 73 deletions(-) diff --git a/packages/app/src/api/index.ts b/packages/app/src/api/index.ts index 64d506e9d..d59d49b50 100644 --- a/packages/app/src/api/index.ts +++ b/packages/app/src/api/index.ts @@ -166,7 +166,7 @@ const data = { const tables = database.getTables([]); for (let table of tables) { - await Collection.import(table.getOptions(), { hooks: false }); + await Collection.import(table.getOptions(), { migrate: false }); } const Page = database.getModel('pages'); diff --git a/packages/app/src/components/actions/Create.tsx b/packages/app/src/components/actions/Create.tsx index 1551b2e72..1aecd56e9 100644 --- a/packages/app/src/components/actions/Create.tsx +++ b/packages/app/src/components/actions/Create.tsx @@ -5,15 +5,15 @@ import ViewFactory from '@/components/views'; export function Create(props) { console.log(props); const { title, viewName, collection_name } = props.schema; - const { activeTab = {}, item = {} } = props; + const { activeTab = {}, item = {}, associatedName, associatedKey } = props; const { association } = activeTab; const params = {}; if (association) { params['resourceName'] = association; - params['associatedName'] = activeTab.collection_name; - params['associatedKey'] = item.itemId; + params['associatedName'] = associatedName; + params['associatedKey'] = associatedKey; } else { params['resourceName'] = collection_name; params['resourceKey'] = item.itemId; diff --git a/packages/app/src/components/actions/Update.tsx b/packages/app/src/components/actions/Update.tsx index 32a8ce8a9..55d4c82a3 100644 --- a/packages/app/src/components/actions/Update.tsx +++ b/packages/app/src/components/actions/Update.tsx @@ -26,6 +26,7 @@ export function Update(props) { {...props} reference={drawerRef} viewName={viewName} + mode={'update'} {...params} /> ]} > diff --git a/packages/app/src/components/views/SimpleTable.tsx b/packages/app/src/components/views/SimpleTable.tsx index ce5653984..6bdd5417e 100644 --- a/packages/app/src/components/views/SimpleTable.tsx +++ b/packages/app/src/components/views/SimpleTable.tsx @@ -64,9 +64,17 @@ export function SimpleTable(props: SimpleTableProps) { console.log('rowViewName', {rowViewName}) return ( - + { + refresh(); + }} + /> diff --git a/packages/app/src/components/views/Table.tsx b/packages/app/src/components/views/Table.tsx index 2711a28e4..cf3ae1121 100644 --- a/packages/app/src/components/views/Table.tsx +++ b/packages/app/src/components/views/Table.tsx @@ -80,7 +80,14 @@ export function Table(props: TableProps) { } return ( - + { + refresh(); + }} + /> { const params = { resourceKey: viewName, associatedName: associatedName, + mode, }; return api.resource(resourceName).getView(params); }, { diff --git a/packages/plugin-collections/src/__tests__/base-model.test.ts b/packages/plugin-collections/src/__tests__/base-model.test.ts index c26f901a6..abb7662ee 100644 --- a/packages/plugin-collections/src/__tests__/base-model.test.ts +++ b/packages/plugin-collections/src/__tests__/base-model.test.ts @@ -1,6 +1,7 @@ import Database, { ModelCtor } from '@nocobase/database'; import { getDatabase } from '.'; import BaseModel from '../models/base'; +import _ from 'lodash'; describe('base model', () => { let database: Database; @@ -21,6 +22,11 @@ describe('base model', () => { name: 'title', type: 'virtual', }, + { + name: 'xyz', + type: 'virtual', + defaultValue: 'xyz1', + }, { name: 'content', type: 'virtual', @@ -92,6 +98,7 @@ describe('base model', () => { bb: 'bb', }, bcd: 'bbb', + xyz: "xyz1", arr: [{a: 'a'}, {b: 'b'}], }); }); @@ -189,4 +196,64 @@ describe('base model', () => { }); expect(test2.get('content')).toBeUndefined(); }); + + it('update', async () => { + const t = await TestModel.create({ + name: 'name1', + // xyz: 'xyz', + }); + await t.update({ + abc: 'abc', + }); + const t2 = await TestModel.findOne({ + where: { + name: 'name1', + } + }); + expect(t2.get()).toMatchObject({ + xyz: 'xyz1', + abc: 'abc', + key2: 'val2', + id: 2, + name: 'name1', + }); + await t2.update({ + abc: 'abcdef', + }); + const t3 = await TestModel.findOne({ + where: { + name: 'name1', + } + }); + // 查询之后更新再重新查询 + expect(t3.get()).toMatchObject({ + xyz: 'xyz1', + abc: 'abcdef', + key2: 'val2', + id: 2, + name: 'name1', + }); + }); + + it('update', async () => { + const t = await TestModel.create({ + name: 'name1', + xyz: 'xyz', + }); + await t.update({ + abc: 'abc', + }); + const t2 = await TestModel.findOne({ + where: { + name: 'name1', + } + }); + expect(t2.get()).toMatchObject({ + xyz: 'xyz', + abc: 'abc', + key2: 'val2', + id: 2, + name: 'name1', + }); + }); }); diff --git a/packages/plugin-collections/src/__tests__/collections.test.ts b/packages/plugin-collections/src/__tests__/collections.test.ts index 128a5f202..564077be9 100644 --- a/packages/plugin-collections/src/__tests__/collections.test.ts +++ b/packages/plugin-collections/src/__tests__/collections.test.ts @@ -17,7 +17,7 @@ describe('collection hooks', () => { const tables = app.database.getTables([]); for (const table of tables) { const Collection = app.database.getModel('collections'); - await Collection.import(table.getOptions(), { hooks: false }); + await Collection.import(table.getOptions(), { migrate: false }); } }); diff --git a/packages/plugin-collections/src/collections/fields.ts b/packages/plugin-collections/src/collections/fields.ts index c85318f5b..2409dfe20 100644 --- a/packages/plugin-collections/src/collections/fields.ts +++ b/packages/plugin-collections/src/collections/fields.ts @@ -139,7 +139,7 @@ export default { name: 'component.tooltip', title: '提示信息', component: { - type: 'string', + type: 'textarea', showInDetail: true, showInForm: true, }, diff --git a/packages/plugin-collections/src/hooks/collections-after-create.ts b/packages/plugin-collections/src/hooks/collections-after-create.ts index e88a5ec5f..51251dddf 100644 --- a/packages/plugin-collections/src/hooks/collections-after-create.ts +++ b/packages/plugin-collections/src/hooks/collections-after-create.ts @@ -1,5 +1,8 @@ import CollectionModel from '../models/collection'; -export default async function (model: CollectionModel) { - await model.migrate(); +export default async function (model: CollectionModel, options: any = {}) { + const { migrate = true } = options; + if (migrate) { + await model.migrate(); + } } diff --git a/packages/plugin-collections/src/hooks/collections-before-validate.ts b/packages/plugin-collections/src/hooks/collections-before-validate.ts index 005929e43..62358aca5 100644 --- a/packages/plugin-collections/src/hooks/collections-before-validate.ts +++ b/packages/plugin-collections/src/hooks/collections-before-validate.ts @@ -1,7 +1,5 @@ import CollectionModel from '../models/collection'; export default async function (model: CollectionModel) { - if (!model.get('name')) { - model.setDataValue('name', this.generateName()); - } + model.generateNameIfNull(); } diff --git a/packages/plugin-collections/src/hooks/fields-after-create.ts b/packages/plugin-collections/src/hooks/fields-after-create.ts index d00e51bd5..5ea8f78c9 100644 --- a/packages/plugin-collections/src/hooks/fields-after-create.ts +++ b/packages/plugin-collections/src/hooks/fields-after-create.ts @@ -1,6 +1,8 @@ import FieldModel from '../models/field'; -export default async function (model: FieldModel) { - // console.log('afterCreate', model.toJSON()); - await model.migrate(); +export default async function (model: FieldModel, options: any = {}) { + const { migrate = true } = options; + if (migrate) { + await model.migrate(); + } } diff --git a/packages/plugin-collections/src/hooks/fields-before-validate.ts b/packages/plugin-collections/src/hooks/fields-before-validate.ts index d89910053..2235abec9 100644 --- a/packages/plugin-collections/src/hooks/fields-before-validate.ts +++ b/packages/plugin-collections/src/hooks/fields-before-validate.ts @@ -1,28 +1,10 @@ import FieldModel from '../models/field'; import * as types from '../interfaces/types'; +import _ from 'lodash'; export default async function (model: FieldModel) { - const values = model.get(); - if (!values.name) { - values.name = this.generateName(); + model.generateNameIfNull(); + if (model.get('interface')) { + model.setInterface(model.get('interface')); } - if (values.interface) { - const { options } = types[values.interface]; - Object.keys(options).forEach(key => { - switch (typeof values[key]) { - case 'undefined': - values[key] = options[key]; - break; - - case 'object': - values[key] = { - ...options[key], - ...values[key] - }; - break; - } - }); - } - - model.set(values, { raw: true }); } diff --git a/packages/plugin-collections/src/interfaces/types.ts b/packages/plugin-collections/src/interfaces/types.ts index a7e2e33dc..547e20d09 100644 --- a/packages/plugin-collections/src/interfaces/types.ts +++ b/packages/plugin-collections/src/interfaces/types.ts @@ -453,6 +453,7 @@ export const json = { options: { interface: 'json', type: 'json', + dottie: true, component: { type: 'hidden', }, diff --git a/packages/plugin-collections/src/models/base.ts b/packages/plugin-collections/src/models/base.ts index caa35a1df..ab4ae6b42 100644 --- a/packages/plugin-collections/src/models/base.ts +++ b/packages/plugin-collections/src/models/base.ts @@ -1,7 +1,9 @@ import _ from 'lodash'; import { getDataTypeKey, Model } from '@nocobase/database'; +import { Utils } from 'sequelize'; export class BaseModel extends Model { + get additionalAttribute() { const tableOptions = this.database.getTable(this.constructor.name).getOptions(); return _.get(tableOptions, 'additionalAttribute') || 'options'; @@ -57,19 +59,22 @@ export class BaseModel extends Model { return _.get(options, key); } - set(key?: any, value?: any, options?: any) { + set(key?: any, value?: any, options: any = {}) { if (typeof key === 'string') { // 不处理关系数据 - // @ts-ignore + // @ts-ignore if (_.get(this.constructor.associations, key)) { return this; } // 如果是 object 数据,merge 处理 if (_.isPlainObject(value)) { - value = _.merge(this.get(key)||{}, value); + // @ts-ignore + value = Utils.merge(this.get(key)||{}, value); } const [column, ...path] = key.split('.'); - this.changed(column, true); + if (!options.raw) { + this.changed(column, true); + } if (this.hasSetAttribute(column)) { if (!path.length) { return super.set(key, value, options); @@ -81,7 +86,9 @@ export class BaseModel extends Model { // 如果未设置 attribute,存到 additionalAttribute 里 const opts = this.get(this.additionalAttribute, options) || {}; _.set(opts, key, value); - this.changed(this.additionalAttribute, true); + if (!options.raw) { + this.changed(this.additionalAttribute, true); + } return super.set(this.additionalAttribute, opts, options); } return super.set(key, value, options); @@ -94,7 +101,8 @@ export class BaseModel extends Model { return this; } if (_.isPlainObject(value)) { - value = _.merge(this.get(key)||{}, value); + // @ts-ignore + value = Utils.merge(this.get(key)||{}, value); } const [column, ...path] = key.split('.'); this.changed(column, true); diff --git a/packages/plugin-collections/src/models/collection.ts b/packages/plugin-collections/src/models/collection.ts index 5292598fb..2c7e01105 100644 --- a/packages/plugin-collections/src/models/collection.ts +++ b/packages/plugin-collections/src/models/collection.ts @@ -1,10 +1,36 @@ import _ from 'lodash'; import BaseModel from './base'; import { TableOptions } from '@nocobase/database'; -import { SaveOptions, Utils } from 'sequelize'; +import { SaveOptions } from 'sequelize'; + +/** + * 生成随机数据库表名 + * + * 策略:暂时使用 3+2 + * 1. 自增 id + * 2. 随机字母 + * 3. 时间戳 + * 4. 转拼音 + * 5. 常见词翻译 + * + * @param title 显示的名称 + */ +export function generateCollectionName(title?: string): string { + return `t_${Date.now().toString(36)}_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; +} export class CollectionModel extends BaseModel { + generateName() { + this.set('name', generateCollectionName()); + } + + generateNameIfNull() { + if (!this.get('name')) { + this.generateName(); + } + } + /** * 通过 name 获取 collection * @@ -14,22 +40,6 @@ export class CollectionModel extends BaseModel { return this.findOne({ where: { name } }); } - /** - * 生成随机数据库表名 - * - * 策略:暂时使用 3+2 - * 1. 自增 id - * 2. 随机字母 - * 3. 时间戳 - * 4. 转拼音 - * 5. 常见词翻译 - * - * @param title 显示的名称 - */ - static generateName(title?: string): string { - return `t_${Date.now().toString(36)}_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; - } - /** * 迁移 */ @@ -80,12 +90,8 @@ export class CollectionModel extends BaseModel { ...item, sort, })); - for (const item of items[key]) { - await collection[`create${_.upperFirst(Utils.singularize(key))}`](item); - } } - // updateAssociations 有 BUG - // await collection.updateAssociations(items, options); + await collection.updateAssociations(items, options); return collection; } } diff --git a/packages/plugin-collections/src/models/field.ts b/packages/plugin-collections/src/models/field.ts index eba06c8ee..9cb7ecab2 100644 --- a/packages/plugin-collections/src/models/field.ts +++ b/packages/plugin-collections/src/models/field.ts @@ -1,10 +1,30 @@ import _ from 'lodash'; import BaseModel from './base'; import { FieldOptions } from '@nocobase/database'; +import * as types from '../interfaces/types'; +import { Utils } from 'sequelize'; + +export function generateFieldName(title?: string): string { + return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; +} export class FieldModel extends BaseModel { - static generateName(title?: string): string { - return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; + + generateName() { + this.set('name', generateFieldName()); + } + + generateNameIfNull() { + if (!this.get('name')) { + this.generateName(); + } + } + + setInterface(value) { + const { options } = types[value]; + // @ts-ignore + const values = Utils.merge(options, this.get()); + this.set(values); } async getOptions(): Promise {