mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:06:06 +00:00
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
This commit is contained in:
parent
1980464f63
commit
3e3cb416b6
@ -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');
|
||||
|
@ -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;
|
||||
|
@ -26,6 +26,7 @@ export function Update(props) {
|
||||
{...props}
|
||||
reference={drawerRef}
|
||||
viewName={viewName}
|
||||
mode={'update'}
|
||||
{...params}
|
||||
/>
|
||||
<Button type={'primary'} onClick={() => {
|
||||
|
@ -15,7 +15,7 @@ export function Details(props: any) {
|
||||
associatedKey,
|
||||
resourceKey,
|
||||
} = props;
|
||||
const { data = {}, loading } = useRequest(() => {
|
||||
const { data = {}, loading, refresh } = useRequest(() => {
|
||||
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||||
return api.resource(name).get({
|
||||
resourceKey,
|
||||
@ -26,7 +26,9 @@ export function Details(props: any) {
|
||||
const { actions = [], fields = [] } = props.schema;
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
|
||||
<Actions {...props} onFinish={() => {
|
||||
refresh();
|
||||
}} style={{ marginBottom: 14 }} actions={actions}/>
|
||||
{loading ? <Spin/> : (
|
||||
<Descriptions bordered column={1}>
|
||||
{fields.map((field: any) => {
|
||||
|
@ -27,10 +27,14 @@ export const DrawerForm = forwardRef((props: any, ref) => {
|
||||
resourceName,
|
||||
associatedName,
|
||||
associatedKey,
|
||||
onFinish,
|
||||
} = props;
|
||||
console.log(associatedKey);
|
||||
const [resourceKey, setResourceKey] = useState(props.resourceKey);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||||
const { data, run, loading } = useRequest((resourceKey) => {
|
||||
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||||
setResourceKey(resourceKey);
|
||||
return api.resource(name).get({
|
||||
resourceKey,
|
||||
associatedKey,
|
||||
@ -44,7 +48,7 @@ export const DrawerForm = forwardRef((props: any, ref) => {
|
||||
}));
|
||||
const actions = createAsyncFormActions();
|
||||
const { title, fields: properties ={} } = props.schema||{};
|
||||
console.log({properties});
|
||||
console.log({onFinish});
|
||||
return (
|
||||
<Drawer
|
||||
{...props}
|
||||
@ -57,8 +61,22 @@ export const DrawerForm = forwardRef((props: any, ref) => {
|
||||
title={title}
|
||||
footer={[
|
||||
<Button type={'primary'} onClick={async () => {
|
||||
const values = await actions.submit();
|
||||
const { values = {} } = await actions.submit();
|
||||
console.log(values);
|
||||
if (resourceKey) {
|
||||
await api.resource(name).update({
|
||||
resourceKey,
|
||||
associatedKey,
|
||||
...values,
|
||||
});
|
||||
} else {
|
||||
await api.resource(name).create({
|
||||
associatedKey,
|
||||
...values,
|
||||
});
|
||||
}
|
||||
setVisible(false);
|
||||
onFinish && onFinish(values);
|
||||
}}>提交</Button>
|
||||
]}
|
||||
>
|
||||
|
@ -64,9 +64,17 @@ export function SimpleTable(props: SimpleTableProps) {
|
||||
console.log('rowViewName', {rowViewName})
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
|
||||
<Actions
|
||||
{...props}
|
||||
style={{ marginBottom: 14 }}
|
||||
actions={actions}
|
||||
onFinish={() => {
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
<ViewFactory
|
||||
{...props}
|
||||
mode={'update'}
|
||||
viewName={rowViewName}
|
||||
reference={drawerRef}
|
||||
/>
|
||||
|
@ -80,7 +80,14 @@ export function Table(props: TableProps) {
|
||||
}
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
|
||||
<Actions
|
||||
{...props}
|
||||
style={{ marginBottom: 14 }}
|
||||
actions={actions}
|
||||
onFinish={() => {
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
<AntdTable
|
||||
rowKey={rowKey}
|
||||
columns={fields2columns(fields)}
|
||||
|
@ -45,12 +45,14 @@ export default function ViewFactory(props: ViewProps) {
|
||||
associatedKey,
|
||||
resourceName,
|
||||
viewName,
|
||||
mode,
|
||||
reference,
|
||||
} = props;
|
||||
const { data = {}, loading } = useRequest(() => {
|
||||
const params = {
|
||||
resourceKey: viewName,
|
||||
associatedName: associatedName,
|
||||
mode,
|
||||
};
|
||||
return api.resource(resourceName).getView(params);
|
||||
}, {
|
||||
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -139,7 +139,7 @@ export default {
|
||||
name: 'component.tooltip',
|
||||
title: '提示信息',
|
||||
component: {
|
||||
type: 'string',
|
||||
type: 'textarea',
|
||||
showInDetail: true,
|
||||
showInForm: true,
|
||||
},
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -453,6 +453,7 @@ export const json = {
|
||||
options: {
|
||||
interface: 'json',
|
||||
type: 'json',
|
||||
dottie: true,
|
||||
component: {
|
||||
type: 'hidden',
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<FieldOptions> {
|
||||
|
Loading…
Reference in New Issue
Block a user