diff --git a/.umirc.ts b/.umirc.ts index cddff7582..7570a2d9b 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -8,10 +8,11 @@ export default defineConfig({ define: { 'process.env.API_URL': process.env.API_URL, }, + includes: ['docs'], // mfsu: {}, // ssr: {}, // exportStatic: {}, - mode: 'site', + mode: 'doc', logo: 'https://www.nocobase.com/dist/images/logo.png', navs: { 'en-US': [ diff --git a/packages/plugin-china-region/src/collections/china_regions.ts b/packages/plugin-china-region/src/collections/china_regions.ts index 2edf7b5bd..007ce03ba 100644 --- a/packages/plugin-china-region/src/collections/china_regions.ts +++ b/packages/plugin-china-region/src/collections/china_regions.ts @@ -10,21 +10,15 @@ export default { // 如使用代码作为 id 可能更节省,但由于代码数字最长为 12 字节,除非使用 bigint(64) 才够放置 { name: 'code', - title: '代码', - interface: 'string', type: 'string', unique: true, }, { name: 'name', - title: '名称', - interface: 'string', type: 'string', }, { name: 'parent', - title: '从属', - interface: 'linkTo', type: 'belongsTo', target: 'china_regions', targetKey: 'code', @@ -32,8 +26,6 @@ export default { }, { name: 'children', - title: '下辖', - interface: 'linkTo', type: 'hasMany', target: 'china_regions', sourceKey: 'code', @@ -41,7 +33,6 @@ export default { }, { name: 'level', - title: '层级', type: 'integer' } ] diff --git a/packages/plugin-china-region/src/server.ts b/packages/plugin-china-region/src/server.ts index 37b3ce17c..48006e59d 100644 --- a/packages/plugin-china-region/src/server.ts +++ b/packages/plugin-china-region/src/server.ts @@ -1,11 +1,19 @@ import path from 'path'; -import { registerModels } from '@nocobase/database'; +import Database, { registerModels } from '@nocobase/database'; import { ChinaRegion } from './models/china-region'; +import Application from '@nocobase/server'; -export default async function (options = {}) { +registerModels({ ChinaRegion }); + +export default async function (this: Application, options = {}) { const { database } = this; - registerModels({ ChinaRegion }); + database.import({ directory: path.resolve(__dirname, 'collections'), }); + + this.on('china-region.init', async () => { + const M = database.getModel('china_regions'); + await M.importData(); + }); } diff --git a/packages/plugin-collections-v04/package.json b/packages/plugin-collections-v04/package.json deleted file mode 100644 index 87d791eda..000000000 --- a/packages/plugin-collections-v04/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@nocobase/plugin-collections-v04", - "version": "0.4.0-alpha.7", - "main": "lib/index.js", - "license": "MIT", - "dependencies": { - "@nocobase/database": "^0.4.0-alpha.7", - "@nocobase/resourcer": "^0.4.0-alpha.7", - "@nocobase/server": "^0.4.0-alpha.7", - "deepmerge": "^4.2.2" - }, - "devDependencies": { - "@nocobase/actions": "^0.4.0-alpha.7" - }, - "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" -} diff --git a/packages/plugin-collections-v04/src/__tests__/actions/collections.test.ts b/packages/plugin-collections-v04/src/__tests__/actions/collections.test.ts deleted file mode 100644 index 1c9578bdf..000000000 --- a/packages/plugin-collections-v04/src/__tests__/actions/collections.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Agent, getAgent, getApp } from '..'; -import { Application } from '@nocobase/server'; -import { types } from '../../interfaces'; - -describe('collection hooks', () => { - let app: Application; - let agent: Agent; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - }); - - afterEach(() => app.database.close()); - - it('create table', async () => { - const response = await agent.resource('collections').create({ - values: { - name: 'tests', - title: 'tests', - }, - }); - const table = app.database.getTable('tests'); - expect(table).toBeDefined(); - }); - - it('create table without name', async () => { - const response = await agent.resource('collections').create({ - values: { - title: 'tests', - }, - }); - - const { name } = response.body; - const table = app.database.getTable(name); - expect(table).toBeDefined(); - expect(table.getOptions().title).toBe('tests'); - - const list = await agent.resource('collections').list(); - expect(list.body.rows.length).toBe(1); - - await table.getModel().drop(); - }); -}); diff --git a/packages/plugin-collections-v04/src/__tests__/index.ts b/packages/plugin-collections-v04/src/__tests__/index.ts deleted file mode 100644 index e0574424b..000000000 --- a/packages/plugin-collections-v04/src/__tests__/index.ts +++ /dev/null @@ -1,144 +0,0 @@ -import qs from 'qs'; -import plugin from '../server'; -import supertest from 'supertest'; -import bodyParser from 'koa-bodyparser'; -import { Dialect } from 'sequelize'; -import Database from '@nocobase/database'; -import { actions, middlewares } from '@nocobase/actions'; -import { Application, middleware } from '@nocobase/server'; - -function getTestKey() { - const { id } = require.main; - const key = id - .replace(`${process.env.PWD}/packages`, '') - .replace(/src\/__tests__/g, '') - .replace('.test.ts', '') - .replace(/[^\w]/g, '_') - .replace(/_+/g, '_'); - return key -} - -const config = { - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - host: process.env.DB_HOST, - port: Number.parseInt(process.env.DB_PORT, 10), - dialect: process.env.DB_DIALECT as Dialect, - define: { - hooks: { - beforeCreate(model, options) { - - }, - }, - }, - logging: false, - sync: { - force: true, - alter: { - drop: true, - }, - }, -}; - -export async function getApp() { - const app = new Application({ - database: { - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - }, - }, - resourcer: { - prefix: '/api', - }, - }); - app.resourcer.use(middlewares.associated); - app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common }); - app.registerPlugin('collections', [plugin]); - await app.loadPlugins(); - await app.database.sync(); - // 表配置信息存到数据库里 - // const tables = app.database.getTables([]); - // for (const table of tables) { - // const Collection = app.database.getModel('collections'); - // await Collection.import(table.getOptions(), { hooks: false }); - // } - app.use(async (ctx, next) => { - ctx.db = app.database; - await next(); - }); - app.use(bodyParser()); - app.use(middleware({ - prefix: '/api', - resourcer: app.resourcer, - database: app.database, - })); - return app; -} - -interface ActionParams { - resourceKey?: string | number; - // resourceName?: string; - // associatedName?: string; - associatedKey?: string | number; - fields?: any; - filter?: any; - values?: any; - [key: string]: any; -} - -interface Handler { - get: (params?: ActionParams) => Promise; - list: (params?: ActionParams) => Promise; - create: (params?: ActionParams) => Promise; - update: (params?: ActionParams) => Promise; - destroy: (params?: ActionParams) => Promise; - [name: string]: (params?: ActionParams) => Promise; -} - -export interface Agent { - resource: (name: string) => Handler; -} - -export function getAgent(app: Application): Agent { - const agent = supertest.agent(app.callback()); - return { - resource(name: string): any { - return new Proxy({}, { - get(target, method, receiver) { - return (params: ActionParams = {}) => { - const { associatedKey, resourceKey, values = {}, ...restParams } = params; - let url = `/api/${name}`; - if (associatedKey) { - url = `/api/${name.split('.').join(`/${associatedKey}/`)}`; - } - url += `:${method as string}`; - if (resourceKey) { - url += `/${resourceKey}`; - } - console.log(url); - if (['list', 'get'].indexOf(method as string) !== -1) { - return agent.get(`${url}?${qs.stringify(restParams)}`); - } else { - return agent.post(`${url}?${qs.stringify(restParams)}`).send(values); - } - } - } - }); - } - }; -} - -export function getDatabase() { - return new Database({ - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - } - }); -}; diff --git a/packages/plugin-collections-v04/src/__tests__/models/base.test.ts b/packages/plugin-collections-v04/src/__tests__/models/base.test.ts deleted file mode 100644 index c4bc28310..000000000 --- a/packages/plugin-collections-v04/src/__tests__/models/base.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -import Database, { ModelCtor } from '@nocobase/database'; -import { getDatabase } from '../'; -import BaseModel from '../../models/base'; -import _ from 'lodash'; - -describe('models.base', () => { - let database: Database; - let TestModel: ModelCtor; - let test: BaseModel; - beforeEach(async () => { - database = getDatabase(); - database.table({ - name: 'tests', - model: BaseModel, - additionalAttribute: 'options', - fields: [ - { - name: 'name', - type: 'string', - }, - { - name: 'title', - type: 'virtual', - }, - { - name: 'xyz', - type: 'virtual', - defaultValue: 'xyz1', - }, - { - name: 'content', - type: 'virtual', - set(val) { - // 留空 - } - }, - { - name: 'key1', - type: 'virtual', - set(val) { - this.setDataValue('options.key1', `111${val}111`); - } - }, - { - name: 'key2', - type: 'virtual', - get() { - return 'val2'; - } - }, - { - type: 'json', - name: 'component', - defaultValue: {}, - }, - { - type: 'json', - name: 'options', - defaultValue: {}, - }, - ], - }); - await database.sync(); - TestModel = database.getModel('tests') as ModelCtor; - test = await TestModel.create({ - name: '123', - abc: { aa: 'aa' }, - 'abc.bb': 'bb', - component: { - a: 'a', - }, - 'component.b': 'b', - options: { - bcd: 'bbb', - }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - afterEach(() => database.close()); - - it('get all attribute', async () => { - // 获取所有字段 - expect(test.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - component: { a: 'a', b: 'b' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - it('get options attribute', async () => { - // 直接取 options 字段 - expect(test.get('options')).toEqual({ - abc: { - aa: 'aa', - bb: 'bb', - }, - bcd: 'bbb', - xyz: "xyz1", - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - it('get component attribute', async () => { - expect(test.get('component')).toEqual({ a: 'a', b: 'b' }); - }); - - it('set component attribute with dot key', async () => { - test.set('component.c', 'c'); - await test.save(); - expect(test.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - component: { a: 'a', b: 'b' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - expect(test.get('component')).toEqual({ a: 'a', b: 'b', c: 'c' }); - }); - - it('set options attribute with dot key', async () => { - test.set('options.cccc', 'cccc'); - await test.save(); - expect(test.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - cccc: 'cccc', - component: { a: 'a', b: 'b' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - it('set options attribute without options prefix', async () => { - test.set('dddd', 'dddd'); - await test.save(); - expect(test.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - dddd: 'dddd', - component: { a: 'a', b: 'b' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - it('refind', async () => { - test.set('component.c', 'c'); - await test.save(); - // 重新查询 - const test2 = await TestModel.findByPk(test.id); - expect(test2.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - component: { a: 'a', b: 'b', c: 'c' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - expect(test2.get('component')).toEqual({ a: 'a', b: 'b', c: 'c' }); - }); - - it('update', async () => { - await test.update({ - 'name123': 'xxx', - 'component.d': 'd', - }); - expect(test.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - name123: 'xxx', - component: { a: 'a', b: 'b', d: 'd' }, - arr: [{ a: 'a' }, { b: 'b' }], - }); - }); - - it('update virtual attribute', async () => { - await test.update({ - title: 'xxx', // 虚拟字段没 set 转存 options - content: 'content123', // set 留空,这个 key 什么也不做 - key1: 'val1', // 走 set 方法 - }); - // 重新获取再验证 - const test2 = await TestModel.findByPk(test.id); - expect(test2.get()).toMatchObject({ - abc: { aa: 'aa', bb: 'bb' }, - bcd: 'bbb', - name: '123', - component: { a: 'a', b: 'b' }, - arr: [{ a: 'a' }, { b: 'b' }], - title: 'xxx', - key2: 'val2', // key2 为 get 方法取的 - key1: '111val1111', - }); - 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', - }); - }); - - it('component', async () => { - const t = await TestModel.create({ - component: { - arr: [ - { a: 'a', aa: 'aa' }, - { b: 'b', bb: 'bb' }, - { c: 'c', cc: 'cc' }, - ], - }, - }); - t.set({ - component: { - arr: [ - { a: 'aa' }, - { b: 'bb' }, - ], - } - }); - await t.save(); - expect(t.get('component')).toEqual({ - arr: [ - { a: 'aa' }, - { b: 'bb' }, - ], - }); - }) -}); diff --git a/packages/plugin-collections-v04/src/__tests__/models/collection.test.ts b/packages/plugin-collections-v04/src/__tests__/models/collection.test.ts deleted file mode 100644 index fb1b5f288..000000000 --- a/packages/plugin-collections-v04/src/__tests__/models/collection.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Agent, getAgent, getApp } from '../'; -import { Application } from '@nocobase/server'; -import * as types from '../../interfaces/types'; - -describe('models.collection', () => { - let app: Application; - let agent: Agent; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - }); - - afterEach(() => app.database.close()); - - it('import all tables', async () => { - const tables = app.database.getTables([]); - for (const table of tables) { - const Collection = app.database.getModel('collections'); - await Collection.import(table.getOptions(), { migrate: false }); - } - }); - - it('import examples', async () => { - await app.database.getModel('collections').import({ - title: '示例', - name: 'examples', - showInDataMenu: true, - statusable: false, - fields: [ - { - interface: 'string', - title: '单行文本', - name: 'string', - component: { - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'textarea', - title: '多行文本', - name: 'textarea', - component: { - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - ], - }, { - // migrate: false, - }); - const table = app.database.getTable('examples'); - expect(table).toBeDefined(); - expect(table.getFields().size).toBe(2); - await table.sync(); - const Example = app.database.getModel('examples'); - const example = await Example.create({ - string: 'string1', - textarea: 'textarea1', - }); - expect(example.toJSON()).toMatchObject({ - string: 'string1', - textarea: 'textarea1', - }); - }); -}); diff --git a/packages/plugin-collections-v04/src/__tests__/models/field.test.ts b/packages/plugin-collections-v04/src/__tests__/models/field.test.ts deleted file mode 100644 index b0ba47f8e..000000000 --- a/packages/plugin-collections-v04/src/__tests__/models/field.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Agent, getAgent, getApp } from '../'; -import { Application } from '@nocobase/server'; -import { types } from '../../interfaces'; - -describe('models.field', () => { - let app: Application; - let agent: Agent; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - }); - - afterEach(() => app.database.close()); - - it('updatedAt', async () => { - const Field = app.database.getModel('fields'); - const field = new Field(); - field.setInterface('updatedAt'); - expect(field.get()).toMatchObject(types.updatedAt.options) - }); - - it('dataSource', async () => { - const Collection = app.database.getModel('collections'); - // @ts-ignore - const collection = await Collection.create({ - title: 'tests', - }); - await collection.updateAssociations({ - fields: [ - { - title: 'xx', - name: 'xx', - interface: 'select', - type: 'virtual', - dataSource: [ - { label: 'xx', value: 'xx' }, - ], - component: { - type: 'string', - showInDetail: true, - showInForm: true, - }, - } - ], - }); - const fields = await collection.getFields(); - expect(fields[0].get('dataSource')).toEqual([ - { label: 'xx', value: 'xx' }, - ]); - }); - - it.skip('sub table field', async () => { - const [Collection, Field] = app.database.getModels(['collections', 'fields']); - const options = { - title: 'tests', - name: 'tests', - fields: [ - { - interface: 'subTable', - title: '子表格', - name: 'subs', - children: [ - { - interface: 'string', - title: '名称', - name: 'name', - }, - ], - }, - ], - }; - const collection = await Collection.create(options); - await collection.updateAssociations(options); - const field = await Field.findOne({ - where: { - title: '子表格', - }, - }); - await field.createChild({ - interface: 'string', - title: '名称', - name: 'title', - }); - const Test = app.database.getModel('tests'); - const Sub = app.database.getModel('subs'); - // console.log(Test.associations); - // console.log(Sub.rawAttributes); - const test = await Test.create({}); - const sub = await test.createSub({ name: 'name1', title: 'title1' }); - expect(sub.toJSON()).toMatchObject({ name: 'name1', title: 'title1' }) - }); - - it('sub table field', async () => { - const [Collection, Field] = app.database.getModels(['collections', 'fields']); - // @ts-ignore - const options = { - title: 'tests', - name: 'tests', - fields: [ - { - interface: 'subTable', - title: '子表格', - // name: 'subs', - children: [ - { - interface: 'string', - title: '名称', - // name: 'name', - }, - ], - }, - ], - }; - const collection = await Collection.create(options); - await collection.updateAssociations(options); - const field = await Field.findOne({ - where: { - title: '子表格', - }, - }); - await field.createChild({ - interface: 'string', - title: '名称', - name: 'title', - }); - }); -}); diff --git a/packages/plugin-collections-v04/src/actions/getCollection.ts b/packages/plugin-collections-v04/src/actions/getCollection.ts deleted file mode 100644 index 380df51c7..000000000 --- a/packages/plugin-collections-v04/src/actions/getCollection.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; -import { ResourceOptions } from '@nocobase/resourcer'; -import { get } from 'lodash'; - -export default async (ctx, next) => { - const { resourceName, resourceKey } = ctx.action.params; - const [Collection, Tab, View] = ctx.db.getModels(['collections', 'tabs', 'views']) as ModelCtor[]; - const collection = await Collection.findOne(Collection.parseApiJson({ - filter: { - name: resourceName, - }, - // fields: { - // // appends: ['tabs'], - // }, - })); - const views = await collection.getViews({ - where: { - default: true, - }, - }); - collection.setDataValue('defaultViewId', get(views, [0, 'id'])); - collection.setDataValue('defaultViewName', get(views, [0, 'name'])); - const tabs = await collection.getTabs(); - ctx.body = { - ...collection.toJSON(), - tabs: tabs.map(tab => ({ - ...tab.toJSON(), - ...tab.options, - viewCollectionName: tab.type == 'association' ? tab.options.association : tab.collection_name, - })), - }; - await next(); -} diff --git a/packages/plugin-collections-v04/src/actions/getView.ts b/packages/plugin-collections-v04/src/actions/getView.ts deleted file mode 100644 index 5a230a5bd..000000000 --- a/packages/plugin-collections-v04/src/actions/getView.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ResourceOptions } from '@nocobase/resourcer'; -import { Model, ModelCtor } from '@nocobase/database'; -import { get } from 'lodash'; - -export default async (ctx, next) => { - const { resourceName, resourceKey } = ctx.action.params; - const [View, Field, Action] = ctx.db.getModels(['views', 'fields', 'actions']) as ModelCtor[]; - const view = await View.findOne(View.parseApiJson({ - filter: { - collection_name: resourceName, - name: resourceKey, - }, - fields: { - appends: ['actions', 'fields'], - }, - })); - const collection = await view.getCollection(); - const fields = await collection.getFields(); - const actions = await collection.getActions(); - const actionNames = view.options.actionNames || []; - // console.log(view.options); - if (view.type === 'table') { - const defaultTabs = await collection.getTabs({ - where: { - default: true, - }, - }); - view.setDataValue('defaultTabName', get(defaultTabs, [0, 'name'])); - } - if (view.options.updateViewId) { - view.setDataValue('rowViewName', view.options.updateViewName); - } - view.setDataValue('viewCollectionName', view.collection_name); - ctx.body = { - ...view.toJSON(), - ...(view.options || {}), - fields, - actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({ - ...action.toJSON(), - ...action.options, - viewCollectionName: action.collection_name, - })), - }; - await next(); -}; diff --git a/packages/plugin-collections-v04/src/collections/actions.ts b/packages/plugin-collections-v04/src/collections/actions.ts deleted file mode 100644 index 1188b4189..000000000 --- a/packages/plugin-collections-v04/src/collections/actions.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'actions', - title: '操作配置', - internal: true, - draggable: true, - model: 'ActionModel', - developerMode: true, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['collection'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '名称', - component: { - type: 'string', - className: 'drag-visible', - showInForm: true, - showInTable: true, - showInDetail: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '标识', - component: { - type: 'string', - showInForm: true, - showInTable: true, - showInDetail: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'type', - title: '类型', - component: { - type: 'string', - showInForm: true, - showInTable: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/collections/collections.ts b/packages/plugin-collections-v04/src/collections/collections.ts deleted file mode 100644 index 23dbe2148..000000000 --- a/packages/plugin-collections-v04/src/collections/collections.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'collections', - title: '数据表配置', - internal: true, - sortable: true, - draggable: true, - model: 'CollectionModel', - developerMode: true, - createdAt: 'createdTime', - updatedAt: 'updatedTime', - fields: [ - // { - // interface: 'sort', - // type: 'sort', - // name: 'sort', - // title: '排序', - // component: { - // type: 'sort', - // className: 'drag-visible', - // width: 60, - // showInTable: true, - // }, - // }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '数据表名称', - required: true, - component: { - type: 'string', - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - createOnly: true, - title: '标识', - unique: true, - required: true, - developerMode: true, - component: { - type: 'string', - }, - }, - { - interface: 'textarea', - type: 'text', - name: 'description', - title: '数据表描述', - component: { - type: 'textarea', - }, - }, - // { - // interface: 'boolean', - // type: 'virtual', - // name: 'createdAt', - // title: '记录创建时间', - // developerMode: true, - // defaultValue: true, - // component: { - // type: 'checkbox', - // default: true, - // showInForm: true, - // }, - // }, - // { - // interface: 'boolean', - // type: 'virtual', - // name: 'updatedAt', - // title: '记录修改时间', - // developerMode: true, - // defaultValue: true, - // component: { - // type: 'checkbox', - // default: true, - // showInForm: true, - // }, - // }, - { - interface: 'boolean', - type: 'virtual', - name: 'createdBy', - title: '记录创建人信息', - developerMode: true, - component: { - type: 'checkbox', - default: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'updatedBy', - title: '记录修改人信息', - developerMode: true, - component: { - type: 'checkbox', - default: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - developerMode: true, - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'internal', - title: '系统内置', - defaultValue: false, - developerMode: true, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'fields', - title: '字段', - sourceKey: 'name', - draggable: true, - actions: { - list: { - sort: 'sort', - }, - get: { - fields: { - appends: ['children'], - }, - }, - }, - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'actions', - title: '动作', - sourceKey: 'name', - draggable: true, - actions: { - list: { - sort: 'sort', - }, - }, - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'views_v2', - target: 'views_v2', - title: '视图', - sourceKey: 'name', - draggable: true, - // actions: { - // list: { - // sort: 'sort', - // }, - // destroy: { - // filter: { - // default: false - // } - // } - // }, - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'scopes', - target: 'scopes', - title: '数据范围', - sourceKey: 'name', - actions: { - list: { - sort: 'id', - }, - update: { - filter: { - locked: false - } - }, - destroy: { - filter: { - locked: false - } - } - }, - component: { - type: 'drawerSelect', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['title', 'description'], - detailsOpenMode: 'window', // window - details: ['descriptions', 'fields', 'views'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: ['title', 'description'], - }, - { - developerMode: true, - type: 'descriptions', - name: 'descriptions', - title: '详情', - fields: ['title', 'description'], - actions: [ - { - name: 'update', - type: 'update', - title: '编辑', - viewName: 'form', - }, - ], - }, - { - developerMode: true, - type: 'table', - name: 'permissions_table', - title: '权限表格', - labelField: 'title', - actions: [], - fields: ['title'], - detailsOpenMode: 'drawer', // window - details: ['permissions_form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'permissions_form', - title: '权限表单', - fields: [ - { - interface: 'json', - type: 'json', - title: '数据操作权限', - name: 'actions', - component: { - "type": "permissions.actions", - "title": "数据操作权限", - "x-linkages": [{ - "type": "value:schema", - "target": "actions", - "schema": { - "x-component-props": { - "resourceKey": "{{ $form.values && $form.values.resourceKey }}" - } - } - }], - "x-component-props": { - "dataSource": [] - } - }, - }, - { - interface: 'json', - type: 'json', - title: '字段权限', - name: 'fields', - component: { - "type": "permissions.fields", - "x-linkages": [{ - "type": "value:schema", - "target": "fields", - "schema": { - "x-component-props": { - "resourceKey": "{{ $form.values && $form.values.resourceKey }}" - } - } - }], - "x-component-props": { - "dataSource": [] - } - }, - }, - ], - }, - { - developerMode: true, - type: 'table', - dataSourceType: 'association', - name: 'fields', - title: '字段', - targetViewName: 'table2', - targetFieldName: 'fields', - }, - { - developerMode: true, - type: 'table', - dataSourceType: 'association', - name: 'views', - title: '视图', - targetViewName: 'table', - targetFieldName: 'views_v2', - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/collections/fields.ts b/packages/plugin-collections-v04/src/collections/fields.ts deleted file mode 100644 index 81be1ed77..000000000 --- a/packages/plugin-collections-v04/src/collections/fields.ts +++ /dev/null @@ -1,690 +0,0 @@ -import { TableOptions } from '@nocobase/database'; -import { types, getOptions } from '../interfaces'; - -export default { - name: 'fields', - title: '字段配置', - internal: true, - draggable: true, - model: 'FieldModel', - developerMode: true, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['collection'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '字段名称', - required: true, - component: { - type: 'string', - className: 'drag-visible', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '标识', - required: true, - createOnly: true, - developerMode: true, - component: { - type: 'string', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'select', - type: 'string', - name: 'interface', - title: '字段类型', - required: true, - dataSource: getOptions(), - createOnly: true, - component: { - type: 'select', - showInTable: true, - showInDetail: true, - showInForm: true, - "x-linkages": [ - // TODO(draft): 统一解决字段类型和配置参数联动的一种方式 - // { - // type: 'value:schema', - // target: 'options', - // schema: { - // 'x-component-props': { - // fields: '{{ $self.values[1].fields || [] }}' - // } - // }, - // condition: '{{ !!$self.value }}' - // }, - { - "type": "value:visible", - "target": "precision", - "condition": "{{ ['number', 'percent'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "dataSource", - "condition": "{{ ['select', 'multipleSelect', 'radio', 'checkboxes'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "dateFormat", - "condition": "{{ ['datetime', 'createdAt', 'updatedAt'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "showTime", - "condition": "{{ ['datetime', 'createdAt', 'updatedAt'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "timeFormat", - "condition": "{{ ['time'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "multiple", - "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "target", - "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}" - }, - // { - // "type": "value:visible", - // "target": "labelField", - // "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}" - // }, - { - "type": "value:visible", - "target": "createable", - "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "children", - "condition": "{{ ['subTable'].indexOf($self.value) !== -1 }}" - }, - { - "type": "value:visible", - "target": "component.showInTable", - "condition": "{{ ['subTable', 'description'].indexOf($self.value) === -1 }}" - }, - { - "type": "value:visible", - "target": "component.showInForm", - "condition": "{{ ['createdAt', 'updatedAt', 'createdBy', 'updatedBy'].indexOf($self.value) === -1 }}" - }, - { - "type": "value:visible", - "target": "required", - "condition": "{{ ['createdAt', 'updatedAt', 'createdBy', 'updatedBy'].indexOf($self.value) === -1 }}" - }, - { - "type": "value:visible", - "target": "maxLevel", - "condition": "{{ ['chinaRegion'].includes($self.value) }}" - }, - { - "type": "value:visible", - "target": "incompletely", - "condition": "{{ ['chinaRegion'].includes($self.value) }}" - }, - ], - }, - }, - // TODO(draft): 将 options 作为集合字段开放出来,可以动态的解决字段参数的配置表单联动问题 - // { - // interface: 'json', - // type: 'json', - // name: 'options', - // title: '配置信息', - // defaultValue: {}, - // component: { - // type: 'subFields', - // showInForm: true, - // }, - // }, - { - interface: 'subTable', - type: 'virtual', - name: 'dataSource', - title: '可选项', - component: { - type: 'table', - default: [{}], - // showInTable: true, - // showInDetail: true, - showInForm: true, - items: { - type: 'object', - properties: { - value: { - type: "string", - title: "值", - required: true - }, - label: { - type: "string", - title: "选项", - required: true - }, - }, - }, - }, - }, - { - interface: 'string', - type: 'string', - name: 'type', - title: '数据类型', - developerMode: true, - component: { - type: 'string', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'number', - type: 'integer', - name: 'parent_id', - title: '所属分组', - component: { - type: 'number', - }, - }, - { - interface: 'select', - type: 'virtual', - name: 'precision', - title: '精度', - dataSource: [ - { value: 0, label: '1' }, - { value: 1, label: '1.0' }, - { value: 2, label: '1.00' }, - { value: 3, label: '1.000' }, - { value: 4, label: '1.0000' }, - ], - component: { - type: 'number', - showInForm: true, - default: 0, - }, - }, - { - interface: 'select', - type: 'virtual', - name: 'dateFormat', - title: '日期格式', - dataSource: [ - { value: 'YYYY/MM/DD', label: '年/月/日' }, - { value: 'YYYY-MM-DD', label: '年-月-日' }, - { value: 'DD/MM/YYYY', label: '日/月/年' }, - ], - component: { - type: 'string', - showInForm: true, - default: 'YYYY-MM-DD', - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'showTime', - title: '显示时间', - component: { - type: 'boolean', - showInForm: true, - default: false, - "x-linkages": [ - { - "type": "value:visible", - "target": "timeFormat", - "condition": "{{ ($form.values && $form.values.interface === 'time') || $self.value === true }}" - }, - ], - }, - }, - { - interface: 'select', - type: 'virtual', - name: 'timeFormat', - title: '时间格式', - dataSource: [ - { value: 'HH:mm:ss', label: '24小时制' }, - { value: 'hh:mm:ss a', label: '12小时制' }, - ], - component: { - type: 'string', - showInForm: true, - default: 'HH:mm:ss', - }, - }, - // TODO(refactor): 此部分类型相关的参数,后期应拆分出去 - { - name: 'maxLevel', - title: '可选层级', - interface: 'radio', - type: 'virtual', - dataSource: [ - { value: 1, label: '省' }, - { value: 2, label: '市' }, - { value: 3, label: '区/县' }, - { value: 4, label: '乡镇/街道' }, - { value: 5, label: '村/居委会' }, - ], - component: { - showInForm: true, - default: 3 - } - }, - { - name: 'incompletely', - title: '可部分选择', - interface: 'boolean', - type: 'virtual', - component: { - showInForm: true, - } - }, - { - interface: 'linkTo', - multiple: false, - type: 'belongsTo', - name: 'parent', - title: '所属分组', - target: 'fields', - foreignKey: 'parent_id', - targetKey: 'id', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'string', - type: 'virtual', - name: 'target', - title: '要关联的数据表', - required: true, - createOnly: true, - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - mode: 'simple', - resourceName: 'collections', - labelField: 'title', - valueField: 'name', - }, - "x-linkages": [ - { - type: "value:state", - target: "labelField", - condition: "{{ $self.inputed }}", - state: { - value: null, - } - }, - { - "type": "value:visible", - "target": "labelField", - "condition": "{{ !!$self.value }}" - }, - { - type: "value:schema", - target: "labelField", - // condition: "{{ $self.value }}", - schema: { - "x-component-props": { - "associatedKey": "{{ $self.value }}" - }, - }, - }, - { - type: 'value:visible', - target: 'component.x-component-props.filter', - condition: '{{ !!$self.value }}' - }, - { - type: "value:schema", - target: "component.x-component-props.filter", - schema: { - "x-component-props": { - "associatedKey": "{{ $self.value }}" - }, - }, - }, - ], - }, - }, - { - interface: 'string', - type: 'virtual', - name: 'labelField', - title: '要显示的字段', - required: true, - component: { - type: 'remoteSelect', - 'x-component-props': { - mode: 'simple', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - }, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'multiple', - title: '允许添加多条记录', - component: { - type: 'checkbox', - showInDetail: true, - showInForm: true, - default: true, - }, - }, - { - name: 'component.x-component-props.filter', - interface: 'json', - type: 'virtual', - title: '数据范围', - component: { - type: 'filter', - 'x-component-props': { - resourceName: 'collections.fields', - }, - showInForm: true, - } - }, - { - interface: 'boolean', - type: 'virtual', - name: 'createable', - title: '允许直接在关联的数据表内新建数据', - component: { - type: 'checkbox', - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'subTable', - type: 'hasMany', - name: 'children', - target: 'fields', - sourceKey: 'id', - foreignKey: 'parent_id', - title: '子表格字段', - viewName: 'table2', - // visible: true, - component: { - type: 'subTable', - default: [], - // showInTable: true, - // showInDetail: true, - showInForm: true, - 'x-linkages': [ - { - type: 'value:schema', - target: 'children', - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.id }}" - }, - }, - }, - ], - }, - }, - // { - // interface: 'linkTo', - // multiple: true, - // type: 'hasMany', - // name: 'children', - // title: '子字段', - // target: 'fields', - // foreignKey: 'parent_id', - // sourceKey: 'id', - // component: { - // type: 'drawerSelect', - // }, - // }, - { - interface: 'textarea', - type: 'virtual', - name: 'component.tooltip', - title: '提示信息', - component: { - type: 'textarea', - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'required', - title: '必填项', - component: { - type: 'checkbox', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'component.showInTable', - title: '显示在表格中', - component: { - type: 'checkbox', - tooltip: '若勾选,该字段将作为一列显示在数据表里', - showInTable: true, - showInDetail: true, - showInForm: true, - default: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'component.showInForm', - title: '显示在表单中', - component: { - type: 'checkbox', - tooltip: '若勾选,该字段将出现在表单中', - showInTable: true, - showInDetail: true, - showInForm: true, - default: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'component.showInDetail', - title: '显示在详情中', - component: { - type: 'checkbox', - tooltip: '若勾选,该字段将出现在详情中', - showInTable: true, - showInDetail: true, - showInForm: true, - default: true, - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - labelField: 'title', - component: { - type: 'drawerSelect', - // showInTable: true, - 'x-component-props': { - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - }, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'json', - type: 'json', - name: 'component', - title: '前端组件', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '关联的字段', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['title', 'interface'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['sort'], - }, - { - developerMode: true, - type: 'table', - name: 'table2', - title: '表格', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['sort', 'title', 'interface'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['sort'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'interface', - 'dataSource', - 'precision', - 'dateFormat', - 'showTime', - 'timeFormat', - 'maxLevel', - 'incompletely', - 'target', - 'labelField', - 'children', - 'multiple', - // 'required', - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/collections/scopes.ts b/packages/plugin-collections-v04/src/collections/scopes.ts deleted file mode 100644 index cf88656a8..000000000 --- a/packages/plugin-collections-v04/src/collections/scopes.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'scopes', - title: '表操作范围', - developerMode: true, - internal: true, - fields: [ - { - comment: '范围名称', - type: 'string', - name: 'title', - title: '名称', - component: { - type: 'string', - showInTable: true, - showInForm: true, - }, - }, - { - interface: 'json', - type: 'jsonb', - name: 'filter', - title: '条件', - developerMode: false, - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - showInForm: true, - "x-linkages": [ - { - type: "value:schema", - target: "filter", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - ], - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'locked', - title: '锁定', - defaultValue: false, - component: { - showInTable: true, - } - }, - { - type: 'belongsTo', - name: 'collection', - targetKey: 'name', - onDelete: 'CASCADE' - } - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['title'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'filter', - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/collections/tabs.ts b/packages/plugin-collections-v04/src/collections/tabs.ts deleted file mode 100644 index 4298a4d2a..000000000 --- a/packages/plugin-collections-v04/src/collections/tabs.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'tabs', - title: '标签配置', - internal: true, - sortable: true, - model: 'TabModel', - developerMode: true, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['collection'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '名称', - required: true, - component: { - type: 'string', - className: 'drag-visible', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '标识', - component: { - type: 'string', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '类型', - required: true, - dataSource: [ - { label: '详情数据', value: 'details' }, - { label: '相关数据', value: 'association' }, - { label: '模块组合', value: 'module', disabled: true }, - ], - component: { - type: 'radio', - showInTable: true, - showInDetail: true, - showInForm: true, - "x-linkages": [ - // { - // "type": "value:visible", - // "target": "association", - // "condition": "{{ $self.value === 'association' }}" - // }, - { - type: "value:visible", - target: "associationField", - condition: "{{ $self.value === 'association' }}" - }, - { - type: "value:visible", - target: "displayFields", - condition: "{{ $self.value === 'details' }}", - }, - { - type: "value:visible", - target: "displayFormFields", - condition: "{{ $self.value === 'details' }}", - }, - // { - // type: "value:schema", - // target: "association", - // condition: "{{ $self.value === 'association' }}", - // schema: { - // "x-component-props": { - // "associatedKey": "{{ $form.values && $form.values.associatedKey }}" - // }, - // }, - // }, - { - type: "value:schema", - target: "displayFields", - condition: "{{ $self.value === 'details' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: "value:schema", - target: "displayFormFields", - condition: "{{ $self.value === 'details' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: "value:schema", - target: "associationField", - condition: "{{ $self.value === 'association' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - ], - }, - }, - // { - // interface: 'string', - // type: 'string', - // name: 'association', - // title: '相关数据', - // component: { - // type: 'remoteSelect', - // showInDetail: true, - // showInForm: true, - // 'x-component-props': { - // resourceName: 'collections.fields', - // labelField: 'title', - // valueField: 'name', - // filter: { - // interface: 'linkTo', - // }, - // }, - // }, - // }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'associationField', - target: 'fields', - title: '相关数据表', - labelField: 'title', - required: true, - // valueField: 'name', - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections.fields', - labelField: 'title', - // valueField: 'name', - objectValue: true, - filter: { - interface: 'linkTo', - }, - }, - "x-linkages": [ - { - type: "value:visible", - target: "viewName", - condition: "{{ !!$self.value }}" - }, - { - type: "value:schema", - target: "viewName", - condition: "{{ !!$self.value }}", - schema: { - "x-component-props": { - associatedKey: "{{ $self.value.target }}" - }, - }, - }, - ], - }, - }, - { - interface: 'json', - type: 'json', - name: 'displayFields', - title: '显示在详情中的字段', - labelField: 'title', - // valueField: 'name', - component: { - type: 'draggableTable', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - mode: 'showInDetail', - fields: [ - // { - // interface: 'sort', - // name: 'sort', - // title: '排序', - // type: 'sort', - // dataIndex: ['sort'], - // className: 'drag-visible', - // }, - { - interface: 'string', - name: 'title', - title: '字段名称', - type: 'string', - className: 'drag-visible', - dataIndex: ['title'], - } - ], - }, - }, - }, - { - interface: 'json', - type: 'json', - name: 'displayFormFields', - title: '当前标签页可编辑字段', - labelField: 'title', - // valueField: 'name', - component: { - type: 'draggableTable', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - mode: 'showInForm', - fields: [ - // { - // interface: 'sort', - // name: 'sort', - // title: '排序', - // type: 'sort', - // dataIndex: ['sort'], - // className: 'drag-visible', - // }, - { - interface: 'string', - name: 'title', - title: '字段名称', - type: 'string', - className: 'drag-visible', - dataIndex: ['title'], - } - ], - }, - }, - }, - { - interface: 'string', - type: 'string', - name: 'viewName', - title: '视图', - labelField: 'title', - required: true, - // valueField: 'name', - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections.views', - labelField: 'title', - valueField: 'name', - }, - }, - }, - { - interface: 'boolean', - type: 'radio', - name: 'default', - title: '作为默认标签页', - defaultValue: false, - scope: ['collection'], - component: { - type: 'checkbox', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'enabled', - title: '启用', - defaultValue: true, - component: { - type: 'checkbox', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - filter: { - default: false - } - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], - views: [ - { - type: 'form', - name: 'form', - title: '表单', - template: 'DrawerForm', - developerMode: true, - }, - { - type: 'details', - name: 'details', - title: '详情', - template: 'Details', - actionNames: ['update'], - developerMode: true, - }, - { - type: 'table', - name: 'simple', - title: '简易模式', - template: 'Table', - mode: 'simple', - default: true, - actionNames: ['destroy', 'create'], - detailsViewName: 'details', - updateViewName: 'form', - paginated: false, - draggable: true, - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/collections/views.ts b/packages/plugin-collections-v04/src/collections/views.ts deleted file mode 100644 index 0c0136fac..000000000 --- a/packages/plugin-collections-v04/src/collections/views.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'views', - title: '视图配置', - internal: true, - sortable: true, - model: 'ViewModel', - developerMode: true, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['collection'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '视图名称', - required: true, - component: { - type: 'string', - className: 'drag-visible', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '标识', - component: { - type: 'string', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '视图类型', - required: true, - dataSource: [ - { label: '表格', value: 'table' }, - { label: '日历', value: 'calendar' }, - // { label: '表单', value: 'form' }, - { label: '看板', value: 'kanban', disabled: true }, - { label: '地图', value: 'map', disabled: true }, - ], - component: { - type: 'radio', - showInTable: true, - showInDetail: true, - showInForm: true, - default: 'table', - "x-linkages": [ - { - "type": "value:visible", - "target": "filter", - "condition": "{{ $self.value !== 'form' }}" - }, - { - type: "value:schema", - target: "labelField", - "condition": "{{ $self.value === 'calendar' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: "value:schema", - target: "startDateField", - "condition": "{{ $self.value === 'calendar' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: "value:schema", - target: "endDateField", - "condition": "{{ $self.value === 'calendar' }}", - schema: { - "x-component-props": { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - "type": "value:visible", - "target": "labelField", - "condition": "{{ $self.value === 'calendar' }}", - }, - { - "type": "value:visible", - "target": "startDateField", - "condition": "{{ $self.value === 'calendar' }}", - }, - { - "type": "value:visible", - "target": "endDateField", - "condition": "{{ $self.value === 'calendar' }}", - }, - ], - }, - }, - { - interface: 'select', - type: 'virtual', - title: '标题字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - mode: 'simple', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - }, - { - interface: 'select', - type: 'virtual', - title: '开始日期字段', - name: 'startDateField', - // required: true, - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - placeholder: '默认为创建时间字段', - mode: 'simple', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - }, - { - interface: 'select', - type: 'virtual', - title: '结束日期字段', - name: 'endDateField', - // required: true, - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - placeholder: '默认为创建时间字段', - mode: 'simple', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - }, - { - interface: 'json', - type: 'json', - name: 'filter', - title: '筛选数据', - developerMode: false, - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - showInForm: true, - }, - }, - { - interface: 'radio', - type: 'string', - name: 'mode', - title: '查看和编辑模式', - required: true, - dataSource: [ - { label: '常规模式', value: 'default' }, - { label: '快捷模式', value: 'simple' }, - ], - component: { - tooltip: "常规模式:点击数据进入查看界面,再次点击进入编辑界面
快捷模式:点击数据直接打开编辑界面", - type: 'radio', - default: 'default', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'select', - type: 'string', - name: 'template', - title: '模板', - required: true, - developerMode: true, - dataSource: [ - { label: '表单', value: 'DrawerForm' }, - { label: '常规表格', value: 'Table' }, - { label: '简易表格', value: 'SimpleTable' }, - { label: '日历模板', value: 'Calendar' }, - ], - component: { - type: 'select', - default: 'Table', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'radio', - type: 'virtual', - name: 'defaultPerPage', - title: '默认每页显示几行数据', - defaultValue: 50, - dataSource: [ - { label: '10', value: 10 }, - { label: '20', value: 20 }, - { label: '50', value: 50 }, - { label: '100', value: 100 }, - ], - component: { - type: 'radio', - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'virtual', - name: 'draggable', - title: '支持拖拽数据排序', - showInForm: true, - showInDetail: true, - component: { - type: 'checkbox', - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'radio', - name: 'default', - title: '作为默认视图', - defaultValue: false, - scope: ['collection'], - component: { - type: 'checkbox', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'showInDataMenu', - title: '作为数据表子菜单', - defaultValue: false, - component: { - type: 'checkbox', - showInTable: true, - showInDetail: true, - showInForm: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - // 以下暂不考虑 - // { - // type: 'belongsToMany', - // name: 'fields', - // component: { - // type: 'drawerSelect', - // }, - // }, - // { - // type: 'belongsToMany', - // name: 'actions', - // component: { - // type: 'drawerSelect', - // }, - // }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - filter: { - default: false - } - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], - views: [ - { - type: 'form', - name: 'form', - title: '表单', - template: 'DrawerForm', - developerMode: true, - }, - { - type: 'details', - name: 'details', - title: '详情', - template: 'Details', - actionNames: ['update'], - developerMode: true, - }, - { - type: 'table', - name: 'simple', - title: '简易模式', - template: 'SimpleTable', - mode: 'simple', - default: true, - actionNames: ['destroy', 'create'], - detailsViewName: 'details', - updateViewName: 'form', - paginated: false, - draggable: true, - }, - ], -} as TableOptions; diff --git a/packages/plugin-collections-v04/src/hooks/collections-after-create.ts b/packages/plugin-collections-v04/src/hooks/collections-after-create.ts deleted file mode 100644 index b3f7ee134..000000000 --- a/packages/plugin-collections-v04/src/hooks/collections-after-create.ts +++ /dev/null @@ -1,42 +0,0 @@ -import CollectionModel from '../models/collection'; - -const defaultValues = { - actions: [ - { - type: 'filter', - name: 'filter', - title: '筛选', - }, - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - ], -}; - -export default async function (model: CollectionModel, options: any = {}) { - const { migrate = true } = options; - console.log('plugin-collections hook', { migrate }) - if (migrate) { - await model.migrate({ ...options, isNewRecord: true }); - } - await model.updateAssociations(defaultValues, options); -} diff --git a/packages/plugin-collections-v04/src/hooks/collections-after-update.ts b/packages/plugin-collections-v04/src/hooks/collections-after-update.ts deleted file mode 100644 index e0c65c273..000000000 --- a/packages/plugin-collections-v04/src/hooks/collections-after-update.ts +++ /dev/null @@ -1,8 +0,0 @@ -import CollectionModel from '../models/collection'; - -export default async function (model: CollectionModel, options: any = {}) { - const { migrate = true } = options; - if (migrate) { - await model.migrate(options); - } -} diff --git a/packages/plugin-collections-v04/src/hooks/collections-before-validate.ts b/packages/plugin-collections-v04/src/hooks/collections-before-validate.ts deleted file mode 100644 index 62358aca5..000000000 --- a/packages/plugin-collections-v04/src/hooks/collections-before-validate.ts +++ /dev/null @@ -1,5 +0,0 @@ -import CollectionModel from '../models/collection'; - -export default async function (model: CollectionModel) { - model.generateNameIfNull(); -} diff --git a/packages/plugin-collections-v04/src/hooks/fields-after-bulk-update.ts b/packages/plugin-collections-v04/src/hooks/fields-after-bulk-update.ts deleted file mode 100644 index 6c897ac95..000000000 --- a/packages/plugin-collections-v04/src/hooks/fields-after-bulk-update.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; - -/** - * 字段导入是先 create 再 bulk update 处理 fk 的 - * - * @param options - */ -export default async function (options: any = {}) { - const { migrate = true, where = {}, attributes: { collection_name }, transaction } = options; - if (migrate && collection_name) { - const Field = this.database.getModel('fields') as ModelCtor; - const fields = await Field.findAll({ - where, - transaction, - }); - for (const field of fields) { - await field.migrate(options); - } - } -} diff --git a/packages/plugin-collections-v04/src/hooks/fields-after-create.ts b/packages/plugin-collections-v04/src/hooks/fields-after-create.ts deleted file mode 100644 index 715917a2a..000000000 --- a/packages/plugin-collections-v04/src/hooks/fields-after-create.ts +++ /dev/null @@ -1,21 +0,0 @@ -import FieldModel from '../models/field'; -import { BELONGSTO, BELONGSTOMANY, HASMANY } from '@nocobase/database'; - -export default async function (model: FieldModel, options: any = {}) { - const { migrate = true } = options; - const Collection = model.database.getModel('collections'); - if (model.get('interface') === 'subTable') { - const target = model.get('target'); - if (target && !model.database.isDefined(target)) { - await Collection.import({ - name: target, - internal: true, - developerMode: true, - }, options); - } - } - if (migrate) { - await model.migrate(options); - } - await model.generatePairField(options); -} diff --git a/packages/plugin-collections-v04/src/hooks/fields-after-update.ts b/packages/plugin-collections-v04/src/hooks/fields-after-update.ts deleted file mode 100644 index b5c44eb64..000000000 --- a/packages/plugin-collections-v04/src/hooks/fields-after-update.ts +++ /dev/null @@ -1,9 +0,0 @@ -import FieldModel from '../models/field'; - -export default async function (model: FieldModel, options: any = {}) { - const { migrate = true } = options; - if (migrate) { - await model.migrate(options); - } - await model.generatePairField(options); -} diff --git a/packages/plugin-collections-v04/src/hooks/fields-before-validate.ts b/packages/plugin-collections-v04/src/hooks/fields-before-validate.ts deleted file mode 100644 index b986a59dc..000000000 --- a/packages/plugin-collections-v04/src/hooks/fields-before-validate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import FieldModel, { generateValueName } from '../models/field'; -import _ from 'lodash'; - -export default async function (model: FieldModel, options) { - // 生成随机 name 要放最后 - // model.generateNameIfNull(); - // 如果 collection_name 不存在 - if (!model.get('collection_name') && model.get('parent_id')) { - const parent = await model.getParent({ - ...options, - }); - const target = parent.get('target'); - if (target) { - model.set('collection_name', target); - } - } - - const dataSource = model.get('dataSource'); - if (Array.isArray(dataSource)) { - model.set('dataSource', dataSource.map(item => { - if (item.value === null || typeof item.value === 'undefined') { - item.value = generateValueName(); - } - return { ...item }; - })); - } -} diff --git a/packages/plugin-collections-v04/src/hooks/generateName.ts b/packages/plugin-collections-v04/src/hooks/generateName.ts deleted file mode 100644 index 9e9f47cc2..000000000 --- a/packages/plugin-collections-v04/src/hooks/generateName.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseModel } from '../models'; - -export default async function (model: BaseModel) { - model.generateNameIfNull(); -} diff --git a/packages/plugin-collections-v04/src/hooks/index.ts b/packages/plugin-collections-v04/src/hooks/index.ts deleted file mode 100644 index 7bec49083..000000000 --- a/packages/plugin-collections-v04/src/hooks/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import collectionsBeforeValidate from './collections-before-validate'; -import collectionsAfterCreate from './collections-after-create'; -import collectionsAfterUpdate from './collections-after-update'; - -import fieldsBeforeValidate from './fields-before-validate'; -import fieldsAfterCreate from './fields-after-create'; -import fieldsAfterBulkUpdate from './fields-after-bulk-update'; -import fieldsAfterUpdate from './fields-after-update'; - -import generateName from './generateName'; - -export default { - collections: { - beforeValidate: collectionsBeforeValidate, - afterCreate: collectionsAfterCreate, - afterUpdate: collectionsAfterUpdate, - }, - fields: { - beforeValidate: fieldsBeforeValidate, - afterCreate: fieldsAfterCreate, - afterUpdate: fieldsAfterUpdate, - afterBulkUpdate: fieldsAfterBulkUpdate, - }, - actions: { - beforeValidate: generateName - }, - views: { - beforeValidate: generateName - }, - tabs: { - beforeValidate: generateName - }, -}; diff --git a/packages/plugin-collections-v04/src/interfaces/index.ts b/packages/plugin-collections-v04/src/interfaces/index.ts deleted file mode 100644 index 9909dd66a..000000000 --- a/packages/plugin-collections-v04/src/interfaces/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 考虑到 Interface 的参数模板还不固定,暂时先放这里了,便于后续修改 - */ -import * as types from './types'; -export * as types from './types'; - -export const groupLabelMap = { - basic: '基本类型', - media: '多媒体类型', - choices: '选择类型', - datetime: '日期和时间', - relation: '关系类型', - systemInfo: '关系类型', - developerMode: '开发者模式', - others: '其他' -}; - -export function getOptions() { - return Object.keys(groupLabelMap).map(key => ({ - key, - label: groupLabelMap[key], - children: Object.values(types) - .filter(type => type['group'] === key) - .map(type => ({ - label: type.title, - value: type.options.interface, - // TODO(draft): 配置信息一并存到数据库方便字段配置时取出参与联动计算 - // properties: type.properties, - disabled: type['disabled'], - })) - })); -} - -export type interfaceType = { - title: string, - group?: string, - options: { - [key: string]: any - }, - disabled?: boolean -}; - -// TODO(draft) -// 目前仅在内存中注册,应用启动时需要解决扩展字段读取并注册到内存 -export function register(type: interfaceType) { - types[type.options.interface] = type; -} diff --git a/packages/plugin-collections-v04/src/interfaces/types.ts b/packages/plugin-collections-v04/src/interfaces/types.ts deleted file mode 100644 index 14ed285cf..000000000 --- a/packages/plugin-collections-v04/src/interfaces/types.ts +++ /dev/null @@ -1,638 +0,0 @@ -// merge:interface 模板,旧数据,用户数据 -// TODO: 删除的情况怎么处理 -// 联动的原则:尽量减少干预,尤其是尽量少改动 type,type 兼容 -// 参数的优先级: -// 1、interface,type 尽量只随 interface 变动,而不受别的字段影响(特殊情况除外) -// 2、 -// TODO: interface 的修改 -export const string = { - title: '单行文本', - group: 'basic', - options: { - interface: 'string', - type: 'string', - filterable: true, - component: { - type: 'string', - }, - }, -}; - -export const textarea = { - title: '多行文本', - group: 'basic', - options: { - interface: 'textarea', - type: 'text', - filterable: true, - component: { - type: 'textarea', - }, - } -}; - -export const phone = { - title: '手机号码', - group: 'basic', - options: { - interface: 'phone', - type: 'string', - filterable: true, - format: 'phone', // 验证的问题 - component: { - type: 'string', - 'x-rules': 'phone', - }, - }, -}; - -export const email = { - title: '邮箱', - group: 'basic', - options: { - interface: 'email', - type: 'string', - filterable: true, - format: 'email', - component: { - type: 'string', - 'x-rules': 'email', - }, - }, -}; - -/** - * 通过 precision 控制精确度 - */ -export const number = { - title: '数字', - group: 'basic', - options: { - interface: 'number', - type: 'float', - filterable: true, - sortable: true, - precision: 0, // 需要考虑 - component: { - type: 'number', - }, - } -}; - -/** - * 通过 precision 控制精确度 - * 百分比转化是前端处理还是后端处理 - */ -export const percent = { - title: '百分比', - group: 'basic', - options: { - interface: 'percent', - type: 'float', - filterable: true, - sortable: true, - precision: 0, - component: { - type: 'percent', - }, - }, -}; - -export const markdown = { - title: 'Markdown', - group: 'media', - options: { - interface: 'markdown', - type: 'json', - component: { - type: 'markdown', - }, - }, -}; - -export const wysiwyg = { - title: '可视化编辑器', - group: 'media', - disabled: true, - options: { - interface: 'wysiwyg', - type: 'json', - component: { - type: 'wysiwyg', - }, - }, -}; - -/** - * 特殊的关系字段 - */ -export const attachment = { - title: '附件', - group: 'media', - // disabled: true, - options: { - interface: 'attachment', - type: 'belongsToMany', - filterable: false, - target: 'attachments', - // storage: { - // name: 'local', - // }, - component: { - type: 'upload', - }, - }, -}; - -/** - * - */ -export const select = { - title: '下拉选择(单选)', - group: 'choices', - options: { - interface: 'select', - type: 'string', - filterable: true, - dataSource: [], - component: { - type: 'select', - }, - }, -}; - -/** - * type 怎么处理 - * 暂时 json 处理 - * 后续:扩展 type=array 的字段 - * array 的情况怎么兼容 - * filter 要处理 - * 不能处理 json 搜索的数据库可以用 hasMany 转化 - * - * 思考:🤔 如果 select合并成一个 interface,multiple 会影响 type - */ -export const multipleSelect = { - title: '下拉选择(多选)', - group: 'choices', - options: { - interface: 'multipleSelect', - type: 'json', // json 过滤 - filterable: true, - dataSource: [], - defaultValue: [], - multiple: true, // 需要重点考虑 - component: { - type: 'select', - }, - }, -}; - -export const radio = { - title: '单选框', - group: 'choices', - options: { - interface: 'radio', - type: 'string', - filterable: true, - dataSource: [], - component: { - type: 'radio', - }, - }, -}; - -export const checkboxes = { - title: '多选框', - group: 'choices', - options: { - interface: 'checkboxes', - type: 'json', - filterable: true, - dataSource: [], - defaultValue: [], - component: { - type: 'checkboxes', - }, - }, -}; - -export const boolean = { - title: '是/否', - group: 'choices', - options: { - interface: 'boolean', - type: 'boolean', - filterable: true, - component: { - type: 'checkbox', // switch - }, - }, -}; - -/** - * dateonly 要不要变 type - * 如果是 dateonly 时间怎么办? - */ -export const datetime = { - title: '日期', - group: 'datetime', - options: { - interface: 'datetime', - type: 'date', - showTime: false, - filterable: true, - sortable: true, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - component: { - type: 'date', - }, - }, - // TODO(draft): 配置参数的一种描述方式 - // properties: [ - // { - // name: 'showTime', - // title: '显示时间', - // interface: 'boolean', - // component: { - // 'x-linkages': [ - // { - // type: 'value:visible', - // target: 'timeFormat', - // condition: '{{ $value }}' - // } - // ] - // } - // }, - // { - // interface: 'string', - // name: 'timeFormat', - // title: '时间格式', - // filterable: false - // } - // ] -}; - -export const time = { - title: '时间', - group: 'datetime', - options: { - interface: 'time', - type: 'time', - filterable: true, - sortable: true, - timeFormat: 'HH:mm:ss', - component: { - type: 'time', - }, - }, -}; - -/** - * 重点: - * 初始化子表和子字段 - * hasMany 相关的设置参数 - * fields 是子字段 - * - * 分组字段 - virtual:不考虑字段分组 - * 子表格 - hasMany - * - 子字段只属于子表格字段关联的表(target),不属于当前表(source) - */ -// database.table({ -// name: 'tablename', -// fields: [ -// { -// type: 'hasMany', -// name: 'foos', -// target: 'foos', -// fields: [ -// { -// type: 'string', -// name: 'xxx', -// } -// ], -// } -// ], -// }); -// database.table({ -// name: 'foos', -// fields: [ -// { -// type: 'string', -// name: 'xxx', -// } -// ], -// }); -export const subTable = { - title: '子表格', - group: 'relation', - // disabled: true, - options: { - interface: 'subTable', - type: 'hasMany', - // fields: [], - component: { - type: 'subTable', - }, - }, -}; - -/** - * 尽量减少更新 multiple 造成的影响 - * 同步生成配对的关系字段 - * - * 只传 name 没有 target,可以通过 addField 处理,找到 target - * 没有 name 但是有 target,name 随机生成 - * 有 name 也有 target - */ - -// database.table({ -// name: 'foos', -// fields: [ -// { -// type: 'hasMany', -// name: 'bars', -// // target: 'bars', -// // sourceKey: 'id', -// // foreignKey: 'foo_id', -// }, -// { -// type: 'hasMany', -// name: 'xxxxx', // 如果没有随机生成 -// target: 'bars', -// // sourceKey: 'id', -// // foreignKey: 'foo_id', -// }, -// { -// type: 'hasMany', -// name: 'xxxxx', // 如果没有随机生成 -// target: 'bars', -// sourceKey: 'id', -// foreignKey: 'foo_id', -// } -// ], -// }); - -// const field = table.addField({ -// type: 'hasMany', -// name: 'xxx', // xxx -// }); - -export const linkTo = { - title: '关联数据', - group: 'relation', - // disabled: true, - options: { - interface: 'linkTo', - multiple: true, // 可能影响 type - paired: false, - type: 'belongsToMany', - // name, - // target: '关联表', // 用户会输入 - filterable: false, - component: { - type: 'drawerSelect', - }, - }, -}; - -export const createdAt = { - title: '创建时间', - group: 'systemInfo', - options: { - interface: 'createdAt', - type: 'date', - // name: 'created_at', - field: 'created_at', - showTime: false, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - required: true, - filterable: true, - sortable: true, - component: { - type: 'date', - }, - }, -}; - -export const updatedAt = { - title: '修改时间', - group: 'systemInfo', - options: { - interface: 'updatedAt', - type: 'date', - // name: 'updated_at', - field: 'updated_at', - showTime: false, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - required: true, - filterable: true, - sortable: true, - component: { - type: 'date', - }, - }, -}; - -export const createdBy = { - title: '创建人', - group: 'systemInfo', - // disabled: true, - options: { - interface: 'createdBy', - type: 'createdBy', - // name: 'createdBy', - // filterable: true, - target: 'users', - labelField: 'nickname', - foreignKey: 'created_by_id', - appends: true, - component: { - type: 'drawerSelect', - }, - }, -}; - -export const updatedBy = { - title: '修改人', - group: 'systemInfo', - // disabled: true, - options: { - interface: 'updatedBy', - // name: 'updatedBy', - type: 'updatedBy', - // filterable: true, - target: 'users', - labelField: 'nickname', - foreignKey: 'updated_by_id', - appends: true, - component: { - type: 'drawerSelect', - }, - }, -}; - -/** - * 字段分组(暂缓) - * - * 影响数据输出结构,树形结构输出 - */ -export const group = { - title: '字段组', - disabled: true, - options: { - interface: 'group', - // name: 'id', - type: 'virtual', - component: { - type: 'hidden', - }, - }, -}; - -export const status = { - title: '状态', - group: 'others', - options: { - interface: 'status', - name: 'status', - type: 'string', - filterable: true, - // index: true, - dataSource: [ - { - label: '已发布', - value: 'publish', - }, - { - label: '草稿', - value: 'draft', - } - ], - component: { - type: 'select', - }, - }, -} - -export const description = { - title: '说明文字', - group: 'others', - options: { - interface: 'description', - // name: 'id', - type: 'virtual', - component: { - type: 'description', - }, - }, -} - -/** - * 主键(暂缓) - */ -export const primaryKey = { - title: '主键', - group: 'developerMode', - options: { - interface: 'primaryKey', - name: 'id', - type: 'integer', - required: true, - autoIncrement: true, - primaryKey: true, - filterable: true, - developerMode: true, - component: { - type: 'number', - }, - }, -}; - -/** - * 自增长 - * scope 的问题 - */ -export const sort = { - title: '排序', - group: 'developerMode', - options: { - interface: 'sort', - type: 'integer', - required: true, - component: { - type: 'sort', - showInTable: true, - }, - }, -}; - -export const password = { - title: '密码', - group: 'developerMode', - options: { - interface: 'password', - type: 'password', - hidden: true, // hidden 用来控制 api 不输出这个字段,但是可能这个字段显示在表单里 showInForm - component: { - type: 'password', - }, - }, -}; - -export const json = { - title: 'JSON', - group: 'developerMode', - options: { - interface: 'json', - type: 'json', - mode: 'replace', - // developerMode: true, - component: { - type: 'hidden', - }, - }, -}; - -export const icon = { - title: '图标', - group: 'developerMode', - options: { - interface: 'icon', - type: 'string', - component: { - type: 'icon', - }, - }, -}; - -export const chinaRegion = { - title: '中国行政区划', - group: 'choices', - options: { - interface: 'chinaRegion', - type: 'belongsToMany', - // 数据来源的数据表,与 dataSource 不同,需要从表数据加载后转化成 dataSource - target: 'china_regions', - targetKey: 'code', - // 值字段 - // valueField: 'code', - // 名称字段 - labelField: 'name', - // TODO(refactor): 等 toWhere 重构完成后要改成 parent - // 上级字段名 - parentField: 'parent_code', - // 深度限制,默认:-1(代表不控制,即如果是数据表,则无限加载) - // limit: -1, - // 可选层级,默认:-1(代表可选的最深层级) - // maxLevel: null, - // 是否可以不选择到最深一级 - // 'x-component-props': { changeOnSelect: true } - incompletely: false, - component: { - type: 'cascader', - } - } -}; diff --git a/packages/plugin-collections-v04/src/models/action.ts b/packages/plugin-collections-v04/src/models/action.ts deleted file mode 100644 index ffd3c4235..000000000 --- a/packages/plugin-collections-v04/src/models/action.ts +++ /dev/null @@ -1,6 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; - -export class ActionModel extends BaseModel { - -} diff --git a/packages/plugin-collections-v04/src/models/base.ts b/packages/plugin-collections-v04/src/models/base.ts deleted file mode 100644 index a6e42652a..000000000 --- a/packages/plugin-collections-v04/src/models/base.ts +++ /dev/null @@ -1,142 +0,0 @@ -import _ from 'lodash'; -import { getDataTypeKey, Model } from '@nocobase/database'; -import { merge } from '../utils'; - -export function generateName(title?: string): string { - return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; -} - -export class BaseModel extends Model { - - generateName() { - this.set('name', generateName()); - } - - generateNameIfNull() { - if (!this.get('name')) { - this.generateName(); - } - } - - get additionalAttribute() { - const tableOptions = this.database.getTable(this.constructor.name).getOptions(); - return _.get(tableOptions, 'additionalAttribute') || 'options'; - } - - hasGetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 get 方法就直接走 get - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.get; - } - return !!attribute; - } - - hasSetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 set 方法就直接走 set - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.set; - } - return !!attribute; - } - - get(key?: any, options?: any) { - if (typeof key === 'string') { - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.get(column, options); - if (path.length) { - return _.get(value, path); - } - return value; - } - return _.get(super.get(this.additionalAttribute, options) || {}, key); - } - const data = super.get(); - return { - ...(data[this.additionalAttribute] || {}), - ..._.omit(data, [this.additionalAttribute]), - }; - } - - getDataValue(key: any) { - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.getDataValue(column); - if (path.length) { - return _.get(value, path); - } - return value; - } - const options = super.getDataValue(this.additionalAttribute) || {}; - return _.get(options, key); - } - - set(key?: any, value?: any, options: any = {}) { - if (typeof key === 'string') { - // 不处理关系数据 - // @ts-ignore - if (_.get(this.constructor.associations, key)) { - return super.set(key, value, options); - } - // 如果是 object 数据,merge 处理 - if (_.isPlainObject(value)) { - // TODO 需要改进 JSON 字段的内部处理逻辑,暂时这里跳过了特殊的 filter 字段 - if (key !== 'filter') { - // console.log(key, value); - // @ts-ignore - value = merge(this.get(key) || {}, value); - } - } - const [column, ...path] = key.split('.'); - if (!options.raw) { - this.changed(column, true); - } - if (this.hasSetAttribute(column)) { - if (!path.length) { - return super.set(key, value, options); - } - const values = this.get(column, options) || {}; - _.set(values, path, value); - return super.set(column, values, options); - } - // 如果未设置 attribute,存到 additionalAttribute 里 - const opts = this.get(this.additionalAttribute, options) || {}; - _.set(opts, key, value); - if (!options.raw) { - this.changed(this.additionalAttribute, true); - } - return super.set(this.additionalAttribute, opts, options); - } - return super.set(key, value, options); - } - - setDataValue(key: any, value: any) { - // 不处理关系数据 - // @ts-ignore - if (_.get(this.constructor.associations, key)) { - return super.setDataValue(key, value); - } - if (_.isPlainObject(value)) { - // @ts-ignore - value = Utils.merge(this.get(key) || {}, value); - } - const [column, ...path] = key.split('.'); - this.changed(column, true); - if (this.hasSetAttribute(column)) { - if (!path.length) { - return super.setDataValue(key, value); - } - const values = this.get(column) || {}; - _.set(values, path, value); - return super.setDataValue(column, values); - } - const opts = this.get(this.additionalAttribute) || {}; - _.set(opts, key, value); - this.changed(this.additionalAttribute, true); - return super.setDataValue(this.additionalAttribute, opts); - } -} - -export default BaseModel; diff --git a/packages/plugin-collections-v04/src/models/collection.ts b/packages/plugin-collections-v04/src/models/collection.ts deleted file mode 100644 index ec692dffa..000000000 --- a/packages/plugin-collections-v04/src/models/collection.ts +++ /dev/null @@ -1,241 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; -import Field from './field'; -import { TableOptions } from '@nocobase/database'; -import { SaveOptions, Op } 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 interface LoadOptions { - reset?: boolean; - where?: any; - skipExisting?: boolean; - [key: string]: any; -} - -export interface MigrateOptions { - [key: string]: any; -} - -export class CollectionModel extends BaseModel { - - generateName() { - this.set('name', generateCollectionName()); - } - - /** - * 通过 name 获取 collection - * - * @param name - */ - static async findByName(name: string) { - return this.findOne({ where: { name } }); - } - - /** - * DOTO: - * - database.table 初始化可能存在一些缺陷 - * - 是否需要考虑关系数据的重载? - * - * @param opts - */ - async loadTableOptions(opts: any = {}) { - const options = await this.getOptions(); - // const prevTable = this.database.getTable(this.get('name')); - // const prevOptions = prevTable ? prevTable.getOptions() : {}; - // table 是初始化和重新初始化 - const table = this.database.table(options); - // console.log({options, actions: table.getOptions()['actions']}) - - // 如果关系表未加载,一起处理 - // const associationTableNames = []; - // for (const [key, association] of table.getAssociations()) { - // // TODO:是否需要考虑重载的情况?(暂时是跳过处理) - // if (!this.database.isDefined(association.options.target)) { - // continue; - // } - // associationTableNames.push(association.options.target); - // } - // console.log({associationTableNames}); - // if (associationTableNames.length) { - // await CollectionModel.load({ - // ...opts, - // where: { - // name: { - // [Op.in]: associationTableNames, - // } - // } - // }); - // } - return table; - } - - /** - * 迁移 - */ - async migrate(options: MigrateOptions = {}) { - const { isNewRecord } = options; - const table = await this.loadTableOptions(options); - // 如果不是新增数据,force 必须为 false - if (!isNewRecord) { - return await table.sync({ - force: false, - alter: { - drop: false, - } - }); - } - // TODO: 暂时加了个 collectionSync 解决 collection.create 的数据不清空问题 - // @ts-ignore - const sync = this.sequelize.options.collectionSync; - return await table.sync(sync || { - force: false, - alter: { - drop: false, - } - }); - } - - async getFieldsOptions() { - const fieldsOptions = []; - const fields = await this.getFields(); - for (const field of fields) { - fieldsOptions.push(await field.getOptions()); - } - return fieldsOptions; - } - - async getOptions(): Promise { - const options: any = { - ...this.get(), - actions: await this.getActions(), - fields: await this.getFieldsOptions(), - } - // @ts-ignore - // console.log(this.constructor.associations); - // @ts-ignore - if (this.constructor.hasAlias('views_v2')) { - options.views_v2 = await this.getViews_v2(); - } - return options; - } - - /** - * TODO:需要考虑是初次加载还是重载 - * - * @param options - */ - static async load(options: LoadOptions = {}) { - const { skipExisting = false, reset = false, where = {}, transaction } = options; - const collections = await this.findAll({ - transaction, - where, - }); - for (const collection of collections) { - if (skipExisting && this.database.isDefined(collection.get('name'))) { - continue; - } - await collection.loadTableOptions({ - transaction, - reset, - }); - } - } - - static async import(data: TableOptions, options: SaveOptions = {}): Promise { - data = _.cloneDeep(data); - // @ts-ignore - const { update } = options; - let collection: CollectionModel; - if (data.name) { - collection = await this.findOne({ - ...options, - where: { - name: data.name, - }, - }); - } - if (collection) { - // @ts-ignore - await collection.update(data, options); - } - if (!collection) { - // @ts-ignore - collection = await this.create(data, options); - } - - const associations = ['fields', 'actions', 'views_v2']; - for (const key of associations) { - if (!Array.isArray(data[key])) { - continue; - } - const Model = this.database.getModel(key); - if (!Model) { - continue; - } - let ids = []; - for (const index in data[key]) { - if (key === 'fields') { - ids = await Model.import(data[key], { - ...options, - collectionName: collection.name, - }); - continue; - } - let model; - const item = data[key][index]; - if (item.name) { - model = await Model.findOne({ - ...options, - where: { - collection_name: collection.name, - name: item.name, - }, - }); - } - if (model) { - await model.update({ - ...item, - // sort: index+1 - }, options); - } - if (!model) { - model = await Model.create( - { - ...item, - // sort: index+1, - collection_name: collection.name, - }, - // @ts-ignore - options - ); - } - if (model) { - ids.push(model.id); - } - } - if (ids.length && collection.get('internal')) { - await collection.updateAssociations({ - [key]: ids, - }); - } - } - return collection; - } -} - -export default CollectionModel; diff --git a/packages/plugin-collections-v04/src/models/field.ts b/packages/plugin-collections-v04/src/models/field.ts deleted file mode 100644 index dc4271b46..000000000 --- a/packages/plugin-collections-v04/src/models/field.ts +++ /dev/null @@ -1,263 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; -import { FieldOptions, BELONGSTO, BELONGSTOMANY, HASMANY } from '@nocobase/database'; -import * as types from '../interfaces/types'; -import { merge } from '../utils'; -import { BuildOptions } from 'sequelize'; -import { SaveOptions, Utils } from 'sequelize'; -import { generateCollectionName } from './collection'; - -interface FieldImportOptions extends SaveOptions { - parentId?: number; - collectionName?: string; -} - -export function generateValueName(title?: string): string { - return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; -} - -export function generateFieldName(title?: string): string { - return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; -} - -export class FieldModel extends BaseModel { - - constructor(values: any = {}, options: any = {}) { - let data = { - ...(values.options || {}), - ...values, - // ..._.omit(values, 'options'), - }; - const interfaceType = data.interface; - if (interfaceType) { - const { options } = types[interfaceType]; - let args = [options, data]; - // @ts-ignore - data = merge(...args); - if (['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(data.type)) { - // 关系字段如果没有 name,相关参数都随机生成 - if (!data.name) { - data.name = generateFieldName(); - data.paired = true; - // 通用,关系表 - if (!data.target) { - data.target = generateCollectionName(); - } - // 通用,外键 - if (!data.foreignKey) { - data.foreignKey = generateFieldName(); - } - if (data.type !== 'belongsTo' && !data.sourceKey) { - data.sourceKey = 'id'; - } - if (['belongsTo', 'belongsToMany'].includes(data.type) && !data.targetKey) { - data.targetKey = 'id'; - } - // 多对多关联 - if (data.type === 'belongsToMany') { - if (!data.through) { - data.through = generateCollectionName(); - } - if (!data.otherKey) { - data.otherKey = generateFieldName(); - } - } - } - // 有 name,但是没有 target - if (!data.target) { - data.target = ['hasOne', 'belongsTo'].includes(data.type) ? Utils.pluralize(data.name) : data.name; - } - } - if (!data.name) { - data.name = generateFieldName(); - } - } - // @ts-ignore - super(data, options); - } - - generateName() { - this.set('name', generateFieldName()); - } - - async generatePairField(options) { - const { interface: control, paired, type, target, sourceKey, targetKey, foreignKey, otherKey, through, collection_name } = this.get(); - if (control !== 'linkTo' || type !== 'belongsToMany' || !collection_name || !paired) { - return; - } - if (!this.database.isDefined(target)) { - return; - } - const targetTable = this.database.getTable(target); - const Field = FieldModel; - let labelField = 'id'; - const targetField = await Field.findOne({ - ...options, - where: { - type: 'string', - collection_name: target, - }, - order: [['sort', 'asc']], - }); - if (targetField) { - labelField = targetField.get('name'); - } - const collection = await this.getCollection(options); - let targetOptions: any = { - ...types.linkTo.options, - interface: 'linkTo', - title: collection.get('title'), - collection_name: target, - options: { - paired: true, - target: collection_name, - labelField, - }, - component: { - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }; - // 暂时不处理 hasone - switch (type) { - case 'hasMany': - targetOptions.type = 'belongsTo'; - targetOptions.options.targetKey = sourceKey; - targetOptions.options.foreignKey = foreignKey; - break; - case 'belongsTo': - targetOptions.type = 'hasMany'; - targetOptions.options.sourceKey = targetKey; - targetOptions.options.foreignKey = foreignKey; - break; - case 'belongsToMany': - targetOptions.type = 'belongsToMany'; - targetOptions.options.sourceKey = targetKey; - targetOptions.options.foreignKey = otherKey; - targetOptions.options.targetKey = sourceKey; - targetOptions.options.otherKey = foreignKey; - targetOptions.options.through = through; - break; - } - const associations = targetTable.getAssociations(); - // console.log(associations); - for (const association of associations.values()) { - if (association instanceof BELONGSTOMANY) { - if ( - association.options.foreignKey === otherKey - && association.options.sourceKey === targetKey - && association.options.otherKey === foreignKey - && association.options.targetKey === sourceKey - && association.options.through === through - ) { - return; - } - } - // if (association instanceof BELONGSTO) { - // continue; - // } - // if (association instanceof HASMANY) { - // continue; - // } - } - const f = await Field.create(targetOptions, options); - // console.log({targetOptions}, f.get('options')); - } - - setInterface(value) { - const { options } = types[value]; - let args = []; - // 如果是新数据或 interface 不相等,interface options 放后 - if (this.isNewRecord || this.get('interface') !== value) { - args = [this.get(), options]; - } else { - // 已存在的数据更新,不相等,interface options 放前面 - args = [options, this.get()]; - } - // @ts-ignore - const values = merge(...args); - this.set(values); - } - - async getOptions(): Promise { - return this.get(); - } - - async migrate(options: any = {}) { - const collectionName = this.get('collection_name'); - if (!collectionName) { - return false; - } - if (!this.database.isDefined(collectionName)) { - throw new Error(`${collectionName} is not defined`); - } - const table = this.database.getTable(collectionName); - table.addField(await this.getOptions()); - await table.sync({ - force: false, - alter: { - drop: false, - } - }); - } - - static async import(items: any, options: FieldImportOptions = {}): Promise { - const { parentId, collectionName } = options; - if (!Array.isArray(items)) { - items = [items]; - } - const ids = []; - for (const index in items) { - const item = items[index]; - let model; - const where: any = {}; - if (parentId) { - where.parent_id = parentId - } else { - where.collection_name = collectionName; - } - if (item.name) { - model = await this.findOne({ - ...options, - where: { - ...where, - name: item.name, - }, - }); - } - if (!model) { - const tmp: any = {}; - if (parentId) { - tmp.parent_id = parentId - } else { - tmp.collection_name = collectionName; - } - model = await this.create( - { - ...item, - ...tmp, - }, - //@ts-ignore - options - ); - } else { - //@ts-ignore - await model.update(item, options); - } - if (Array.isArray(item.children)) { - const childrenIds = await this.import(item.children, { - ...options, - parentId: model.id, - collectionName, - }); - await model.updateAssociations({ - children: childrenIds, - }, options); - } - } - return ids; - } -} - -export default FieldModel; diff --git a/packages/plugin-collections-v04/src/models/index.ts b/packages/plugin-collections-v04/src/models/index.ts deleted file mode 100644 index 91a4aef21..000000000 --- a/packages/plugin-collections-v04/src/models/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './base'; -export * from './action'; -export * from './collection'; -export * from './field'; -export * from './tab'; -export * from './view'; -export * from './page'; diff --git a/packages/plugin-collections-v04/src/models/page.ts b/packages/plugin-collections-v04/src/models/page.ts deleted file mode 100644 index 4daea6999..000000000 --- a/packages/plugin-collections-v04/src/models/page.ts +++ /dev/null @@ -1,43 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; -import { SaveOptions, Op } from 'sequelize'; - -interface PageImportOptions extends SaveOptions { - parentId?: number; -} - -/** - * 暂时放在这里 - */ -export class PageModel extends BaseModel { - static async import(items: any, options: PageImportOptions = {}): Promise { - const { parentId } = options; - if (!Array.isArray(items)) { - items = [items]; - } - for (const item of items) { - let page = await this.findOne({ - ...options, - where: { - path: item.path, - }, - }); - if (!page) { - page = await this.create( - { - ...item, - parent_id: parentId, - }, - // @ts-ignore - options - ); - } - if (Array.isArray(item.children)) { - await this.import(item.children, { - ...options, - parentId: page.id, - }); - } - } - } -} diff --git a/packages/plugin-collections-v04/src/models/tab.ts b/packages/plugin-collections-v04/src/models/tab.ts deleted file mode 100644 index e3e2df774..000000000 --- a/packages/plugin-collections-v04/src/models/tab.ts +++ /dev/null @@ -1,6 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; - -export class TabModel extends BaseModel { - -} diff --git a/packages/plugin-collections-v04/src/models/view.ts b/packages/plugin-collections-v04/src/models/view.ts deleted file mode 100644 index 4822adbb4..000000000 --- a/packages/plugin-collections-v04/src/models/view.ts +++ /dev/null @@ -1,6 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; - -export class ViewModel extends BaseModel { - -} diff --git a/packages/plugin-collections-v04/src/server.ts b/packages/plugin-collections-v04/src/server.ts deleted file mode 100644 index 82e94e24f..000000000 --- a/packages/plugin-collections-v04/src/server.ts +++ /dev/null @@ -1,77 +0,0 @@ -import path from 'path'; -import { Application } from '@nocobase/server'; -import hooks from './hooks'; -import { registerModels, Table } from '@nocobase/database'; -import * as models from './models'; - -export default async function (this: Application, options = {}) { - const database = this.database; - const resourcer = this.resourcer; - // 提供全局的 models 注册机制 - registerModels(models); - - database.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - database.addHook('afterUpdateAssociations', async function (model, options) { - if (model instanceof models.FieldModel) { - if (model.get('interface') === 'subTable') { - const { migrate = true } = options; - const Collection = model.database.getModel('collections'); - await Collection.load({ ...options, where: { name: model.get('collection_name') } }); - migrate && await model.migrate(options); - } - } - }); - - Object.keys(hooks).forEach(modelName => { - const Model = database.getModel(modelName); - Object.keys(hooks[modelName]).forEach(hookKey => { - // TODO(types): 多层 map 映射类型定义较为复杂,暂时忽略 - // @ts-ignore - Model.addHook(hookKey, hooks[modelName][hookKey]); - }); - }); - - const Collection = database.getModel('collections'); - Collection.addHook('afterCreate', async (model: any, options) => { - if (model.get('developerMode')) { - return; - } - - if (model.get('statusable') === false) { - return; - } - - console.log("model.get('developerMode')", model.get('name')); - - const { transaction = await model.sequelize.transaction() } = options; - - await model.createField({ - interface: 'radio', - name: 'status', - type: 'string', - filterable: true, - title: '状态', - // index: true, - dataSource: [ - { - label: '已发布', - value: 'publish', - }, - { - label: '草稿', - value: 'draft', - } - ], - component: { - type: 'radio', - }, - }, { transaction }); - - if (!options.transaction) { - await transaction.commit(); - } - }); -} diff --git a/packages/plugin-collections/src/server.ts b/packages/plugin-collections/src/server.ts index df297c604..532bac938 100644 --- a/packages/plugin-collections/src/server.ts +++ b/packages/plugin-collections/src/server.ts @@ -7,16 +7,33 @@ import { create } from './actions/fields'; export default async function (this: Application, options = {}) { const database = this.database; + registerModels(models); + database.import({ directory: path.resolve(__dirname, 'collections'), }); - this.on('plugins.afterLoad', async () => { - console.log('plugins.afterLoad'); + + this.on('pluginsLoaded', async () => { + console.log('pluginsLoaded'); await database.getModel('collections').load(); }); + + this.on('collections.init', async () => { + const userTable = database.getTable('users'); + const config = userTable.getOptions(); + const Collection = database.getModel('collections'); + const collection = await Collection.create(config); + await collection.updateAssociations({ + generalFields: config.fields.filter((field) => field.state !== 0), + systemFields: config.fields.filter((field) => field.state === 0), + }); + await collection.migrate(); + }); + const [Collection, Field] = database.getModels(['collections', 'fields']); - Field.beforeCreate(async (model) => { + + database.on('fields.beforeCreate', async (model) => { if (!model.get('name')) { model.set('name', model.get('key')); } @@ -30,7 +47,8 @@ export default async function (this: Application, options = {}) { } } }); - Field.beforeUpdate(async (model) => { + + database.on('fields.beforeUpdate', async (model) => { console.log('beforeUpdate', model.key); if (!model.get('collection_name') && model.get('parentKey')) { const field = await Field.findByPk(model.get('parentKey')); @@ -42,7 +60,8 @@ export default async function (this: Application, options = {}) { } } }); - Field.afterCreate(async (model, options) => { + + database.on('fields.afterCreate', async (model) => { console.log('afterCreate', model.key, model.get('collection_name')); if (model.get('interface') !== 'subTable') { return; @@ -71,7 +90,8 @@ export default async function (this: Application, options = {}) { throw error; } }); - Field.afterUpdate(async (model) => { + + database.on('fields.afterUpdate', async (model) => { console.log('afterUpdate'); if (model.get('interface') !== 'subTable') { return; @@ -97,6 +117,7 @@ export default async function (this: Application, options = {}) { throw error; } }); + this.resourcer.registerActionHandler('collections.fields:create', create); this.resourcer.registerActionHandler('collections:findAll', findAll); this.resourcer.registerActionHandler('collections:createOrUpdate', createOrUpdate); diff --git a/packages/plugin-export/src/actions/export.ts b/packages/plugin-export/src/actions/export.ts index 521ab4760..f7852b841 100644 --- a/packages/plugin-export/src/actions/export.ts +++ b/packages/plugin-export/src/actions/export.ts @@ -1,8 +1,8 @@ import xlsx from 'node-xlsx'; -import { actions } from '@nocobase/actions'; +import { actions, Context, Next } from '@nocobase/actions'; import render from '../renders'; -async function _export(ctx: actions.Context, next: actions.Next) { +async function _export(ctx: Context, next: Next) { let { columns } = ctx.action.params; if (typeof columns === 'string') { columns = JSON.parse(columns); @@ -13,7 +13,7 @@ async function _export(ctx: actions.Context, next: actions.Next) { payload: 'replace', }); console.log({ columns }); - await actions.common.list(ctx, async () => { + await actions.list(ctx, async () => { const { db, action: { diff --git a/packages/plugin-export/src/server.ts b/packages/plugin-export/src/server.ts index 42c4db145..5999094a1 100644 --- a/packages/plugin-export/src/server.ts +++ b/packages/plugin-export/src/server.ts @@ -8,21 +8,21 @@ export default async function (options = {}) { resourcer.registerActionHandler(ACTION_NAME_EXPORT, _export); - // TODO(temp): 继承 list 权限的临时写法 - resourcer.use(async (ctx, next) => { - if (ctx.action.params.actionName === ACTION_NAME_EXPORT) { - ctx.action.mergeParams({ - actionName: 'list' - }); + // // TODO(temp): 继承 list 权限的临时写法 + // resourcer.use(async (ctx, next) => { + // if (ctx.action.params.actionName === ACTION_NAME_EXPORT) { + // ctx.action.mergeParams({ + // actionName: 'list' + // }); - console.log('action name in export has been rewritten to:', ctx.action.params.actionName); + // console.log('action name in export has been rewritten to:', ctx.action.params.actionName); - const permissionPlugin = ctx.app.getPluginInstance('@nocobase/plugin-permissions'); - if (permissionPlugin) { - return permissionPlugin.middleware(ctx, next); - } - } + // const permissionPlugin = ctx.app.getPluginInstance('@nocobase/plugin-permissions'); + // if (permissionPlugin) { + // return permissionPlugin.middleware(ctx, next); + // } + // } - await next(); - }); + // await next(); + // }); } diff --git a/packages/plugin-file-manager/src/actions/upload.ts b/packages/plugin-file-manager/src/actions/upload.ts index 64685cb23..6b29af05c 100644 --- a/packages/plugin-file-manager/src/actions/upload.ts +++ b/packages/plugin-file-manager/src/actions/upload.ts @@ -1,11 +1,11 @@ import path from 'path'; import multer from '@koa/multer'; -import actions from '@nocobase/actions'; +import { Context, Next } from '@nocobase/actions'; import storageMakers from '../storages'; import * as Rules from '../rules'; import { FILE_FIELD_NAME, LIMIT_FILES, LIMIT_MAX_FILE_SIZE } from '../constants'; -function getRules(ctx: actions.Context) { +function getRules(ctx: Context) { const { resourceField } = ctx.action.params; if (!resourceField) { return ctx.storage.rules; @@ -15,7 +15,7 @@ function getRules(ctx: actions.Context) { } // TODO(optimize): 需要优化错误处理,计算失败后需要抛出对应错误,以便程序处理 -function getFileFilter(ctx: actions.Context) { +function getFileFilter(ctx: Context) { return (req, file, cb) => { // size 交给 limits 处理 const { size, ...rules } = getRules(ctx); @@ -27,7 +27,7 @@ function getFileFilter(ctx: actions.Context) { } } -export async function middleware(ctx: actions.Context, next: actions.Next) { +export async function middleware(ctx: Context, next: Next) { const { resourceName, actionName, resourceField } = ctx.action.params; if (actionName !== 'upload') { return next(); @@ -79,7 +79,7 @@ export async function middleware(ctx: actions.Context, next: actions.Next) { return upload.single(FILE_FIELD_NAME)(ctx, next); }; -export async function action(ctx: actions.Context, next: actions.Next) { +export async function action(ctx: Context, next: Next) { const { [FILE_FIELD_NAME]: file, storage } = ctx; if (!file) { return ctx.throw(400, 'file validation failed'); diff --git a/packages/plugin-file-manager/src/server.ts b/packages/plugin-file-manager/src/server.ts index 34ebbedf9..a138becf7 100644 --- a/packages/plugin-file-manager/src/server.ts +++ b/packages/plugin-file-manager/src/server.ts @@ -22,4 +22,27 @@ export default async function () { resourcer.use(uploadMiddleware); resourcer.registerActionHandler('upload', uploadAction); localMiddleware(this); + + this.on('file-manager.init', async () => { + const Storage = database.getModel('storages'); + await Storage.create({ + title: '本地存储', + name: `local`, + type: 'local', + baseUrl: process.env.LOCAL_STORAGE_BASE_URL, + default: process.env.STORAGE_TYPE === 'local', + }); + await Storage.create({ + name: `ali-oss`, + type: 'ali-oss', + baseUrl: process.env.ALI_OSS_STORAGE_BASE_URL, + options: { + region: process.env.ALI_OSS_REGION, + accessKeyId: process.env.ALI_OSS_ACCESS_KEY_ID, + accessKeySecret: process.env.ALI_OSS_ACCESS_KEY_SECRET, + bucket: process.env.ALI_OSS_BUCKET, + }, + default: process.env.STORAGE_TYPE === 'ali-oss', + }); + }); } diff --git a/packages/plugin-full-collections/.npmignore b/packages/plugin-full-collections/.npmignore deleted file mode 100644 index 461574b2f..000000000 --- a/packages/plugin-full-collections/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -*.log -docs -__tests__ -tsconfig.json -src -.fatherrc.ts \ No newline at end of file diff --git a/packages/plugin-full-collections/package.json b/packages/plugin-full-collections/package.json deleted file mode 100644 index c816f3d8b..000000000 --- a/packages/plugin-full-collections/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@nocobase/plugin-full-collections", - "version": "0.4.0-alpha.7", - "main": "lib/index.js", - "license": "MIT", - "private": true, - "dependencies": { - "@nocobase/server": "^0.4.0-alpha.7", - "crypto-random-string": "^3.3.1", - "deepmerge": "^4.2.2", - "just-has": "^1.0.0" - }, - "devDependencies": { - "@nocobase/actions": "^0.4.0-alpha.7", - "@nocobase/database": "^0.4.0-alpha.7", - "@nocobase/resourcer": "^0.4.0-alpha.7" - } -} diff --git a/packages/plugin-full-collections/src/__tests__/collections.test.ts b/packages/plugin-full-collections/src/__tests__/collections.test.ts deleted file mode 100644 index 5d4ba648f..000000000 --- a/packages/plugin-full-collections/src/__tests__/collections.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Agent, getAgent, getApp } from '.'; -import { Application } from '@nocobase/server'; -import Database, { ModelCtor, Model } from '@nocobase/database'; - -describe('collections', () => { - let app: Application; - let agent: Agent; - let db: Database; - let Collection: ModelCtor; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - db = app.database; - Collection = db.getModel('collections'); - }); - - afterEach(() => app.database.close()); - - it('create', async () => { - const collection = await Collection.create({ - name: 'test1', - }); - await collection.updateAssociations({ - fields: [ - { - type: 'string', - name: 'name', - }, - { - type: 'string', - name: 'title', - }, - ], - }); - }); - - it('create2', async () => { - const collection = await Collection.create({}); - await collection.updateAssociations({ - fields: [ - { - type: 'string', - name: 'name', - }, - { - type: 'string', - }, - ], - }); - }); - - it('api', async () => { - await agent.resource('collections').create({ - values: { - name: 'test2', - }, - }); - await agent.resource('collections.fields').create({ - associatedKey: 'test2', - values: { - type: 'string', - name: 'name', - }, - }); - }); -}); diff --git a/packages/plugin-full-collections/src/__tests__/fields.test.ts b/packages/plugin-full-collections/src/__tests__/fields.test.ts deleted file mode 100644 index 64f229870..000000000 --- a/packages/plugin-full-collections/src/__tests__/fields.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Agent, getAgent, getApp } from '.'; -import { Application } from '@nocobase/server'; -import Database, { ModelCtor, Model, FieldOptions } from '@nocobase/database'; -import { FieldModel } from '../models'; - -type Options = { name?: string } & FieldOptions; - -describe('fields', () => { - let app: Application; - let agent: Agent; - let db: Database; - let Collection: ModelCtor; - let collection: Model; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - db = app.database; - Collection = db.getModel('collections'); - collection = await Collection.create({ name: 'foos' }); - await Collection.create({ name: 'bars' }); - const tables = db.getTables(); - for (const table of tables.values()) { - await Collection.import(table.getOptions(), { migrate: false }); - } - }); - - afterEach(() => app.database.close()); - - async function createField(options: any) { - return await collection.createField(options); - } - - describe('basic', () => { - it.only('string', async () => { - // await createField({ - // interface: 'string', - // }); - }); - it('number', async () => { - await createField({ - interface: 'number', - }); - }); - }); - - describe('relation', () => { - it('linkTo', async () => { - const field = await createField({ - interface: 'linkTo', - 'x-linkTo-props': { - multiple: false, - target: 'bars', - }, - }); - const data = field.get(); - const keys = ['target', 'multiple', 'foreignKey', 'otherKey', 'sourceKey', 'targetKey']; - for (const key of keys) { - expect(data[key]).toEqual(field.get(key)); - } - expect(data['x-linkTo-props']).toEqual({ target: 'bars', multiple: false }); - }); - - it('linkTo', async () => { - const field = await createField({ - interface: 'linkTo', - type: 'hasMany', - 'x-linkTo-props': { - // multiple: false, - target: 'bars', - }, - }); - const data = field.get(); - const keys = ['target', 'multiple', 'foreignKey', 'otherKey', 'sourceKey', 'targetKey']; - for (const key of keys) { - expect(data[key]).toEqual(field.get(key)); - } - expect(data['x-linkTo-props']).toEqual({ target: 'bars', multiple: true }); - }); - }); -}); diff --git a/packages/plugin-full-collections/src/__tests__/index.ts b/packages/plugin-full-collections/src/__tests__/index.ts deleted file mode 100644 index a642a2e5a..000000000 --- a/packages/plugin-full-collections/src/__tests__/index.ts +++ /dev/null @@ -1,144 +0,0 @@ -import qs from 'qs'; -import plugin from '../server'; -import supertest from 'supertest'; -import bodyParser from 'koa-bodyparser'; -import { Dialect } from 'sequelize'; -import Database from '@nocobase/database'; -import { actions, middlewares } from '@nocobase/actions'; -import { Application, middleware } from '@nocobase/server'; - -function getTestKey() { - const { id } = require.main; - const key = id - .replace(`${process.env.PWD}/packages`, '') - .replace(/src\/__tests__/g, '') - .replace('.test.ts', '') - .replace(/[^\w]/g, '_') - .replace(/_+/g, '_'); - return key -} - -const config = { - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - host: process.env.DB_HOST, - port: Number.parseInt(process.env.DB_PORT, 10), - dialect: process.env.DB_DIALECT as Dialect, - define: { - hooks: { - beforeCreate(model, options) { - - }, - }, - }, - logging: false, - sync: { - force: true, - alter: { - drop: true, - }, - }, -}; - -export async function getApp() { - const app = new Application({ - database: { - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - }, - }, - resourcer: { - prefix: '/api', - }, - }); - app.resourcer.use(middlewares.associated); - app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common }); - app.registerPlugin('full-collections', [plugin]); - await app.loadPlugins(); - await app.database.sync(); - // 表配置信息存到数据库里 - // const tables = app.database.getTables([]); - // for (const table of tables) { - // const Collection = app.database.getModel('collections'); - // await Collection.import(table.getOptions(), { migrate: false }); - // } - app.use(async (ctx, next) => { - ctx.db = app.database; - await next(); - }); - app.use(bodyParser()); - app.use(middleware({ - prefix: '/api', - resourcer: app.resourcer, - database: app.database, - })); - return app; -} - -interface ActionParams { - resourceKey?: string | number; - // resourceName?: string; - // associatedName?: string; - associatedKey?: string | number; - fields?: any; - filter?: any; - values?: any; - [key: string]: any; -} - -interface Handler { - get: (params?: ActionParams) => Promise; - list: (params?: ActionParams) => Promise; - create: (params?: ActionParams) => Promise; - update: (params?: ActionParams) => Promise; - destroy: (params?: ActionParams) => Promise; - [name: string]: (params?: ActionParams) => Promise; -} - -export interface Agent { - resource: (name: string) => Handler; -} - -export function getAgent(app: Application): Agent { - const agent = supertest.agent(app.callback()); - return { - resource(name: string): any { - return new Proxy({}, { - get(target, method, receiver) { - return (params: ActionParams = {}) => { - const { associatedKey, resourceKey, values = {}, ...restParams } = params; - let url = `/api/${name}`; - if (associatedKey) { - url = `/api/${name.split('.').join(`/${associatedKey}/`)}`; - } - url += `:${method as string}`; - if (resourceKey) { - url += `/${resourceKey}`; - } - console.log(url); - if (['list', 'get'].indexOf(method as string) !== -1) { - return agent.get(`${url}?${qs.stringify(restParams)}`); - } else { - return agent.post(`${url}?${qs.stringify(restParams)}`).send(values); - } - } - } - }); - } - }; -} - -export function getDatabase() { - return new Database({ - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - } - }); -}; diff --git a/packages/plugin-full-collections/src/collections/collections.ts b/packages/plugin-full-collections/src/collections/collections.ts deleted file mode 100644 index 144337c87..000000000 --- a/packages/plugin-full-collections/src/collections/collections.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'collections', - title: '数据表配置', - internal: true, - model: 'CollectionModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '数据表名称', - required: true, - }, - { - interface: 'string', - type: 'randomString', - name: 'name', - randomString: { - length: 10, - template: 't_%r', - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - createOnly: true, - title: '标识', - unique: true, - required: true, - developerMode: true, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - developerMode: true, - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'internal', - title: '系统内置', - defaultValue: false, - developerMode: true, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'fields', - title: '字段', - sourceKey: 'name', - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'actions', - title: '操作方法', - sourceKey: 'name', - }, - ], -} as TableOptions; diff --git a/packages/plugin-full-collections/src/collections/fields.ts b/packages/plugin-full-collections/src/collections/fields.ts deleted file mode 100644 index b75c9d4bb..000000000 --- a/packages/plugin-full-collections/src/collections/fields.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { TableOptions } from '@nocobase/database'; -import { getInterfaceFields } from '../interfaces'; - -export default { - name: 'fields', - title: '字段配置', - internal: true, - model: 'FieldModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '字段名称', - required: true, - }, - { - interface: 'string', - type: 'randomString', - name: 'name', - title: '标识', - required: true, - createOnly: true, - randomString: { - length: 5, - template: 'f_%r', - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - developerMode: true, - }, - ...getInterfaceFields(), - { - interface: 'string', - type: 'string', - name: 'type', - title: '数据类型', - developerMode: true, - }, - { - interface: 'number', - type: 'integer', - name: 'parent_id', - title: '所属分组', - }, - { - interface: 'linkTo', - multiple: false, - type: 'belongsTo', - name: 'parent', - title: '所属分组', - target: 'fields', - foreignKey: 'parent_id', - targetKey: 'id', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'textarea', - type: 'virtual', - name: 'component.tooltip', - title: '提示信息', - }, - { - interface: 'boolean', - type: 'boolean', - name: 'required', - title: '必填项', - defaultValue: false, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - labelField: 'title', - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - developerMode: true, - defaultValue: false, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - ], -} as TableOptions; diff --git a/packages/plugin-full-collections/src/collections/menus.ts b/packages/plugin-full-collections/src/collections/menus.ts deleted file mode 100644 index c0633f9fb..000000000 --- a/packages/plugin-full-collections/src/collections/menus.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'menus', - title: '菜单配置', - internal: true, - // model: 'CollectionModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '菜单名称', - required: true, - }, - { - interface: 'icon', - type: 'string', - name: 'icon', - title: '图标', - component: { - type: 'icon', - }, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '菜单类型', - required: true, - dataSource: [ - { value: 'group', label: '菜单组' }, - { value: 'link', label: '自定义链接' }, - { value: 'page', label: '页面' }, - ], - linkages: [ - { - "type": "value:visible", - "target": "page", - "condition": "{{ $self.value === 'page' }}" - }, - { - "type": "value:visible", - "target": "url", - "condition": "{{ $self.value === 'link' }}" - }, - ], - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'page', - title: '页面', - target: 'pages', - // targetKey: 'name', - }, - { - interface: 'string', - type: 'string', - name: 'url', - title: '链接地址', - required: true, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - developerMode: true, - defaultValue: false, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - ], -} as TableOptions; diff --git a/packages/plugin-full-collections/src/collections/pages.ts b/packages/plugin-full-collections/src/collections/pages.ts deleted file mode 100644 index 33d5aebd1..000000000 --- a/packages/plugin-full-collections/src/collections/pages.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'pages', - title: '页面配置', - internal: true, - // model: 'CollectionModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '页面名称', - required: true, - }, - { - interface: 'string', - type: 'randomString', - name: 'name', - title: '缩略名', - required: true, - createOnly: true, - randomString: { - length: 6, - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - developerMode: true, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '类型', - required: true, - dataSource: [ - { value: 'default', label: '页面' }, - { value: 'collection', label: '数据集' }, - ], - }, - { - interface: 'boolean', - type: 'boolean', - name: 'dynamic', - title: '单条数据子页面', - }, - // { - // interface: 'subTable', - // type: 'hasMany', - // name: 'views', - // title: '视图', - // target: 'pages_views', - // children: [ - // { - // interface: 'linkTo', - // type: 'belongsToMany', - // name: 'view', - // title: '视图', - // target: 'views', - // }, - // { - // interface: 'percent', - // type: 'float', - // name: 'width', - // title: '宽度', - // }, - // ], - // }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - ], -} as TableOptions; diff --git a/packages/plugin-full-collections/src/collections/views.ts b/packages/plugin-full-collections/src/collections/views.ts deleted file mode 100644 index 30803f8ea..000000000 --- a/packages/plugin-full-collections/src/collections/views.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { TableOptions } from '@nocobase/database'; -import { getViewFields } from '../views'; - -export default { - name: 'views', - title: '视图配置', - internal: true, - model: 'ViewModel', - developerMode: true, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '视图名称', - required: true, - component: { - type: 'string', - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '标识', - component: { - type: 'string', - }, - }, - ...getViewFields(), - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - title: '所属数据表', - target: 'collections', - targetKey: 'name', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - component: { - type: 'hidden', - }, - }, - ], -} as TableOptions; diff --git a/packages/plugin-full-collections/src/fields/randomString.ts b/packages/plugin-full-collections/src/fields/randomString.ts deleted file mode 100644 index bb22d4366..000000000 --- a/packages/plugin-full-collections/src/fields/randomString.ts +++ /dev/null @@ -1,27 +0,0 @@ -import cryptoRandomString from 'crypto-random-string'; -import { STRING, FieldContext } from '@nocobase/database'; -import { - DataTypes -} from 'sequelize'; - -export class RANDOMSTRING extends STRING { - constructor(options: any, context: FieldContext) { - super(options, context); - const Model = context.sourceTable.getModel(); - const { name, randomString } = options; - randomString && Model.addHook('beforeValidate', (model) => { - const { template, ...opts } = randomString; - let value = cryptoRandomString(opts); - if (template && template.includes('%r')) { - value = template.replace('%r', value); - } - if (!model.get(name)) { - model.set(name, value); - } - }); - } - - getDataType() { - return DataTypes.STRING; - } -} diff --git a/packages/plugin-full-collections/src/hooks/collections.afterCreate.ts b/packages/plugin-full-collections/src/hooks/collections.afterCreate.ts deleted file mode 100644 index 77d8879ac..000000000 --- a/packages/plugin-full-collections/src/hooks/collections.afterCreate.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default async function afterCreate(model: any, options: any = {}) { - const { migrate = true } = options; - if (migrate) { - await model.migrate(); - } -} diff --git a/packages/plugin-full-collections/src/hooks/collections.beforeValidate.ts b/packages/plugin-full-collections/src/hooks/collections.beforeValidate.ts deleted file mode 100644 index 849494173..000000000 --- a/packages/plugin-full-collections/src/hooks/collections.beforeValidate.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default async function beforeValidate(model: any, options = {}) { - -} diff --git a/packages/plugin-full-collections/src/hooks/fields.afterCreate.ts b/packages/plugin-full-collections/src/hooks/fields.afterCreate.ts deleted file mode 100644 index 77d8879ac..000000000 --- a/packages/plugin-full-collections/src/hooks/fields.afterCreate.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default async function afterCreate(model: any, options: any = {}) { - const { migrate = true } = options; - if (migrate) { - await model.migrate(); - } -} diff --git a/packages/plugin-full-collections/src/hooks/fields.beforeValidate.ts b/packages/plugin-full-collections/src/hooks/fields.beforeValidate.ts deleted file mode 100644 index ca14400a5..000000000 --- a/packages/plugin-full-collections/src/hooks/fields.beforeValidate.ts +++ /dev/null @@ -1,36 +0,0 @@ -import _ from 'lodash'; -import { has, merge, generateRandomString } from '../utils'; -import { interfaces } from '../interfaces'; -import { Model } from '@nocobase/database'; - -export default async function beforeValidate(model: Model, opts = {}) { - let data = model.get(); - const { interface: interfaceType } = data; - if (!interfaceType || !interfaces.has(interfaceType)) { - return; - } - const defaults = {}; - Object.keys(data).forEach(name => { - const match = /options\.x-(\w+)-props\.(\w+)/.exec(name); - if (match) { - if (match[1] !== interfaceType) { - delete data[name]; - delete model.dataValues[name]; - } else { - _.set(defaults, match[2], data[name]); - } - } - }); - const { options, properties = {}, initialize } = interfaces.get(interfaceType); - Object.keys(properties).forEach(name => { - if (has(data, `x-${interfaceType}-props.${name}`)) { - const value = _.get(data, `x-${interfaceType}-props.${name}`); - _.set(data, name, value); - } - }); - data = merge(merge(defaults, options), data); - initialize && await initialize(data, model); - model.set(data); - // 清除掉 interfaceType 下的临时数据 - model.set(`x-${interfaceType}-props`, undefined); -} diff --git a/packages/plugin-full-collections/src/interfaces/index.ts b/packages/plugin-full-collections/src/interfaces/index.ts deleted file mode 100644 index 13fc9b07e..000000000 --- a/packages/plugin-full-collections/src/interfaces/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import * as types from './types'; - -export const groups= new Map(Object.entries({ - basic: '基本类型', - media: '多媒体类型', - choices: '选择类型', - datetime: '日期和时间', - relation: '关系类型', - systemInfo: '系统信息', - developerMode: '开发者模式', - others: '其他' -})); - -export const interfaces = new Map(); - -export function registerInterface(name: string, options: any) { - interfaces.set(name, options); -} - -export function registerInterfaces(values: any) { - Object.keys(values).forEach(name => { - registerInterface(name, { - ...values[name], - interface: name, - }); - }); -} - -export function getOptions() { - const options = []; - const map = new Map(); - for (const [key, item] of interfaces) { - const { title, group } = item; - if (!map.has(group)) { - map.set(group, []); - } - map.get(group).push({ - key: key, - value: key, - label: title, - }); - } - for (const [key, label] of groups) { - options.push({ - key, - label, - children: map.get(key) || [], - }); - } - return options; -} - -export function getInterfaceLinkages() { - let xlinkages = []; - for (const [key, item] of interfaces) { - const { linkages = {}, properties = {} } = item; - xlinkages.push({ - "type": "value:visible", - "target": `x-${key}-props.*`, - "condition": `{{ $self.value === '${key}' }}`, - }); - if (linkages.interface) { - xlinkages.push(linkages.interface); - } - if (linkages.interface) { - xlinkages = xlinkages.concat(linkages.interface.map(linkage => { - if (properties[linkage.target]) { - linkage.condition = `{{ $self.value === '${key}' }}`; - linkage.target = `x-${key}-props.${linkage.target}`; - } - return linkage; - })); - } - } - return xlinkages; -} - -export function getInterfaceFields() { - const fields = new Map(); - fields.set('interface', { - interface: 'select', - type: 'string', - name: 'interface', - title: '字段类型', - required: true, - dataSource: getOptions(), - createOnly: true, - component: { - type: 'select', - }, - linkages: getInterfaceLinkages(), - }); - for (const [key, item] of interfaces) { - const { properties = {}, linkages = {} } = item; - Object.keys(properties).forEach(name => { - const property = { - ...properties[name], - name, - }; - if (!property.type) { - property.type = 'virtual'; - } - if (property.type === 'virtual') { - property.name = `x-${key}-props.${name}`; - } - if (linkages[name]) { - property.linkages = linkages[name].map((linkage: any) => { - linkage.target = `x-${key}-props.${linkage.target}`; - return linkage; - }); - } - fields.set(`x-${key}-props.${name}`, property); - }); - } - return [...fields.values()]; -} - -registerInterfaces(types); diff --git a/packages/plugin-full-collections/src/interfaces/types.ts b/packages/plugin-full-collections/src/interfaces/types.ts deleted file mode 100644 index 0f46781f9..000000000 --- a/packages/plugin-full-collections/src/interfaces/types.ts +++ /dev/null @@ -1,738 +0,0 @@ -import { generateRandomString } from '../utils'; - -export const string = { - title: '单行文本', - group: 'basic', - options: { - type: 'string', - filterable: true, - component: { - type: 'string', - }, - }, -}; - -export const textarea = { - title: '多行文本', - group: 'basic', - options: { - type: 'text', - filterable: true, - component: { - type: 'textarea', - }, - } -}; - -export const phone = { - title: '手机号码', - group: 'basic', - options: { - type: 'string', - filterable: true, - format: 'phone', // 验证的问题 - component: { - type: 'string', - 'x-rules': 'phone', - }, - }, -}; - -export const email = { - title: '邮箱', - group: 'basic', - options: { - type: 'string', - filterable: true, - format: 'email', - component: { - type: 'string', - 'x-rules': 'email', - }, - }, -}; - -export const number = { - title: '数字', - group: 'basic', - options: { - type: 'float', - filterable: true, - sortable: true, - precision: 0, - component: { - type: 'number', - }, - }, - properties: { - precision: { - interface: 'select', - type: 'virtual', - title: '精度', - dataSource: [ - { value: 0, label: '1' }, - { value: 1, label: '1.0' }, - { value: 2, label: '1.00' }, - { value: 3, label: '1.000' }, - { value: 4, label: '1.0000' }, - ], - component: { - type: 'number', - default: 0, - }, - }, - }, -}; - -export const percent = { - title: '百分比', - group: 'basic', - options: { - type: 'float', - filterable: true, - sortable: true, - precision: 0, - component: { - type: 'percent', - }, - }, - properties: { - precision: { - interface: 'select', - type: 'virtual', - title: '精度', - dataSource: [ - { value: 0, label: '1' }, - { value: 1, label: '1.0' }, - { value: 2, label: '1.00' }, - { value: 3, label: '1.000' }, - { value: 4, label: '1.0000' }, - ], - component: { - type: 'number', - default: 0, - }, - }, - }, -}; - -export const wysiwyg = { - title: '可视化编辑器', - group: 'media', - disabled: true, - options: { - type: 'text', - component: { - type: 'wysiwyg', - }, - }, -}; - -export const boolean = { - title: '是/否', - group: 'choices', - options: { - type: 'boolean', - filterable: true, - component: { - type: 'checkbox', // switch - }, - }, -}; - -export const select = { - title: '下拉选择(单选)', - group: 'choices', - options: { - type: 'string', - filterable: true, - dataSource: [], - component: { - type: 'select', - }, - }, - properties: { - dataSource: { - interface: 'subTable', - type: 'virtual', - title: '可选项', - component: { - type: 'table', - default: [{}], - items: { - type: 'object', - properties: { - value: { - type: "string", - title: "值", - required: true - }, - label: { - type: "string", - title: "选项", - required: true - }, - }, - }, - }, - }, - }, - initialize: (data: any) => { - if (Array.isArray(data.dataSource)) { - data.dataSource = data.dataSource.map((item: any) => { - if (item.value === null || typeof item.value === 'undefined') { - item.value = generateRandomString(); - } - return { ...item }; - }); - } - }, -}; - -export const multipleSelect = { - title: '下拉选择(多选)', - group: 'choices', - options: { - type: 'json', // TODO: json 不是个通用的方案 - filterable: true, - dataSource: [], - defaultValue: [], - multiple: true, - component: { - type: 'select', - }, - }, - properties: { - dataSource: select.properties.dataSource, - }, - initialize: select.initialize, -}; - -export const radio = { - title: '单选框', - group: 'choices', - options: { - type: 'string', - filterable: true, - dataSource: [], - component: { - type: 'radio', - }, - }, - properties: { - dataSource: select.properties.dataSource, - }, - initialize: select.initialize, -}; - -export const checkboxes = { - title: '多选框', - group: 'choices', - options: { - type: 'json', // TODO: json 不是个通用的方案 - filterable: true, - dataSource: [], - defaultValue: [], - component: { - type: 'checkboxes', - }, - }, - properties: { - dataSource: select.properties.dataSource, - }, - initialize: select.initialize, -}; - -export const datetime = { - title: '日期', - group: 'datetime', - options: { - type: 'date', - showTime: false, - filterable: true, - sortable: true, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - component: { - type: 'date', - }, - }, - properties: { - dateFormat: { - interface: 'select', - type: 'virtual', - title: '日期格式', - dataSource: [ - { value: 'YYYY/MM/DD', label: '年/月/日' }, - { value: 'YYYY-MM-DD', label: '年-月-日' }, - { value: 'DD/MM/YYYY', label: '日/月/年' }, - ], - defaultValue: 'YYYY-MM-DD', - component: { - type: 'string', - default: 'YYYY-MM-DD', - }, - }, - showTime: { - interface: 'boolean', - type: 'virtual', - title: '显示时间', - component: { - type: 'boolean', - default: false, - }, - }, - timeFormat: { - interface: 'select', - type: 'virtual', - title: '时间格式', - dataSource: [ - { value: 'HH:mm:ss', label: '24小时制' }, - { value: 'hh:mm:ss a', label: '12小时制' }, - ], - defaultValue: 'HH:mm:ss', - component: { - type: 'string', - default: 'HH:mm:ss', - }, - }, - }, - linkages: { - showTime: [ - { - "type": "value:visible", - "target": "timeFormat", - "condition": "{{ ($form.values && $form.values.control === 'time') || $self.value === true }}" - }, - ], - }, -}; - -export const time = { - title: '时间', - group: 'datetime', - options: { - type: 'time', - filterable: true, - sortable: true, - timeFormat: 'HH:mm:ss', - component: { - type: 'time', - }, - }, -}; - -export const subTable = { - title: '子表格', - group: 'relation', - // disabled: true, - options: { - type: 'hasMany', - // target, - // children: [], - component: { - type: 'subTable', - }, - }, - properties: { - children: { - interface: 'subTable', - type: 'hasMany', - target: 'fields', - sourceKey: 'id', - foreignKey: 'parent_id', - title: '子表格字段', - component: { - type: 'subTable', - default: [], - }, - }, - }, - initialize(data: any) { - if (!data.target) { - data.target = generateRandomString({ prefix: 't_', length: 12 }); - } - }, -}; - -export const linkTo = { - title: '关联数据', - group: 'relation', - // disabled: true, - options: { - type: 'belongsToMany', - // name, - // target: '关联表', - // targetKey, - // sourceKey, - // otherKey, - // foreignKey, - // labelField, - // valueField, - filterable: false, - // multiple: true, - component: { - type: 'drawerSelect', - // labelField, - // valueField, - }, - }, - properties: { - target: { - interface: 'string', - type: 'virtual', - name: 'target', - title: '要关联的数据表', - required: true, - createOnly: true, - component: { - type: 'remoteSelect', - labelField: 'title', - valueField: 'name', - resourceName: 'collections', - 'x-component-props': { - mode: 'simple', - }, - }, - }, - 'component.labelField': { - interface: 'string', - type: 'virtual', - title: '要显示的字段', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - 'x-component-props': { - mode: 'simple', - }, - }, - }, - 'component.filter': { - interface: 'json', - type: 'virtual', - title: '数据范围', - component: { - type: 'filter', - resourceName: 'collections.fields', - }, - }, - multiple: { - interface: 'boolean', - type: 'virtual', - name: 'multiple', - title: '允许添加多条记录', - defaultValue: true, - component: { - type: 'checkbox', - }, - }, - }, - linkages: { - target: [ - { - type: "value:state", - target: "component.labelField", - condition: "{{ $self.inputed }}", - state: { - value: null, - } - }, - { - "type": "value:visible", - "target": "component.labelField", - "condition": "{{ !!$self.value }}" - }, - { - type: "value:schema", - target: "component.labelField", - // condition: "{{ $self.value }}", - schema: { - "x-component-props": { - "associatedKey": "{{ $self.value }}" - }, - }, - }, - { - type: 'value:visible', - target: 'component.filter', - condition: '{{ !!$self.value }}' - }, - { - type: "value:schema", - target: "component.filter", - schema: { - "x-component-props": { - "associatedKey": "{{ $self.value }}" - }, - }, - }, - ], - }, - initialize(data: any, model: any) { - if (!['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(data.type)) { - return; - } - if (!data.foreignKey) { - data.foreignKey = generateRandomString({ prefix: 'f_', length: 6 }); - } - if (data.type === 'belongsToMany') { - if (!data.through) { - data.through = generateRandomString({ prefix: 't_', length: 12 }); - } - if (!data.otherKey) { - data.otherKey = generateRandomString({ prefix: 'f_', length: 6 }); - } - } - if (data.type !== 'belongsTo' && !data.sourceKey) { - data.sourceKey = model.constructor.primaryKeyAttribute; - } - if (['belongsTo', 'belongsToMany'].includes(data.type) && !data.targetKey) { - const TargetModel = model.database.getModel(data.target); - data.targetKey = TargetModel.primaryKeyAttribute; - } - } -}; - -export const createdAt = { - title: '创建时间', - group: 'systemInfo', - options: { - interface: 'createdAt', - type: 'date', - field: 'created_at', - showTime: false, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - required: true, - filterable: true, - sortable: true, - component: { - type: 'date', - }, - }, - properties: { - ...datetime.properties, - }, - linkages: { - ...datetime.linkages, - }, -}; - -export const updatedAt = { - title: '修改时间', - group: 'systemInfo', - options: { - interface: 'updatedAt', - type: 'date', - field: 'updated_at', - showTime: false, - dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm:ss', - required: true, - filterable: true, - sortable: true, - component: { - type: 'date', - }, - }, - properties: { - ...datetime.properties, - }, - linkages: { - ...datetime.linkages, - }, -}; - -export const group = { - title: '字段组', - disabled: true, - options: { - type: 'virtual', - component: { - type: 'hidden', - }, - }, -}; - -export const description = { - title: '说明文字', - group: 'others', - options: { - type: 'virtual', - component: { - type: 'description', - }, - }, -}; - -export const primaryKey = { - title: '主键', - group: 'developerMode', - options: { - name: 'id', - type: 'integer', - required: true, - autoIncrement: true, - primaryKey: true, - filterable: true, - developerMode: true, - component: { - type: 'number', - }, - }, -}; - -export const sort = { - title: '排序', - group: 'developerMode', - options: { - type: 'integer', - required: true, - // scope: [], - component: { - type: 'sort', - }, - }, -}; - -export const password = { - title: '密码', - group: 'developerMode', - options: { - type: 'password', - hidden: true, // hidden 用来控制 api 不输出这个字段 - component: { - type: 'password', - }, - }, -}; - -export const json = { - title: 'JSON', - group: 'developerMode', - options: { - type: 'json', - mode: 'replace', - // developerMode: true, - component: { - type: 'hidden', - }, - }, -}; - -export const icon = { - title: '图标', - group: 'developerMode', - options: { - type: 'string', - component: { - type: 'icon', - }, - }, -}; - -export const createdBy = { - title: '创建人', - group: 'systemInfo', - options: { - type: 'createdBy', - // filterable: true, - target: 'users', - foreignKey: 'created_by_id', - component: { - type: 'drawerSelect', - labelField: 'nickname', - }, - }, -}; - -export const updatedBy = { - title: '修改人', - group: 'systemInfo', - options: { - type: 'updatedBy', - // filterable: true, - target: 'users', - foreignKey: 'updated_by_id', - component: { - type: 'drawerSelect', - labelField: 'nickname', - }, - }, -}; - -export const attachment = { - title: '附件', - group: 'media', - // disabled: true, - options: { - type: 'belongsToMany', - filterable: false, - target: 'attachments', - // storage: { - // name: 'local', - // }, - component: { - type: 'upload', - }, - }, - initialize(data: any, model: any) { - if (data.type === 'belongsToMany' && !data.through) { - data.through = generateRandomString({ prefix: 't_', length: 12 }); - } - }, -}; - -export const chinaRegion = { - title: '中国行政区划', - group: 'choices', - options: { - type: 'belongsToMany', - // 数据来源的数据表,与 dataSource 不同,需要从表数据加载后转化成 dataSource - target: 'china_regions', - targetKey: 'code', - // 可选层级,最大层级 - maxLevel: 3, - // 可以选到任意一级结束 - incompletely: false, - component: { - type: 'cascader', - // 值字段 - valueField: 'code', - // 名称字段 - labelField: 'name', - // TODO(refactor): 等 toWhere 重构完成后要改成 parent - // 上级字段名 - parentField: 'parent_code', - }, - }, - properties: { - maxLevel: { - interface: 'radio', - type: 'virtual', - title: '可选层级', - defaultValue: 3, - dataSource: [ - { value: 1, label: '省' }, - { value: 2, label: '市' }, - { value: 3, label: '区/县' }, - { value: 4, label: '乡镇/街道' }, - { value: 5, label: '村/居委会' }, - ], - }, - incompletely: { - interface: 'boolean', - type: 'virtual', - title: '可以选到任意一级结束', - defaultValue: false, - } - }, - initialize(data: any, model: any) { - if (data.type === 'belongsToMany' && !data.through) { - data.through = generateRandomString({ prefix: 't_', length: 12 }); - } - }, -}; diff --git a/packages/plugin-full-collections/src/models/base.ts b/packages/plugin-full-collections/src/models/base.ts deleted file mode 100644 index 72b3be2a6..000000000 --- a/packages/plugin-full-collections/src/models/base.ts +++ /dev/null @@ -1,77 +0,0 @@ -import _ from 'lodash'; -import { getDataTypeKey, Model } from '@nocobase/database'; -import Dottie from 'dottie'; - -export class BaseModel extends Model { - - get additionalAttribute() { - const tableOptions = this.database.getTable(this.constructor.name).getOptions(); - return _.get(tableOptions, 'additionalAttribute') || 'options'; - } - - hasGetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 get 方法就直接走 get - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.get; - } - return !!attribute; - } - - hasSetAttribute(key: string) { - // @ts-ignore - if (this.constructor.hasAlias(key)) { - return false; - } - const attribute = this.rawAttributes[key]; - // virtual 如果有 set 方法就直接走 set - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.set; - } - return !!attribute; - } - - get(key?: any, options?: any) { - if (typeof key !== 'string') { - const data = super.get(key); - return { - ..._.omit(data, [this.additionalAttribute]), - ...(data[this.additionalAttribute] || {}), - }; - } - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.get(column, options); - if (path.length) { - return _.get(value, path); - } - return value; - } - return _.get(super.get(this.additionalAttribute, options) || {}, key); - } - - set(key?: any, value?: any, options: any = {}) { - if (typeof key !== 'string') { - return super.set(key, value, options); - } - // @ts-ignore - if (this.constructor.hasAlias(key)) { - return this; - } - const [column] = key.split('.'); - if (this.hasSetAttribute(column)) { - return super.set(key, value, options); - } - return super.set(`${this.additionalAttribute}.${key}`, value, options); - } - - // getDataValue(key: any) { - // return super.getDataValue(key); - // } - - // setDataValue(key: any, value: any) { - // return super.setDataValue(key, value); - // } -} - -export default BaseModel; diff --git a/packages/plugin-full-collections/src/models/collection.ts b/packages/plugin-full-collections/src/models/collection.ts deleted file mode 100644 index 5efffefce..000000000 --- a/packages/plugin-full-collections/src/models/collection.ts +++ /dev/null @@ -1,100 +0,0 @@ -import _ from 'lodash'; -import { Model, Table, TableOptions } from '@nocobase/database'; -import { SaveOptions, Op } from 'sequelize'; -import BaseModel from './base'; - -export interface LoadOptions { - reset?: boolean; - where?: any; - skipExisting?: boolean; - [key: string]: any; -} - -export class CollectionModel extends BaseModel { - - async migrate() { - const table = await this.loadTableOptions(); - return await table.sync({ - force: false, - alter: { - drop: false, - } - }); - } - - async getFieldsOptions() { - const fieldsOptions = []; - const fields = await this.getFields(); - for (const field of fields) { - fieldsOptions.push(await field.getOptions()); - } - return fieldsOptions; - } - - async getOptions(): Promise { - return { - ...this.get(), - fields: await this.getFieldsOptions(), - }; - } - - async loadTableOptions(opts: any = {}): Promise { - const options = await this.getOptions(); - const table = this.database.table(options); - return table; - } - - /** - * TODO:需要考虑是初次加载还是重载 - * - * @param options - */ - static async load(options: LoadOptions = {}) { - const { skipExisting = false, reset = false, where = {}, transaction } = options; - const collections = await this.findAll({ - transaction, - where, - }); - for (const collection of collections) { - if (skipExisting && this.database.isDefined(collection.get('name'))) { - continue; - } - await collection.loadTableOptions({ - transaction, - reset, - }); - } - } - - static async import(data: TableOptions, options: SaveOptions = {}): Promise { - let collection: CollectionModel; - - if (data.name) { - collection = await this.findOne({ - ...options, - where: { - name: data.name, - }, - }); - } else if (data.title) { - collection = await this.findOne({ - ...options, - where: { - title: data.title, - }, - }); - } - - if (collection) { - await collection.update(data, options); - } else { - collection = await this.create(data, options); - } - - await collection.updateAssociations(data, options); - - return collection; - } -} - -export default CollectionModel; diff --git a/packages/plugin-full-collections/src/models/field.ts b/packages/plugin-full-collections/src/models/field.ts deleted file mode 100644 index a82da91fe..000000000 --- a/packages/plugin-full-collections/src/models/field.ts +++ /dev/null @@ -1,60 +0,0 @@ -import _ from 'lodash'; -import { Model, FieldOptions } from '@nocobase/database'; -import BaseModel from './base'; -import { interfaces } from '../interfaces'; -import has from 'just-has'; - -export class FieldModel extends BaseModel { - - get(key?: any, options?: any) { - if (typeof key !== 'string') { - const data = super.get(key); - const { interface: interfaceType } = data; - if (interfaceType && interfaces.has(interfaceType)) { - const { properties = {} } = interfaces.get(interfaceType); - Object.keys(properties).forEach(name => { - if (has(data, name)) { - const value = _.get(data, name); - _.set(data, `x-${interfaceType}-props.${name}`, value); - } - }); - } - return data; - } - return super.get(key, options); - } - - async getOptions(): Promise { - return { - ...this.get(), - }; - } - - async migrate(options: any = {}) { - const collectionName = this.get('collection_name'); - - if (!collectionName) { - return false; - } - - if (!this.database.isDefined(collectionName)) { - throw new Error(`${collectionName} is not defined`); - } - - const table = this.database.getTable(collectionName); - const fieldOptions = await this.getOptions(); - console.log({ fieldOptions }); - - table.addField(fieldOptions); - - return await table.sync({ - force: false, - alter: { - drop: false, - } - }); - } - -} - -export default FieldModel; diff --git a/packages/plugin-full-collections/src/models/index.ts b/packages/plugin-full-collections/src/models/index.ts deleted file mode 100644 index d5b669b3f..000000000 --- a/packages/plugin-full-collections/src/models/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './collection'; -export * from './field'; -export * from './view'; diff --git a/packages/plugin-full-collections/src/models/view.ts b/packages/plugin-full-collections/src/models/view.ts deleted file mode 100644 index cd3309a20..000000000 --- a/packages/plugin-full-collections/src/models/view.ts +++ /dev/null @@ -1,33 +0,0 @@ -import _ from 'lodash'; -import BaseModel from './base'; -import { interfaces } from '../interfaces'; -import has from 'just-has'; - -export class ViewModel extends BaseModel { - - get(key?: any, options?: any) { - if (typeof key !== 'string') { - const data = super.get(key); - const { type } = data; - if (type && interfaces.has(type)) { - const { properties = {} } = interfaces.get(type); - Object.keys(properties).forEach(name => { - if (has(data, name)) { - const value = _.get(data, name); - _.set(data, `x-${type}-props.${name}`, value); - } - }); - } - return data; - } - return super.get(key, options); - } - - async getOptions(): Promise { - return { - ...this.get(), - }; - } -} - -export default ViewModel; diff --git a/packages/plugin-full-collections/src/server.ts b/packages/plugin-full-collections/src/server.ts deleted file mode 100644 index 80f219dbf..000000000 --- a/packages/plugin-full-collections/src/server.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from 'path'; -import { Application } from '@nocobase/server'; -import { registerModels, registerFields } from '@nocobase/database'; -import * as models from './models'; -import collectionsBeforeValidate from './hooks/collections.beforeValidate'; -import collectionsAfterCreate from './hooks/collections.afterCreate'; -import fieldsBeforeValidate from './hooks/fields.beforeValidate'; -import fieldsAfterCreate from './hooks/fields.afterCreate'; - -import { RANDOMSTRING } from './fields/randomString'; -import { interfaces } from './interfaces'; -import { merge } from './utils'; - -export default async function (this: Application, options = {}) { - const database = this.database; - const resourcer = this.resourcer; - // 提供全局的 models 注册机制 - registerModels(models); - registerFields({ - RANDOMSTRING, - }); - - database.addHook('beforeAddField', (options: any) => { - const { interface: interfaceType } = options; - if (interfaceType && interfaces.has(interfaceType)) { - const defaults = interfaces.get(interfaceType).options; - Object.assign(options, merge(defaults, options)) - } - // console.log({options}); - }); - - database.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - database.getModel('collections').addHook('beforeValidate', collectionsBeforeValidate); - database.getModel('fields').addHook('beforeValidate', fieldsBeforeValidate); - database.getModel('collections').addHook('afterCreate', collectionsAfterCreate); - database.getModel('fields').addHook('afterCreate', fieldsAfterCreate); -} diff --git a/packages/plugin-full-collections/src/utils.ts b/packages/plugin-full-collections/src/utils.ts deleted file mode 100644 index 9b6f73f8a..000000000 --- a/packages/plugin-full-collections/src/utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import deepmerge from 'deepmerge'; -import cryptoRandomString from 'crypto-random-string'; -import justHas from 'just-has'; - -const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray - -export function merge(obj1: any, obj2: any) { - return deepmerge(obj1, obj2, { - arrayMerge: overwriteMerge, - }); -} - -export function generateRandomString(options: any = {}) { - const { prefix = '' } = options; - // @ts-ignore - return prefix + cryptoRandomString({ - length: 6, - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - ...options, - }); -} - -export const has = justHas; diff --git a/packages/plugin-full-collections/src/views/index.ts b/packages/plugin-full-collections/src/views/index.ts deleted file mode 100644 index b4a0d898c..000000000 --- a/packages/plugin-full-collections/src/views/index.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as types from './types'; - -export const views = new Map(); - -export function registerView(type: string, value: any) { - views.set(type, value); -} - -export function registerViews(values: any) { - Object.keys(values).forEach(type => { - registerView(type, { - ...values[type], - type, - }); - }); -} - -registerViews(types); - -export function getOptions() { - const options = []; - for (const [type, view] of views) { - options.push({ - key: type, - value: type, - label: view.title, - }); - } - return options; -} - -export function getViewTypeLinkages() { - let xlinkages = []; - for (const [key, item] of views) { - const { linkages = {}, properties = {} } = item; - xlinkages.push({ - "type": "value:visible", - "target": `x-${key}-props.*`, - "condition": `{{ $self.value === '${key}' }}`, - }); - if (linkages.type) { - xlinkages = xlinkages.concat(linkages.type.map(linkage => { - if (properties[linkage.target]) { - linkage.condition = `{{ $self.value === '${key}' }}`; - linkage.target = `x-${key}-props.${linkage.target}`; - } - return linkage; - })); - } - } - return xlinkages; -} - -export function getViewFields() { - const fields = new Map(); - fields.set('type', { - interface: 'select', - type: 'string', - name: 'type', - title: '视图类型', - required: true, - dataSource: getOptions(), - createOnly: true, - component: { - type: 'select', - }, - linkages: getViewTypeLinkages(), - }); - for (const [key, item] of views) { - const { properties = {}, linkages = {} } = item; - Object.keys(properties).forEach(name => { - const property = { - ...properties[name], - name, - }; - if (!property.type) { - property.type = 'virtual'; - } - if (property.type === 'virtual') { - property.name = `x-${key}-props.${name}`; - } - if (linkages[name]) { - property.linkages = linkages[name].map((linkage: any) => { - linkage.target = `x-${key}-props.${linkage.target}`; - return linkage; - }); - } - fields.set(`x-${key}-props.${name}`, property); - }); - } - return [...fields.values()]; -} diff --git a/packages/plugin-full-collections/src/views/types.ts b/packages/plugin-full-collections/src/views/types.ts deleted file mode 100644 index 2086aefc8..000000000 --- a/packages/plugin-full-collections/src/views/types.ts +++ /dev/null @@ -1,403 +0,0 @@ -const fields = { - interface: 'json', - title: '要显示的字段', - // target: 'views_fields', - fields: [ - { - interface: 'string', - type: 'string', - name: 'name', - title: '字段', - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '字段标题', - }, - ], -}; - -const actions = { - interface: 'json', - title: '可进行的操作', - fields: [ - { - interface: 'string', - type: 'string', - name: 'name', - title: '操作', - }, - ], -}; - -const pages = { - interface: 'json', - title: '详情页要显示的单条数据子页面', - fields: [ - { - interface: 'string', - type: 'string', - name: 'name', - title: '页面', - }, - ], -}; - -const openMode = { - interface: 'radio', - // type: 'string', - title: '单条数据详情页的打开方式', - required: true, - dataSource: [ - { label: '常规页面', value: 'default' }, - { label: '快捷抽屉', value: 'simple' }, - ], - component: { - type: 'radio', - default: 'default', - }, -}; - -export const form = { - // fields, - title: '表单', - options: { - // fields, - }, - properties: { - fields, - }, - linkages: { - type: [ - { - type: 'value:schema', - target: "fields", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - ], - }, -}; - -export const detail = { - title: '详情', - options: { - // actions, - // fields, - }, - properties: { - actions, - fields, - }, - linkages: { - type: [ - { - type: 'value:schema', - target: "fields", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - ], - }, -}; - -export const table = { - title: '表格', - options: { - defaultPerPage: 20, - draggable: false, - filter: {}, - sort: {}, - openMode: 'default', - // actions, - // fields, - // pages, - // labelField, - }, - properties: { - // 数据配置 - filter: { - interface: 'json', - type: 'virtual', - title: '筛选数据', - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - }, - }, - sort: { - interface: 'json', - type: 'virtual', - title: '默认排序', - mode: 'replace', - defaultValue: {}, - component: { - type: 'string', - }, - }, - // 表格配置 - labelField: { - interface: 'select', - type: 'virtual', - title: '标题字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - fields, - defaultPerPage: { - interface: 'radio', - type: 'virtual', - name: 'defaultPerPage', - title: '默认每页显示几行数据', - defaultValue: 50, - dataSource: [ - { label: '10', value: 10 }, - { label: '20', value: 20 }, - { label: '50', value: 50 }, - { label: '100', value: 100 }, - ], - }, - draggable: { - interface: 'boolean', - type: 'virtual', - title: '支持拖拽数据排序', - }, - // 操作配置 - actions, - // 详情配置 - openMode, - pages, - }, - linkages: { - type: [ - { - type: 'value:schema', - target: "filter", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "sort", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "labelField", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "fields", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "pages", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - } - ], - }, -}; - -export const calendar = { - title: '日历', - options: { - // filter, - // labelField, - // startDateField, - // endDateField, - // openMode, - // pages, - }, - properties: { - // 数据配置 - filter: { - interface: 'json', - type: 'virtual', - title: '筛选数据', - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - }, - }, - // 日历配置 - labelField: { - interface: 'select', - type: 'virtual', - title: '标题字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - startDateField: { - interface: 'select', - type: 'virtual', - title: '开始日期字段', - // required: true, - component: { - type: 'remoteSelect', - placeholder: '默认为创建时间字段', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - endDateField: { - interface: 'select', - type: 'virtual', - title: '结束日期字段', - // required: true, - component: { - type: 'remoteSelect', - placeholder: '默认为创建时间字段', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - // 详情配置 - openMode, - pages, - }, - linkages: { - type: [ - { - type: 'value:schema', - target: "filter", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "labelField", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "startDateField", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "endDateField", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - { - type: 'value:schema', - target: "pages", - schema: { - 'x-component-props': { - associatedKey: "{{ $form.values && $form.values.associatedKey }}" - }, - }, - }, - ], - }, -}; - -export const association = { - title: '相关数据视图', - options: { - // tableName, - // viewName, - // actions, - }, - properties: { - tableName: { - interface: 'select', - type: 'virtual', - title: '相关数据', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - }, - }, - viewName: { - interface: 'select', - type: 'virtual', - title: '相关数据表的视图', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.views', - labelField: 'title', - valueField: 'name', - }, - }, - actions, - }, - linkages: { - tableName: [], - viewName: [], - }, -}; diff --git a/packages/plugin-pages/.fatherrc.ts b/packages/plugin-pages/.fatherrc.ts deleted file mode 100755 index 86bce87c0..000000000 --- a/packages/plugin-pages/.fatherrc.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - target: 'node', - cjs: { type: 'babel', lazy: true }, - disableTypeCheck: true, -}; diff --git a/packages/plugin-pages/.npmignore b/packages/plugin-pages/.npmignore deleted file mode 100644 index 461574b2f..000000000 --- a/packages/plugin-pages/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -*.log -docs -__tests__ -tsconfig.json -src -.fatherrc.ts \ No newline at end of file diff --git a/packages/plugin-pages/package.json b/packages/plugin-pages/package.json deleted file mode 100644 index d5a8a8045..000000000 --- a/packages/plugin-pages/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@nocobase/plugin-pages", - "version": "0.4.0-alpha.7", - "main": "lib/index.js", - "license": "MIT", - "dependencies": { - "@nocobase/actions": "^0.4.0-alpha.7", - "@nocobase/database": "^0.4.0-alpha.7", - "@nocobase/resourcer": "^0.4.0-alpha.7", - "@nocobase/server": "^0.4.0-alpha.7", - "crypto-random-string": "^3.3.1" - }, - "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" -} diff --git a/packages/plugin-pages/src/actions/getCollection.ts b/packages/plugin-pages/src/actions/getCollection.ts deleted file mode 100644 index d85848efe..000000000 --- a/packages/plugin-pages/src/actions/getCollection.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; - -export default async (ctx, next) => { - const { resourceName, resourceKey } = ctx.action.params; - const [Collection, Field, Tab, View] = ctx.db.getModels(['collections', 'fields', 'tabs', 'views']) as ModelCtor[]; - const collection = await Collection.findOne(Collection.parseApiJson({ - filter: { - name: resourceName, - }, - })); - const permissions = (await ctx.ac.isRoot() || collection.developerMode || collection.internal) - ? await ctx.ac.getRootPermissions() - : await ctx.ac.can(resourceName).permissions(); - const defaultView = await collection.getViews({ - where: { - default: true, - }, - limit: 1, - plain: true, - }); - collection.setDataValue('defaultViewName', defaultView.get('name')); - const options = Tab.parseApiJson({ - filter: ctx.state.developerMode ? { - 'id.in': permissions.tabs, - enabled: true, - } : { - 'id.in': permissions.tabs, - enabled: true, - developerMode: { '$isFalsy': true }, - }, - fields: { - appends: ['associationField'], - }, - sort: ['sort'], - }); - const tabs = await collection.getTabs(options) as Model[]; - const tabItems = []; - for (const tab of tabs) { - const itemTab = { - ...tab.get(), - }; - if (itemTab.type === 'details' && !itemTab.viewName) { - itemTab.viewName = 'details'; - } - // if (itemTab.type == 'association') { - // itemTab.field = await collection.getFields({ - // where: { - // name: itemTab.association, - // }, - // limit: 1, - // plain: true, - // }); - // } - tabItems.push(itemTab); - } - ctx.body = { - ...collection.toJSON(), - tabs: tabItems, - }; - await next(); -} diff --git a/packages/plugin-pages/src/actions/getCollections.ts b/packages/plugin-pages/src/actions/getCollections.ts deleted file mode 100644 index 898403923..000000000 --- a/packages/plugin-pages/src/actions/getCollections.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; -import _ from 'lodash'; - -async function getPageInfo(ctx, { resourceName, resourceKey }) { - // const { resourceName, resourceKey } = ctx.action.params; - const M = ctx.db.getModel(resourceName) as ModelCtor; - const model = await M.findByPk(resourceKey); - const Field = ctx.db.getModel('fields') as ModelCtor; - const field = await Field.findOne({ - where: { - collection_name: resourceName, - type: 'string', - }, - order: [['sort', 'asc']], - }); - return { - pageTitle: field ? (model.get(field.get('name')) || `#${model.get(M.primaryKeyAttribute)} 无标题`) : model.get(M.primaryKeyAttribute), - ...model.toJSON(), - }; -}; - - -async function getCollection(ctx, resourceName) { - const [Collection, Field, Tab, View] = ctx.db.getModels(['collections', 'fields', 'tabs', 'views']) as ModelCtor[]; - const collection = await Collection.findOne(Collection.parseApiJson({ - filter: { - name: resourceName, - }, - })); - const permissions = (await ctx.ac.isRoot() || collection.developerMode || collection.internal) - ? await ctx.ac.can(resourceName).getRootPermissions() - : await ctx.ac.can(resourceName).permissions(); - const defaultView = await collection.getViews({ - where: { - default: true, - }, - limit: 1, - plain: true, - }); - collection.setDataValue('defaultViewName', defaultView.get('name')); - const options = Tab.parseApiJson({ - filter: ctx.state.developerMode ? { - 'id.in': permissions.tabs, - enabled: true, - } : { - 'id.in': permissions.tabs, - enabled: true, - developerMode: { '$isFalsy': true }, - }, - fields: { - appends: ['associationField'], - }, - sort: ['sort'], - }); - const tabs = await collection.getTabs(options) as Model[]; - const tabItems = []; - for (const tab of tabs) { - const itemTab = { - ...tab.get(), - }; - if (itemTab.type === 'details' && !itemTab.viewName) { - itemTab.viewName = 'details'; - } - // if (itemTab.type == 'association') { - // itemTab.field = await collection.getFields({ - // where: { - // name: itemTab.association, - // }, - // limit: 1, - // plain: true, - // }); - // } - tabItems.push(itemTab); - } - return { - ...collection.toJSON(), - tabs: tabItems, - }; -} - -export default async (ctx, next) => { - const { resourceName, values = {} } = ctx.action.params; - const { tabs: items = [] } = values; - // console.log({items}) - const collection = await getCollection(ctx, resourceName); - - ctx.body = [ - collection, - ]; - - const lastItem = items.pop(); - - for (const item of items) { - const lastCollection = ctx.body[ctx.body.length - 1]; - lastCollection.pageInfo = await getPageInfo(ctx, { resourceName: lastCollection.name, resourceKey: item.itemId }); - const activeTab = _.find(lastCollection.tabs, tab => tab.name == item.tabName) || {}; - if (activeTab && activeTab.type === 'association') { - // console.log(activeTab.associationField.target); - const nextCollection = await getCollection(ctx, activeTab.associationField.target); - ctx.body.push(nextCollection); - } - } - - const lastCollection = ctx.body[ctx.body.length - 1]; - lastCollection.pageInfo = await getPageInfo(ctx, { resourceName: lastCollection.name, resourceKey: lastItem.itemId }); - - await next(); -} diff --git a/packages/plugin-pages/src/actions/getInfo.ts b/packages/plugin-pages/src/actions/getInfo.ts deleted file mode 100644 index 2c3707868..000000000 --- a/packages/plugin-pages/src/actions/getInfo.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ResourceOptions } from '@nocobase/resourcer'; -import { Model, ModelCtor } from '@nocobase/database'; -import _ from 'lodash'; -import { Op } from 'sequelize'; - -export default async (ctx, next) => { - const { resourceKey } = ctx.action.params; - let primaryKey: any; - let pageName: any; - let collectionName: any; - const roles = ctx.ac ? await ctx.ac.getRoles() : []; - const isRoot = ctx.ac.constructor.isRoot(roles); - const MenuPermission = ctx.db.getModel('menus_permissions'); - const menu_permissions = await MenuPermission.findAll({ - where: { - role_id: { - [Op.in]: roles.map(role => role.id), - } - } - }); - const menuIds = menu_permissions.map(item => item.menu_id); - const Menu = ctx.db.getModel('menus') as ModelCtor; - const menu = await Menu.findOne({ - where: isRoot ? { - name: resourceKey, - } : { - id: { - [Op.in]: menuIds, - }, - name: resourceKey, - } - }); - if (!menu) { - ctx.throw(404, 'Not Found'); - } - const body: any = { - ...menu.toJSON(), - }; - if (body.views) { - const views = []; - for (const item of body.views) { - let name: string; - if (typeof item === 'object') { - if (item.view) { - item.name = item.view.collection_name ? `${item.view.collection_name}.${item.view.name}` : item.view.name; - } - views.push(item); - } else if (typeof item === 'string') { - views.push({ - name: item, - width: '100%', - }); - } - } - body.views = views; - } - ctx.body = body; - await next(); -}; diff --git a/packages/plugin-pages/src/actions/getPageInfo.ts b/packages/plugin-pages/src/actions/getPageInfo.ts deleted file mode 100644 index cb59809c0..000000000 --- a/packages/plugin-pages/src/actions/getPageInfo.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ResourceOptions } from '@nocobase/resourcer'; -import { Model, ModelCtor } from '@nocobase/database'; - -export default async (ctx, next) => { - const { resourceName, resourceKey } = ctx.action.params; - const M = ctx.db.getModel(resourceName) as ModelCtor; - const model = await M.findByPk(resourceKey); - const Field = ctx.db.getModel('fields') as ModelCtor; - const field = await Field.findOne({ - where: { - collection_name: resourceName, - type: 'string', - }, - order: [['sort', 'asc']], - }); - ctx.body = { - pageTitle: field ? (model.get(field.get('name')) || `#${model.get(M.primaryKeyAttribute)} 无标题`) : model.get(M.primaryKeyAttribute), - ...model.toJSON(), - }; - await next(); -}; diff --git a/packages/plugin-pages/src/actions/getRoutes.ts b/packages/plugin-pages/src/actions/getRoutes.ts deleted file mode 100644 index af9e10d02..000000000 --- a/packages/plugin-pages/src/actions/getRoutes.ts +++ /dev/null @@ -1,181 +0,0 @@ -import Database from '@nocobase/database'; -import { ResourceOptions } from '@nocobase/resourcer'; -import { flatToTree } from '../utils'; -import { get } from 'lodash'; -import { Op } from 'sequelize'; - -function pages2routes(pages: Array) { - let routes: any = {}; - pages.forEach(page => { - const { children = [], ...restProps } = page; - const route: any = { - ...restProps, - }; - // page.type === 'layout' && - if (!page.redirect && children.length) { - const items = children.filter(item => item.showInMenu).sort((a, b) => a.sort - b.sort); - const redirect = get(items, [0, 'path']); - if (redirect) { - route.redirect = redirect; - } - } - if (page.type === 'layout' && children.length) { - const items = children.filter(item => item.showInMenu).sort((a, b) => a.sort - b.sort); - route.menu = items.map(child => ({ - ...child, - title: child.title, - path: child.path, - sort: child.sort, - })); - } - if (page.children) { - routes = { ...routes, ...pages2routes(page.children) }; - } - routes[page.path] = route; - }); - return routes; -} - -export default async function getRoutes(ctx, next) { - const database: Database = ctx.database; - const Page = database.getModel('pages'); - const View = database.getModel('views'); - const Collection = database.getModel('collections'); - const RoutePermission = database.getModel('routes_permissions'); - const roles = ctx.ac ? await ctx.ac.getRoles() : []; - // TODO(optimize): isRoot 的判断需要在内部完成,尽量不要交给调用者 - const isRoot = true; // ctx.ac ? ctx.ac.constructor.isRoot(roles) : true; - const routesPermissionsMap = new Map(); - if (!isRoot) { - const routesPermissions = await RoutePermission.findAll({ - where: { - role_id: roles.map(({ id }) => id) - } - }); - routesPermissions.forEach(permission => { - routesPermissionsMap.set(`${permission.routable_type}:${permission.routable_id}`, permission); - }); - } - let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? { - filter: { - }, - sort: ['sort'], - } : { - filter: { - developerMode: { '$isFalsy': true }, - }, - sort: ['sort'], - })); - const items = []; - for (const page of pages) { - if (!isRoot - && !routesPermissionsMap.has(`pages:${page.id}`) - // 以下路径先临时处理 - && page.get('path') !== '/' - && page.get('path') !== '/admin' - && page.get('path') !== '/register' - && page.get('path') !== '/login' - ) { - continue; - } - items.push(page.toJSON()); - if (page.get('path') === '/collections') { - const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? { - filter: { - showInDataMenu: true, - }, - sort: ['sort'], - } : { - filter: { - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - }, - sort: ['sort'], - })); - for (const collection of collections) { - if (!isRoot && !routesPermissionsMap.has(`collections:${collection.id}`)) { - continue; - } - const pageId = `collection-${collection.id}`; - items.push({ - id: pageId, - type: 'collection', - collection: collection.get('name'), - title: collection.get('title'), - icon: collection.get('icon'), - path: `/collections/${collection.name}`, - parent_id: page.id, - showInMenu: true, - sort: collection.get('sort'), - }); - const views = await collection.getViews({ - where: { - [Op.or]: [ - { showInDataMenu: true }, - { default: true } - ] - }, - order: [['sort', 'asc']] - }); - if (views.length > 1) { - for (const view of views) { - if (!isRoot && !routesPermissionsMap.has(`views:${view.id}`)) { - continue; - } - items.push({ - id: `view-${view.get('id')}`, - type: 'collection', - collection: collection.get('name'), - title: view.title, - viewName: view.name, - path: `/collections/${collection.name}/views/${view.name}`, - parent_id: pageId, - showInMenu: true, - sort: view.get('sort'), - }); - } - } - } - } else if (page.get('path') === '/users/users') { - const userViews = await View.findAll(View.parseApiJson(ctx.state.developerMode ? { - filter: { - collection_name: 'users', - showInDataMenu: true, - }, - sort: ['sort'], - } : { - filter: { - collection_name: 'users', - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - }, - sort: ['sort'], - })); - if (userViews.length > 1) { - for (const view of userViews) { - if (!isRoot && !routesPermissionsMap.has(`views:${view.id}`)) { - continue; - } - items.push({ - id: `view-${view.get('id')}`, - type: 'collection', - collection: 'users', - title: view.title, - viewName: view.name, - path: `${page.get('path')}/views/${view.name}`, - parent_id: page.id, - showInMenu: true, - sort: view.get('sort'), - }); - } - } - } - } - const data = flatToTree(items, { - id: 'id', - parentId: 'parent_id', - children: 'children', - }); - ctx.body = pages2routes(data); - await next(); -} diff --git a/packages/plugin-pages/src/actions/getTree.ts b/packages/plugin-pages/src/actions/getTree.ts deleted file mode 100644 index e75388fbc..000000000 --- a/packages/plugin-pages/src/actions/getTree.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; -import { flatToTree } from '../utils'; -import { Op } from 'sequelize'; - -export function generateName(): string { - return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; -} - -function toPaths(item) { - if (!Array.isArray(item.children)) { - return []; - } - if (item.children.length === 0) { - return []; - } - let paths = []; - for (const child of item.children) { - if (child.path && !child.children) { - paths.push(child.path); - } - if (child.children) { - child.paths = toPaths(child); - paths = paths.concat(child.paths); - } - } - return paths; -} - -export default async (ctx, next) => { - const { resourceName, resourceKey } = ctx.action.params; - const [Menu] = ctx.db.getModels(['menus']) as ModelCtor[]; - const roles = ctx.ac ? await ctx.ac.getRoles() : []; - const isRoot = ctx.ac.constructor.isRoot(roles); - const MenuPermission = ctx.db.getModel('menus_permissions'); - const menu_permissions = await MenuPermission.findAll({ - where: { - role_id: { - [Op.in]: roles.map(role => role.id), - } - } - }); - const menuIds = menu_permissions.map(item => item.menu_id); - const menus = await Menu.findAll(Menu.parseApiJson({ - filter: isRoot ? { - } : { - 'id.in': menuIds, - }, - sort: 'sort', - })); - const data = flatToTree(menus.map(item => { - const json: any = item.toJSON(); - if (item.url) { - json.path = item.url; - } else { - json.path = item.name; - } - return json; - }), { - id: 'id', - parentId: 'parent_id', - children: 'children', - }); - const items = []; - for (const item of data) { - if (item.parent_id) { - continue; - } - item.paths = toPaths(item); - if (item.paths[0]) { - item.path = item.paths[0]; - } - items.push(item); - } - ctx.body = items; - await next(); -} diff --git a/packages/plugin-pages/src/actions/getView.ts b/packages/plugin-pages/src/actions/getView.ts deleted file mode 100644 index 6af3bc595..000000000 --- a/packages/plugin-pages/src/actions/getView.ts +++ /dev/null @@ -1,589 +0,0 @@ -import { Model, ModelCtor, BELONGSTOMANY } from '@nocobase/database'; -import { get, set, isString } from 'lodash'; -import { Op } from 'sequelize'; - -const transforms = { - table: async (fields: Model[], context?: any) => { - const arr = []; - for (const field of fields) { - if (!field.get('component.showInTable')) { - continue; - } - if (!context.listFields.includes(field.id)) { - continue; - } - arr.push({ - ...field.get(), - sorter: field.get('sortable'), - dataIndex: field.name.split('.'), - }); - } - return arr; - }, - form: async (fields: Model[], ctx?: any) => { - const [Field] = ctx.db.getModels(['fields']) as ModelCtor[]; - const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode); - const schema = {}; - for (const field of fields) { - if (field.get('component.type') === 'hidden') { - continue; - } - if (!field.get('component.showInForm')) { - continue; - } - if (!ctx.listFields.includes(field.id)) { - continue; - } - const interfaceType = field.get('interface'); - const type = field.get('component.type') || 'string'; - const prop: any = { - type, - title: field.title || field.name, - ...(field.component || {}), - } - if (field.interface === 'description') { - field.title && set(prop, 'x-component-props.title', field.title); - field.get('component.tooltip') && set(prop, 'x-component-props.children', field.get('component.tooltip')); - } - if (ctx.formMode === 'update') { - if (!ctx.updateFields.includes(field.id)) { - set(prop, 'x-component-props.disabled', true); - } - } else if (!ctx.createFields.includes(field.id)) { - set(prop, 'x-component-props.disabled', true); - } - if (field.get('name') === 'interface' && ctx.state.developerMode === false) { - const dataSource = field.get('dataSource').filter(item => item.key !== 'developerMode'); - field.set('dataSource', dataSource); - } - const { values } = ctx.action.params; - if (field.get('component.type') === 'filter' && get(values, 'associatedKey') && isString(get(values, 'associatedKey'))) { - const options = Field.parseApiJson(ctx.state.developerMode ? { - filter: { - collection_name: get(values, 'associatedKey'), - }, - sort: 'sort', - } : { - filter: { - collection_name: get(values, 'associatedKey'), - developerMode: { '$isFalsy': true }, - }, - sort: 'sort', - }); - const all = await Field.findAll(options); - set(prop, 'x-component-props.fields', all.filter(f => f.get('filterable'))); - } - if (type === 'select') { - prop.type = 'string' - } - if (field.get('component.tooltip')) { - // prop.description = `{{html('${encodeURIComponent(field.get('component.tooltip'))}')}}`; - } - if (field.get('name') === 'dataSource') { - - set(prop, 'x-component-props.operationsWidth', 'auto'); - set(prop, 'x-component-props.bordered', true); - set(prop, 'x-component-props.className', 'data-source-table'); - const properties = {}; - if (ctx.state.developerMode) { - Object.assign(properties, { - value: { - type: "string", - title: "值", - // required: true, - 'x-component-props': { - bordered: false, - }, - }, - }); - } - Object.assign(properties, { - label: { - type: "string", - title: "选项", - required: true, - 'x-component-props': { - bordered: false, - }, - }, - color: { - type: "colorSelect", - title: "颜色", - 'x-component-props': { - bordered: false, - }, - }, - }); - set(prop, 'items.properties', properties); - } - if (['number', 'percent'].includes(interfaceType) && field.get('precision')) { - set(prop, 'x-component-props.step', field.get('precision')); - } - if (field.get('required')) { - prop.required = true; - } - if (mode === 'update' && field.get('createOnly')) { - set(prop, 'x-component-props.disabled', true); - } - if (typeof field.get('showTime') === 'boolean') { - set(prop, 'x-component-props.showTime', field.get('showTime')); - } - const defaultValue = get(field.options, 'defaultValue'); - if (typeof defaultValue !== 'undefined') { - prop.default = defaultValue; - } - if (interfaceType === 'boolean') { - set(prop, 'x-component-props.children', prop.title); - delete prop.title; - } - if (interfaceType === 'linkTo') { - set(prop, 'x-component-props.associatedName', field.get('collection_name')); - set(prop, 'x-component-props.target', field.get('target')); - set(prop, 'x-component-props.multiple', field.get('multiple')); - set(prop, 'x-component-props.labelField', field.get('labelField')); - } - if (interfaceType === 'multipleSelect') { - set(prop, 'x-component-props.mode', 'multiple'); - } - if (interfaceType === 'subTable' && field.get('target')) { - set(prop, 'x-component-props.target', field.get('target')); - // resourceName - } - if (['radio', 'select', 'multipleSelect', 'checkboxes'].includes(interfaceType)) { - prop.enum = field.get('dataSource'); - } - if (interfaceType === 'chinaRegion') { - set(prop, 'x-component-props.target', field.get('target')); - set(prop, 'x-component-props.labelField', field.get('labelField')); - set(prop, 'x-component-props.valueField', field.get('targetKey')); - set(prop, 'x-component-props.parentField', field.get('parentField')); - set(prop, 'x-component-props.maxLevel', field.get('maxLevel')); - set(prop, 'x-component-props.changeOnSelect', field.get('incompletely')); - } - schema[field.name] = { - id: field.id, - ...prop, - }; - } - return schema; - }, - details: async (fields: Model[], context?: any) => { - const [Field] = context.db.getModels(['fields']) as ModelCtor[]; - const arr = []; - for (const field of fields) { - if (!get(field.component, 'showInDetail')) { - continue; - } - if (!context.listFields.includes(field.id)) { - continue; - } - const props = {}; - if (field.get('interface') === 'subTable') { - const children = await Field.findAll(Field.parseApiJson({ - filter: { - collection_name: field.get('target'), - }, - perPage: -1, - sort: ['sort'], - })); - // const children = await field.getChildren({ - // order: [['sort', 'asc']], - // }); - props['children'] = children.filter(item => item.get('component.showInTable')).map(child => ({ ...child.toJSON(), dataIndex: child.name.split('.') })) - } - arr.push({ - ...field.toJSON(), - ...props, - }); - } - return arr; - }, - filter: async (fields: Model[], ctx?: any) => { - const properties = { - filter: { - type: 'filter', - 'x-component-props': { - fields: fields.filter(field => ctx.listFields.includes(field.id) && field.get('filterable')), - }, - }, - } - return properties; - }, -}; - -export default async (ctx, next) => { - const { resourceName, resourceKey, values = {} } = ctx.action.params; - const [View, Collection, Field, Action] = ctx.db.getModels(['views', 'collections', 'fields', 'actions']) as ModelCtor[]; - const collection = await Collection.findOne({ - where: { - name: resourceName, - }, - }); - let view = await View.findOne(View.parseApiJson({ - filter: { - collection_name: resourceName, - name: resourceKey, - }, - // fields: { - // appends: ['actions', 'fields'], - // }, - })); - let throughName; - const { resourceKey: resourceKey2, associatedName, resourceFieldName, associatedKey } = values; - // TODO: 暂时不处理 developerMode 和 internal 的情况 - const permissions = (await ctx.ac.isRoot() || collection.developerMode || collection.internal) - ? await ctx.ac.getRootPermissions() - : await ctx.ac.can(resourceName).permissions(); - ctx.listFields = []; - ctx.createFields = []; - ctx.updateFields = []; - ctx.allowedActions = []; - for (const action of permissions.actions) { - ctx.allowedActions.push(action.name); - } - // console.log(ctx.allowedActions); - for (const permissionField of permissions.fields) { - const pfc = permissionField.actions; - if (pfc.includes(`${resourceName}:list`)) { - ctx.listFields.push(permissionField.field_id); - } - if (pfc.includes(`${resourceName}:create`)) { - ctx.createFields.push(permissionField.field_id); - } - if (pfc.includes(`${resourceName}:update`)) { - ctx.updateFields.push(permissionField.field_id); - } - } - // console.log({ - // a: (await ctx.ac.isRoot() || collection.developerMode || collection.internal), - // listFields: ctx.listFields, - // createFields: ctx.createFields, - // updateFields: ctx.updateFields, - // }) - if (associatedName) { - const table = ctx.db.getTable(associatedName); - const resourceField = table.getField(resourceFieldName); - if (resourceField instanceof BELONGSTOMANY) { - // console.log({associatedName, resourceField}); - throughName = resourceField.options.through; - } - } - if (!view) { - // 如果不存在 view,新建一个 - view = new View({ type: resourceKey, template: 'FilterForm' }); - } - - // const where: any = { - // developerMode: ctx.state.developerMode, - // } - const filter: any = {} - if (!ctx.state.developerMode) { - filter.developerMode = { '$isFalsy': true } - } - if (!view.get('draggable')) { - filter.type = { - not: 'sort', - }; - } - const fieldOptions = Field.parseApiJson({ - filter, - sort: 'sort', - }); - let fields = await collection.getFields(fieldOptions); - fields = fields.filter(field => { - if (field.get('interface') === 'linkTo') { - if (throughName && throughName === field.get('through')) { - return false; - } - } - return true; - }) - const options = Action.parseApiJson(ctx.state.developerMode ? { - filter: {}, - sort: 'sort', - } : { - filter: { - developerMode: { '$isFalsy': true }, - }, - sort: 'sort', - }); - const actions = await collection.getActions(options); - let actionNames = view.get('actionNames') || []; - if (actionNames.length === 0 && resourceKey !== 'permissionTable') { - actionNames = ['filter', 'create', 'destroy']; - } - const defaultTabs = await collection.getTabs({ - where: { - id: { [Op.in]: permissions.tabs }, - }, - order: [['sort', 'asc']], - }); - - const defaultTab = defaultTabs.find(tab => tab.default) || get(defaultTabs, [0]); - - view.setDataValue('defaultTabName', get(defaultTab, ['name'])); - if (view.get('type') === 'table') { - view.setDataValue('rowViewName', 'form'); - } - if (view.get('type') === 'calendar') { - view.setDataValue('template', 'Calendar'); - view.setDataValue('rowViewName', 'form'); - } - if (view.get('updateViewName')) { - view.setDataValue('rowViewName', view.get('updateViewName')); - } - if (!view.get('template')) { - if (view.get('type') === 'table') { - view.setDataValue('template', 'Table'); - } else if (view.get('type') === 'calendar') { - view.setDataValue('template', 'Calendar'); - } - } - // view.setDataValue('viewCollectionName', view.collection_name); - let title = collection.get('title'); - const mode = get(ctx.action.params, ['values', 'mode'], ctx.action.params.mode); - ctx.formMode = mode; - if (mode === 'update') { - title = `编辑${title}`; - } else { - title = `新增${title}`; - } - const viewType = view.get('type'); - const actionDefaultParams: any = {}; - if (resourceName === 'collections') { - actionDefaultParams.sort = ['sort']; - } - if (view.filter) { - actionDefaultParams.filter = view.filter; - } - const appends = []; - - const others: any = {}; - - if (viewType === 'form') { - if (associatedName === 'automations' && resourceFieldName === 'jobs' && mode !== 'update') { - const Automation = ctx.db.getModel('automations'); - others['initialValues'] = { - automation: await Automation.findByPk(associatedKey), - }; - } - } - - for (const field of fields) { - if (!['subTable', 'linkTo', 'attachment', 'createdBy', 'updatedBy', 'chinaRegion'].includes(field.get('interface'))) { - continue; - } - let showInKey; - switch (viewType) { - case 'table': - showInKey = 'showInTable'; - break; - case 'form': - showInKey = 'showInForm'; - break; - case 'details': - showInKey = 'showInDetail'; - break; - } - if (showInKey && field.get(`component.${showInKey}`)) { - appends.push(field.name); - if (field.get('interface') === 'attachment') { - appends.push(`${field.name}.storage`); - } - if (field.get('interface') === 'subTable') { - const children = await field.getChildren(); - // console.log(children); - for (const child of children) { - if (!['subTable', 'linkTo', 'attachment', 'updatedBy', 'createdBy'].includes(child.get('interface'))) { - continue; - } - appends.push(`${field.name}.${child.name}`); - } - } - } - } - actionDefaultParams['fields[appends]'] = appends.join(','); - if (resourceFieldName === 'pages' && resourceKey === 'permissionTable') { - ctx.body = { - ...view.get(), - title, - actionDefaultParams, - original: fields, - disableRowClick: true, - fields: [ - { - "title": "页面", - "name": "title", - "interface": "string", - "type": "string", - "parent_id": null, - "required": true, - "developerMode": false, - "component": { - "type": "string", - "className": "drag-visible", - "showInForm": true, - "showInTable": true, - "showInDetail": true - }, - "dataIndex": ["title"] - }, - { - "title": "访问权限", - "name": "accessible", - "interface": "boolean", - "type": "boolean", - "parent_id": null, - "required": true, - "editable": true, - "resource": 'roles.pages', - "developerMode": false, - "component": { - "type": "boolean", - "showInTable": true, - }, - "dataIndex": ["accessible"] - } - ], - }; - } else if (resourceFieldName === 'collections' && resourceKey === 'permissionTable') { - ctx.body = { - ...view.get(), - title, - actionDefaultParams, - original: fields, - rowKey: 'name', - fields: [ - { - "title": "数据表名称", - "name": "title", - "interface": "string", - "type": "string", - "parent_id": null, - "required": true, - "developerMode": false, - "component": { - "type": "string", - "className": "drag-visible", - "showInForm": true, - "showInTable": true, - "showInDetail": true - }, - "dataIndex": ["title"] - }, - { - "title": "描述", - "name": "permissions[0].description", - "interface": "string", - "type": "string", - "parent_id": null, - "required": true, - "developerMode": false, - "component": { - "type": "string", - "className": "drag-visible", - "showInForm": true, - "showInTable": true, - "showInDetail": true - }, - "dataIndex": ["permissions", 0, 'description'] - } - ], - }; - } else if ( - (resourceFieldName === 'collections' && resourceKey === 'permissionForm') - || - (resourceFieldName === 'roles' && resourceKey === 'permissionForm') - ) { - ctx.body = { - ...view.get(), - title, - actionDefaultParams, - original: fields, - fields: { - actions: { - type: 'permissions.actions', - title: '数据操作权限', - 'x-linkages': [ - { - type: "value:schema", - target: "actions", - schema: { - "x-component-props": { - resourceKey: resourceFieldName === 'roles' ? associatedKey : "{{ $form.values && $form.values.resourceKey }}" - }, - }, - }, - ], - 'x-component-props': { - dataSource: [], - }, - }, - fields: { - type: 'permissions.fields', - title: '字段权限', - 'x-linkages': [ - { - type: "value:schema", - target: "fields", - schema: { - "x-component-props": { - resourceKey: resourceFieldName === 'roles' ? associatedKey : "{{ $form.values && $form.values.resourceKey }}" - }, - }, - }, - ], - 'x-component-props': { - dataSource: [], - } - }, - tabs: { - type: 'permissions.tabs', - title: '标签页权限', - 'x-linkages': [ - { - type: "value:schema", - target: "tabs", - schema: { - "x-component-props": { - resourceKey: resourceFieldName === 'roles' ? associatedKey : "{{ $form.values && $form.values.resourceKey }}" - }, - }, - }, - ], - 'x-component-props': { - dataSource: [], - } - }, - description: { - type: 'textarea', - title: '权限描述', - }, - }, - }; - } else { - let allowedUpdate = false; - if (view.type === 'details' && await ctx.ac.can(resourceName).act('update').one(resourceKey2)) { - allowedUpdate = true; - } - ctx.body = { - ...view.get(), - ...others, - title, - actionDefaultParams, - original: fields, - fields: await (transforms[view.type] || transforms.table)(fields, ctx), - actions: actions.filter(action => { - if (view.type === 'details' && action.name === 'update') { - return allowedUpdate; - } - return actionNames.includes(action.name) && ctx.allowedActions.includes(`${resourceName}:${action.name}`); - }).map(action => ({ - ...action.toJSON(), - ...action.options, - // viewCollectionName: action.collection_name, - })), - }; - } - await next(); -}; diff --git a/packages/plugin-pages/src/actions/menus.ts b/packages/plugin-pages/src/actions/menus.ts deleted file mode 100644 index 0c2caeb26..000000000 --- a/packages/plugin-pages/src/actions/menus.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Model, ModelCtor } from '@nocobase/database'; -import { actions } from '@nocobase/actions'; -import { flatToTree } from '../utils'; - -export const list = async (ctx, next) => { - await actions.common.list(ctx, async () => { - const items = ctx.body.rows as any; - ctx.body.rows = flatToTree(items.map(item => item.toJSON()), { - id: 'id', - parentId: 'parent_id', - children: 'children', - }); - }); - await next(); -} \ No newline at end of file diff --git a/packages/plugin-pages/src/actions/roles.menus.ts b/packages/plugin-pages/src/actions/roles.menus.ts deleted file mode 100644 index 2d7c2765e..000000000 --- a/packages/plugin-pages/src/actions/roles.menus.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Op } from 'sequelize'; -import { actions } from '@nocobase/actions'; -import _ from 'lodash'; -import { flatToTree } from '../utils'; - -export async function list(ctx: any, next: actions.Next) { - const { associated, associatedKey } = ctx.action.params; - // TODO: 暂时 action 中间件就这么写了 - ctx.action.mergeParams({ - associated: null - }); - await actions.common.list(ctx, async () => { - }); - const MenuPermission = ctx.db.getModel('menus_permissions'); - const rows = ctx.body.rows as any; - for (const row of rows) { - row.setDataValue('associatedKey', associatedKey); - const mp = await MenuPermission.findOne({ - where: { - role_id: associatedKey, - menu_id: row.id, - }, - }); - row.setDataValue('accessible', !!mp); - } - ctx.body.rows = flatToTree(rows.map(item => item.toJSON()), { - id: 'id', - parentId: 'parent_id', - children: 'children', - }); - await next(); -} diff --git a/packages/plugin-pages/src/actions/roles.pages.ts b/packages/plugin-pages/src/actions/roles.pages.ts deleted file mode 100644 index b9cc5fd84..000000000 --- a/packages/plugin-pages/src/actions/roles.pages.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { actions } from '@nocobase/actions'; -import Database from '@nocobase/database'; -import { flatToTree } from '../utils'; -import { Op } from 'sequelize'; - -async function getRoutes(ctx) { - const database: Database = ctx.db; - const Page = database.getModel('pages'); - const View = database.getModel('views'); - const Collection = database.getModel('collections'); - let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? { - filter: { - 'parent_id.$notNull': true, - }, - sort: ['sort'], - } : { - filter: { - 'parent_id.$notNull': true, - developerMode: { '$isFalsy': true }, - }, - sort: ['sort'], - })); - const items = []; - for (const page of pages) { - items.push({ - routable_type: 'pages', - routable_id: page.id, - }); - if (page.get('path') === '/collections') { - const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? { - filter: { - showInDataMenu: true, - }, - sort: ['sort'], - } : { - filter: { - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - }, - sort: ['sort'], - })); - for (const collection of collections) { - items.push({ - routable_type: 'collections', - routable_id: collection.id, - }); - const views = await collection.getViews({ - where: { - [Op.or]: [ - { showInDataMenu: true }, - { default: true } - ] - }, - order: [['sort', 'asc']] - }); - if (views.length > 1) { - for (const view of views) { - items.push({ - routable_id: view.id, - routable_type: 'views', - }); - } - } - } - } else if (page.get('path') === '/users/users') { - const userViews = await View.findAll(View.parseApiJson(ctx.state.developerMode ? { - filter: { - showInDataMenu: true, - collection_name: 'users', - }, - sort: ['sort'], - } : { - filter: { - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - collection_name: 'users', - }, - sort: ['sort'], - })); - if (userViews.length > 1) { - for (const view of userViews) { - items.push({ - routable_id: view.id, - routable_type: 'views', - }); - } - } - } - } - return items; -} - -export async function list(ctx: actions.Context, next: actions.Next) { - const database: Database = ctx.db; - const { associatedKey, associated } = ctx.action.params; - const Page = database.getModel('pages'); - const View = database.getModel('views'); - const Collection = database.getModel('collections'); - // TODO(optimize): isRoot 的判断需要在内部完成,尽量不要交给调用者 - const isRoot = ctx.ac.constructor.isRoot(associated); - const routesPermissionsMap = new Map(); - if (!isRoot) { - const routesPermissions = await associated.getRoutes(); - - routesPermissions.forEach(permission => { - routesPermissionsMap.set(`${permission.routable_type}:${permission.routable_id}`, permission); - }); - } - let pages = await Page.findAll(Page.parseApiJson(ctx.state.developerMode ? { - filter: { - 'parent_id.$notNull': true, - }, - sort: ['sort'], - } : { - filter: { - 'parent_id.$notNull': true, - developerMode: { '$isFalsy': true }, - }, - sort: ['sort'], - })); - const items = []; - for (const page of pages) { - items.push({ - id: page.id, - key: `page-${page.id}`, - title: page.title, - tableName: 'pages', - parent_id: `page-${page.parent_id}`, - associatedKey, - accessible: isRoot || routesPermissionsMap.has(`pages:${page.id}`), // TODO 对接权限 - }); - if (page.get('path') === '/collections') { - const collections = await Collection.findAll(Collection.parseApiJson(ctx.state.developerMode ? { - filter: { - showInDataMenu: true, - }, - sort: ['sort'], - } : { - filter: { - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - }, - sort: ['sort'], - })); - for (const collection of collections) { - items.push({ - associatedKey, - id: collection.id, - key: `collection-${collection.id}`, - tableName: 'collections', - title: collection.get('title'), - parent_id: `page-${page.id}`, - accessible: isRoot || routesPermissionsMap.has(`collections:${collection.id}`), // TODO 对接权限 - }); - const views = await collection.getViews({ - where: { - [Op.or]: [ - { showInDataMenu: true }, - { default: true } - ] - }, - order: [['sort', 'asc']] - }); - if (views.length > 1) { - for (const view of views) { - items.push({ - associatedKey, - id: view.id, - tableName: 'views', - title: view.title, - key: `view-${view.id}`, - parent_id: `collection-${collection.id}`, - accessible: isRoot || routesPermissionsMap.has(`views:${view.id}`), // TODO 对接权限 - }); - } - } - } - } else if (page.get('path') === '/users/users') { - const userViews = await View.findAll(View.parseApiJson(ctx.state.developerMode ? { - filter: { - showInDataMenu: true, - collection_name: 'users', - }, - sort: ['sort'], - } : { - filter: { - collection_name: 'users', - developerMode: { '$isFalsy': true }, - showInDataMenu: true, - }, - sort: ['sort'], - })); - if (userViews.length > 1) { - for (const view of userViews) { - items.push({ - associatedKey, - id: view.id, - tableName: 'views', - title: view.title, - key: `view-${view.id}`, - parent_id: `page-${page.id}`, - accessible: isRoot || routesPermissionsMap.has(`views:${view.id}`), // TODO 对接权限 - }); - } - } - } - } - const data = flatToTree(items, { - id: 'key', - parentId: 'parent_id', - children: 'children', - }); - ctx.body = data; - // TODO: 暂时 action 中间件就这么写了 - // ctx.action.mergeParams({associated: null}); - // const { associatedKey } = ctx.action.params; - // ctx.action.mergeParams({ - // filter: { - // 'parent_id.$notNull': true, - // } - // }) - // const done = async () => { - // ctx.body.rows = ctx.body.rows.map(row => { - // row.setDataValue('tableName', 'pages'); - // row.setDataValue('associatedKey', parseInt(associatedKey)); - // return row.get(); - // }); - // console.log(ctx.body.rows); - // await next(); - // } - // return actions.common.list(ctx, done); -} - -export async function update(ctx: actions.Context, next: actions.Next) { - const { - associated, - resourceKey, - values: { - tableName, - accessible - } - } = ctx.action.params; - - if (!resourceKey) { - if (accessible === false) { - await associated.updateAssociations({ - routes: [], - }); - } else if (accessible === true) { - const routes = await getRoutes(ctx); - // console.log(routes); - await associated.updateAssociations({ - routes, - }); - } - ctx.body = {}; - return next(); - } - - // console.log(ctx.action.params, { routable_type: tableName, routable_id: resourceKey }); - let [route] = await associated.getRoutes({ - where: { routable_type: tableName, routable_id: resourceKey }, - limit: 1 - }); - if (accessible) { - if (!route) { - route = await associated.createRoute({ routable_type: tableName, routable_id: resourceKey }); - } - ctx.body = route; - } else { - if (route) { - await route.destroy(); - } - } - - await next(); -} diff --git a/packages/plugin-pages/src/actions/views_v2.ts b/packages/plugin-pages/src/actions/views_v2.ts deleted file mode 100644 index d104c7f11..000000000 --- a/packages/plugin-pages/src/actions/views_v2.ts +++ /dev/null @@ -1,398 +0,0 @@ -import { ResourceOptions } from '@nocobase/resourcer'; -import { Model, ModelCtor } from '@nocobase/database'; -import actions from '@nocobase/actions'; -import { merge } from '../utils'; -import _ from 'lodash'; -import { getViewTypeLinkages } from '../views'; - -export const getInfo = async (ctx: actions.Context, next) => { - const { resourceKey } = ctx.action.params; - const View = ctx.db.getModel('views_v2') as ModelCtor; - const Field = ctx.db.getModel('fields') as ModelCtor; - let primaryKey: string; - let viewName: string; - let collectionName: string; - let associatedName: string; - let resourceName: string; - let associationField: any; - - if (!resourceKey.includes('.')) { - const view = await View.findOne({ - where: { - name: resourceKey, - }, - }); - const viewData: any = view.toJSON(); - for (const [key, value] of Object.entries(viewData[`x-${viewData.type}-props`] || {})) { - if (_.get(viewData, key) === null || _.get(viewData, key) === undefined) { - _.set(viewData, key, value); - } - } - ctx.body = viewData; - return next(); - } - - if (resourceKey.includes('.')) { - const keys = resourceKey.split('.'); - viewName = keys.pop(); - const key1 = keys.shift(); - const key2 = keys.join('.'); - // const [key1, key2] = keys; - if (key2) { - const field = ctx.db.getTable(key1).getField(key2); - associationField = await Field.findOne({ - where: { - collection_name: key1, - name: key2, - }, - }); - collectionName = field.options.target; - associatedName = key1; - resourceName = key2; - } else { - collectionName = key1; - } - } - // console.log({viewName, collectionName, associatedName}) - let view = await View.findOne({ - where: { - name: viewName, - collection_name: collectionName - }, - }); - - if (view && view.type === 'form') { - const statusable = !!ctx.db.getTable(view.collection_name).getField('status'); - if (statusable) { - view.setDataValue('statusable', statusable); - } - } - - const Collection = ctx.db.getModel('collections') as ModelCtor; - const M = ctx.db.getModel(collectionName) as ModelCtor; - - // if (!view && viewName === 'descriptions') { - // view = await View.findOne({ - // where: { - // name: 'form', - // collection_name: collectionName - // }, - // }); - // view.type = 'descriptions'; - // } - let viewData: any = view ? view.toJSON() : {}; - - if (view) { - viewData.fields = view.get(`x-${view.type}-props.fields`) || view.get('fields') || []; - viewData.actions = view.get(`x-${view.type}-props.actions`) || view.get('actions') || []; - viewData.details = view.get(`x-${view.type}-props.details`) || view.get('details') || []; - } - - if (!view) { - const collection = await Collection.findOne({ - where: { - name: collectionName, - } - }); - const fields = await collection.getFields({ - order: [['sort', 'asc']], - }); - if (viewName === 'table') { - viewData = { - collection_name: collectionName, - type: 'table', - name: 'table', - title: '全部数据', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - // labelField: 'title', - fields: fields.filter(field => { - return field.name !== 'action_logs'; - }).map(field => field.name), - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }; - } else if (viewName === 'form') { - viewData = { - collection_name: collectionName, - type: 'form', - name: 'form', - title: '表单', - actions: [ - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - } - ], - // labelField: 'title', - fields: fields.filter(field => { - return field.name !== 'action_logs'; - }).map(field => field.name), - }; - } else if (viewName === 'descriptions') { - viewData = { - collection_name: collectionName, - type: 'descriptions', - name: 'descriptions', - title: '详情', - // labelField: 'title', - actions: [], - fields: fields.filter(field => { - return field.name !== 'action_logs'; - }).map(field => field.name), - }; - } - } - - // ctx.body = {}; - // return next(); - - const fields = []; - for (const field of viewData.fields || []) { - let fieldName: any; - let json: any; - if (typeof field === 'string') { - fieldName = field; - } else if (typeof field === 'object') { - // console.log({field}); - if (field.field) { - const { field: f, ...others } = field; - fieldName = f.name; - json = { ...others }; - } else if (field.name) { - fieldName = field.name; - json = field; - } - } - const model = await Field.findOne({ - where: { - collection_name: viewData.collection_name, - name: fieldName, - }, - }); - if (model) { - const target = model.get('target'); - if (target && model.get('interface') === 'subTable') { - const children = await Field.findAll(Field.parseApiJson({ - filter: { - collection_name: target, - 'name.ne': 'action_logs', - }, - sort: 'sort', - })); - if (children.length) { - model.setDataValue('children', children); - } - } - model.setDataValue('dataIndex', model.name.split('.')); - if (typeof field === 'object') { - json = merge(model.toJSON(), json); - } else { - json = model.toJSON(); - } - } - if (!json.name) { - continue; - } - // console.log({field, json}) - if (json.name === 'type' && json.collection_name === 'views_v2') { - json.linkages = getViewTypeLinkages(); - } - json && fields.push(json); - } - - const actions = []; - const toFields = async (values = []) => { - const fields = []; - for (const value of values) { - if (typeof value === 'string') { - const model = await Field.findOne({ - where: { - collection_name: viewData.collection_name, - name: value, - }, - }); - if (model) { - fields.push(model); - } - } - } - return fields; - } - if (viewData.actions) { - const parts = resourceKey.split('.'); - parts.pop(); - for (const action of viewData.actions || []) { - if (action.viewName) { - if (!action.viewName.includes('.')) { - action.viewName = `${parts.join('.')}.${action.viewName}`; - } - } - if (action.view && action.view.name) { - action.viewName = `${parts.join('.')}.${action.view.name}`; - } - if (action.fields) { - action.fields = await toFields(action.fields); - } - actions.push({ - ...action, - }) - } - } - const details = []; - for (const item of viewData.details || []) { - let vName: string; - if (typeof item === 'string') { - vName = item; - const sView = await View.findOne({ - where: { - collection_name: viewData.collection_name, - name: vName, - } - }); - if (sView) { - details.push({ - title: sView.title, - views: [sView], - }); - } else { - details.push({ - title: '表单', - views: [{ - name: item, - width: '50%', - }], - }); - } - } else if (typeof item === 'object') { - if (item.views) { - // TODO 标签页多视图支持 - } else if (item.view) { - const { view: v, ...others } = item; - vName = v.name; - const sView = await View.findOne({ - where: { - collection_name: viewData.collection_name, - name: vName, - } - }); - sView && details.push({ - ...others, - views: [sView], - }); - } - } - } - const data: any = { - ...viewData, - details, - actions, - fields, - }; - if (data.target_field_id) { - const targetField = await Field.findByPk(data.target_field_id); - data.targetFieldName = targetField.name; - } - if (data.target_view_id) { - const targetView = await View.findByPk(data.target_view_id); - data.targetViewName = targetView.name; - } - if (associationField) { - data.associationField = associationField; - } - if (data.dataSourceType === 'association') { - const field = await Field.findOne({ - where: { - name: data.targetFieldName, - collection_name: collectionName, - } - }); - // console.log(field, data.targetFieldName, collectionName) - const targetViewName = `${field.get('target')}.${data.targetViewName}`; - const resourceName = `${collectionName}.${data.targetFieldName}`; - ctx.action.mergeParams({ - resourceKey: targetViewName, - }); - await getInfo(ctx, async () => { }); - const body = ctx.body as any; - const actions = body.actions.map(action => { - if (action.viewName) { - const names = action.viewName.split('.'); - action.viewName = `${resourceName}.${names.pop()}`; - } - return action; - }); - const item = { - ...body, - associationField: field, - resourceName, - actions, - rowKey: resourceKey === 'roles.collections' ? 'name' : body.rowKey, - } - ctx.body = item; - return next(); - } else { - data.rowKey = data.rowKey || M.primaryKeyAttribute; - if (associatedName) { - data.resourceName = `${associatedName}.${resourceName}`; - } else { - data.resourceName = collectionName; - } - data.appends = data.fields.filter(field => { - return ['hasMany', 'hasOne', 'belongsToMany', 'belongsTo'].includes(field.type); - }).map(field => field.name); - - for (const field of data.fields) { - if (field.interface === 'attachment') { - data.appends.push(`${field.name}.storage`); - } - } - } - delete data['resourceKey']; - delete data['associatedKey']; - for (const [key, value] of Object.entries(viewData[`x-${viewData.type}-props`] || {})) { - if (_.get(data, key) === null || _.get(data, key) === undefined) { - _.set(data, key, value); - } - } - - if (_.isEmpty(data.sort)) { - data.sort = []; - } - - if (data.type === 'kanban' && data.groupField) { - const groupField = await Field.findOne({ - where: { - name: data.groupField, - collection_name: data.collection_name, - } - }); - data.groupField = groupField; - } - - data.actions = data.actions.map(action => { - if (action.type === 'filter') { - if (!action.fields) { - action.fields = data.fields.filter(({ filterable }) => filterable); - } - } - return action; - }); - - ctx.body = data; - await next(); -}; diff --git a/packages/plugin-pages/src/collections/menus.ts b/packages/plugin-pages/src/collections/menus.ts deleted file mode 100644 index 53acddc52..000000000 --- a/packages/plugin-pages/src/collections/menus.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'menus', - title: '菜单和页面配置', - internal: true, - // model: 'CollectionModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['parent_id'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'randomString', - name: 'name', - title: '缩略名', - required: true, - createOnly: true, - randomString: { - length: 6, - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - developerMode: true, - }, - { - interface: 'linkTo', - multiple: false, - type: 'belongsTo', - name: 'parent', - title: '父级菜单', - target: 'menus', - foreignKey: 'parent_id', - targetKey: 'id', - labelField: 'title', - valueField: 'id', - component: { - type: 'drawerSelect', - 'x-component-props': { - placeholder: '请选择菜单组', - labelField: 'title', - valueField: 'id', - filter: { - type: 'group', - }, - }, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '菜单/页面名称', - required: true, - }, - { - interface: 'icon', - type: 'string', - name: 'icon', - title: '图标', - component: { - type: 'icon', - }, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '菜单类型', - required: true, - dataSource: [ - { value: 'group', label: '菜单组', color: 'red' }, - { value: 'page', label: '页面', color: 'green' }, - { value: 'link', label: '自定义链接', color: 'orange' }, - ], - component: { - 'x-linkages': [ - { - "type": "value:visible", - "target": "views", - "condition": "{{ $self.value === 'page' }}" - }, - { - "type": "value:visible", - "target": "url", - "condition": "{{ $self.value === 'link' }}" - }, - ], - }, - }, - { - interface: 'string', - type: 'string', - name: 'url', - title: '链接地址', - required: true, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - developerMode: true, - defaultValue: false, - }, - { - interface: 'json', - type: 'json', - name: 'views', - title: '显示在页面里的视图', - target: 'menus_views_v2', - component: { - type: 'subTable', - 'x-component-props': { - viewName: 'menus.views', - }, - 'x-linkages': [ - { - type: 'value:schema', - target: 'views', - schema: { - 'x-component-props': { - __parent: '{{ $form.values && $form.values.associatedKey }}', - associatedKey: "{{ $form.values && $form.values.id }}" - }, - }, - }, - ], - }, - }, - // { - // interface: 'subTable', - // type: 'hasMany', - // name: 'menus_views', - // title: '显示在页面里的视图', - // target: 'menus_views_v2', - // sourceKey: 'id', - // foreignKey: 'menu_id', - // component: { - // type: 'subTable', - // 'x-component-props': { - // viewName: 'menus.menus_views', - // filter: { - // or: [ - // { - // 'type.neq': 'descriptions', - // }, - // { - // 'data_source_type.neq': 'association', - // } - // ], - // }, - // }, - // 'x-linkages': [ - // { - // type: 'value:schema', - // target: 'menus_views', - // schema: { - // 'x-component-props': { - // __parent: '{{ $form.values && $form.values.associatedKey }}', - // associatedKey: "{{ $form.values && $form.values.id }}" - // }, - // }, - // }, - // ], - // }, - // }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - { - type: 'hasMany', - name: 'children', - target: 'menus', - foreignKey: 'parent_id', - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - ], - views: [ - { - type: 'form', - name: 'form', - title: '表单', - template: 'DrawerForm', - }, - { - type: 'details', - name: 'details', - title: '详情', - template: 'Details', - actionNames: ['update'], - }, - { - type: 'table', - name: 'simple', - title: '简易模式', - template: 'SimpleTable', - default: true, - mode: 'simple', - actionNames: ['create', 'destroy'], - detailsViewName: 'details', - updateViewName: 'form', - paginated: false, - }, - { - type: 'table', - name: 'table', - title: '列表', - template: 'Table', - actionNames: ['create', 'destroy'], - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - expandable: { - expandIconColumnIndex: 3, - }, - paginated: false, - fields: ['sort', 'title', 'icon', 'type'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['sort'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: ['type', 'parent', 'title', 'icon', 'url', 'views'], - }, - { - developerMode: true, - type: 'table', - name: 'permissions_table', - title: '权限表格', - labelField: 'title', - actions: [], - expandable: {}, - fields: [ - 'title', - { - interface: 'boolean', - name: 'accessible', - type: 'boolean', - title: '允许访问', - dataIndex: ['accessible'], - editable: true, - resourceName: 'roles.menus', - component: { - type: 'checkbox', - }, - }, - ], - // detailsOpenMode: 'drawer', // window - details: [], - sort: ['sort'], - }, - { - developerMode: true, - type: 'form', - name: 'permissions_form', - title: '权限表单', - fields: ['type', 'title'], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/menus_views_v2.ts b/packages/plugin-pages/src/collections/menus_views_v2.ts deleted file mode 100644 index 3f180dbc5..000000000 --- a/packages/plugin-pages/src/collections/menus_views_v2.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'menus_views_v2', - title: '页面视图', - internal: true, - // model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'linkTo', - type: 'belongsTo', - name: 'view', - target: 'views_v2', - title: '视图', - labelField: 'title', - valueField: 'id', - multiple: false, - viewName: 'views_v2.form', - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'views_v2.table', - resourceName: 'views_v2', - labelField: 'title', - valueField: 'id', - }, - 'x-linkages': [ - { - "type": "value:visible", - "target": "returnType", - "condition": "{{ $self.value && $self.value.type === 'form' }}" - }, - { - "type": "value:visible", - "target": "redirect", - "condition": "{{ $self.value && $self.value.type === 'form' }}" - }, - { - "type": "value:visible", - "target": "message", - "condition": "{{ $self.value && $self.value.type === 'form' }}" - }, - { - "type": "value:visible", - "target": "draft.returnType", - "condition": "{{ $self.value && $self.value.type === 'form' }}" - }, - ], - }, - }, - { - interface: 'string', - type: 'virtual', - name: 'view.collection.title', - title: '所属数据表', - }, - { - interface: 'radio', - type: 'string', - name: 'width', - title: '宽度', - dataSource: [ - { label: '50%', value: '50%' }, - { label: '100%', value: '100%' }, - ], - component: { - type: 'radio', - 'x-linkages': [ - { - "type": "value:visible", - "target": "float", - "condition": "{{ $self.value && $self.value === '50%' }}" - }, - ] - }, - }, - { - interface: 'radio', - type: 'string', - name: 'float', - title: '位置', - dataSource: [ - { label: '左边', value: 'left' }, - { label: '右边', value: 'right' }, - ], - component: { - type: 'radio', - }, - }, - { - interface: 'radio', - type: 'string', - name: 'returnType', - title: '表单提交成功后', - dataSource: [ - { label: '显示文字信息', value: 'message' }, - { label: '跳转到页面', value: 'redirect' }, - ], - component: { - type: 'radio', - 'x-linkages': [ - { - "type": "value:visible", - "target": "message", - "condition": "{{ $self.value === 'message' }}" - }, - { - "type": "value:visible", - "target": "redirect", - "condition": "{{ $self.value === 'redirect' }}" - }, - ], - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'redirect', - target: 'menus', - title: '跳转到页面', - labelField: 'title', - valueField: 'id', - multiple: false, - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'menus.table', - resourceName: 'menus', - labelField: 'title', - valueField: 'id', - }, - }, - }, - { - interface: 'markdown', - type: 'json', - title: '显示文字信息', - name: 'message', - component: { - type: 'markdown', - }, - }, - { - interface: 'radio', - type: 'virtual', - name: 'draft.returnType', - title: '草稿提交成功后', - dataSource: [ - { label: '显示文字信息', value: 'message' }, - { label: '跳转到页面', value: 'redirect' }, - ], - component: { - type: 'radio', - 'x-linkages': [ - { - "type": "value:visible", - "target": "draft.message", - "condition": "{{ $self.value === 'message' }}" - }, - { - "type": "value:visible", - "target": "draft.redirect", - "condition": "{{ $self.value === 'redirect' }}" - }, - ], - }, - }, - { - interface: 'linkTo', - type: 'virtual', - name: 'draft.redirect', - target: 'menus', - title: '跳转到页面', - labelField: 'title', - valueField: 'id', - multiple: false, - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'menus.table', - resourceName: 'menus', - labelField: 'title', - valueField: 'id', - }, - }, - }, - { - interface: 'markdown', - type: 'virtual', - title: '显示文字信息', - name: 'draft.message', - component: { - type: 'markdown', - }, - } - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - draggable: true, - actions: [ - { - name: 'add', - type: 'add', - title: '选择', - transform: { - 'data': 'view', - 'data.title': 'title', - }, - viewName: 'views_v2.table', - componentProps: { - type: 'primary', - }, - filter: { - and: [ - { 'type.ne': 'descriptions' }, - { 'data_source_type.ne': 'association' }, - ] - }, - }, - { - name: 'create', - type: 'create', - title: '新增', - transform: { - 'data': 'view', - 'data.title': 'title', - }, - componentProps: { - type: 'default', - }, - viewName: 'views_v2.form', - }, - { - name: 'destroy', - type: 'destroy', - title: '移除', - }, - ], - fields: [ - 'view', - 'view.collection.title', - 'width', - 'float' - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'view', - 'width', - 'float', - 'returnType', - 'redirect', - 'message', - 'draft.returnType', - 'draft.redirect', - 'draft.message', - ], - }, - { - developerMode: true, - type: 'descriptions', - name: 'descriptions', - title: '详情', - actions: [ - { - name: 'update', - type: 'update', - title: '编辑', - }, - ], - fields: [ - 'view', - 'width', - 'float', - 'returnType', - 'redirect', - 'message', - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/pages.ts b/packages/plugin-pages/src/collections/pages.ts deleted file mode 100644 index da2b9dfed..000000000 --- a/packages/plugin-pages/src/collections/pages.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'pages', - title: '页面配置', - model: 'PageModel', - internal: true, - developerMode: true, - fields: [ - { - interface: 'sort', - type: 'sort', - name: 'sort', - scope: ['parent_id'], - title: '排序', - component: { - type: 'sort', - className: 'drag-visible', - width: 60, - showInTable: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'title', - title: '名称', - component: { - type: 'string', - className: 'drag-visible', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'number', - type: 'integer', - name: 'parent_id', - title: '父级页面', - component: { - type: 'number', - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'string', - type: 'string', - name: 'path', - title: '路径', - unique: true, - component: { - type: 'string', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'icon', - type: 'string', - name: 'icon', - title: '图标', - component: { - type: 'icon', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'select', - type: 'string', - name: 'type', - title: '类型', - dataSource: [ - { - label: '页面', - value: 'page', - }, - { - label: '布局', - value: 'layout', - }, - { - label: '数据集', - value: 'collection', - }, - ], - component: { - type: 'string', - showInTable: true, - showInForm: true, - showInDetail: true, - 'x-linkages': [ - { - "type": "value:visible", - "target": "collection", - "condition": "{{ ['collection'].indexOf($self.value) !== -1 }}" - }, - ] - }, - }, - { - interface: 'select', - type: 'string', - name: 'collection', - title: '属于哪种数据集?', - component: { - type: 'select', - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'select', - type: 'string', - name: 'template', - title: '模板', - dataSource: [ - { - label: '顶部菜单布局', - value: 'TopMenuLayout', - }, - { - label: '左侧菜单布局', - value: 'SideMenuLayout', - }, - ], - component: { - type: 'select', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'showInMenu', - title: '在菜单里显示', - defaultValue: false, - component: { - type: 'checkbox', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'inherit', - title: '继承父级页面内容', - defaultValue: true, - component: { - type: 'checkbox', - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - { - interface: 'linkTo', - type: 'hasMany', - name: 'children', - title: '子页面', - target: 'pages', - foreignKey: 'parent_id', - sourceKey: 'id', - component: { - type: 'drawerSelect', - }, - }, - { - interface: 'number', - type: 'integer', - name: 'viewId', - title: '视图ID', - component: { - type: 'number', - }, - }, - // { - // type: 'belongsToMany', - // name: 'roles', - // through: 'routes_permissions', - // foreignKey: 'routable_id', - // otherKey: 'role_id', - // morphType: 'routable', - // constraints: false, - // }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '元数据', - component: { - type: 'hidden', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/pages_v2.ts b/packages/plugin-pages/src/collections/pages_v2.ts deleted file mode 100644 index 2ffe0c039..000000000 --- a/packages/plugin-pages/src/collections/pages_v2.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'pages_v2', - title: '页面配置', - internal: true, - model: 'BaseModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '页面名称', - required: true, - }, - { - interface: 'string', - type: 'string', - name: 'path', - title: '路径', - required: true, - unique: true, - createOnly: true, - developerMode: true, - }, - { - interface: 'string', - type: 'randomString', - name: 'name', - title: '缩略名', - required: true, - createOnly: true, - randomString: { - length: 6, - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - developerMode: true, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '类型', - required: true, - defaultValue: 'static', - dataSource: [ - { value: 'static', label: '多条数据页面' }, - { value: 'dynamic', label: '单条数据子页面' }, - ], - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - target: 'collections', - targetKey: 'name', - title: '所属数据表', - labelField: 'title', - valueField: 'name', - multiple: false, - component: { - type: 'remoteSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections', - labelField: 'title', - valueField: 'name', - }, - }, - }, - { - interface: 'json', - type: 'virtual', - name: 'views', - title: '显示在页面里的视图', - target: 'pages_views_v2', - component: { - type: 'subTable', - 'x-linkages': [ - { - type: 'value:schema', - target: 'views', - schema: { - 'x-component-props': { - __parent: '{{ $form.values && $form.values.associatedKey }}', - associatedKey: "{{ $form.values && $form.values.id }}" - }, - }, - }, - ], - }, - }, - // { - // interface: 'subTable', - // type: 'hasMany', - // name: 'pages_views', - // target: 'pages_views_v2', - // // sourceKey: 'path', - // title: '显示在页面里的视图(pages_views)', - // component: { - // type: 'subTable', - // 'x-linkages': [ - // { - // type: 'value:schema', - // target: 'pages_views', - // schema: { - // 'x-component-props': { - // __parent: '{{ $form.values && $form.values.associatedKey }}', - // associatedKey: "{{ $form.values && $form.values.id }}" - // }, - // }, - // }, - // ], - // }, - // }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'all_pages', - title: '全部页面', - rowKey: 'path', - labelField: 'title', - fields: ['title', 'collection'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - filter: { - type: 'static', - }, - actions: [ - { - name: 'filter', - type: 'filter', - title: '过滤', - fields: [ - 'title', - ], - } - ], - }, - { - developerMode: true, - type: 'table', - name: 'collection_pages', - title: '数据表页面', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['title'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'table', - name: 'global_pages', - title: '独立页面', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'global_form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - filter: { - collection_name: null, - }, - fields: ['title'], - detailsOpenMode: 'drawer', // window - details: ['global_form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'table', - name: 'permissions_table', - title: '全部数据', - labelField: 'title', - actions: [], - fields: ['title'], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: ['title', 'type', 'views', 'pages_views'], - }, - { - developerMode: true, - type: 'form', - name: 'global_form', - title: '独立页面表单', - fields: ['title', 'views', 'pages_views'], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/pages_views_v2.ts b/packages/plugin-pages/src/collections/pages_views_v2.ts deleted file mode 100644 index 1a8fcb78c..000000000 --- a/packages/plugin-pages/src/collections/pages_views_v2.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'pages_views_v2', - title: '页面视图', - internal: true, - // model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'linkTo', - type: 'belongsTo', - name: 'view', - target: 'views_v2', - title: '视图', - labelField: 'title', - valueField: 'id', - multiple: false, - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'views_v2.table', - resourceName: 'views_v2', - labelField: 'title', - valueField: 'id', - }, - }, - }, - { - interface: 'radio', - type: 'string', - name: 'width', - title: '宽度', - dataSource: [ - { label: '50%', value: '50%' }, - { label: '100%', value: '100%' }, - ], - component: { - type: 'radio', - }, - } - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: [ - 'view', - 'width' - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'view', - 'width' - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/roles.ts b/packages/plugin-pages/src/collections/roles.ts deleted file mode 100644 index f96c022a6..000000000 --- a/packages/plugin-pages/src/collections/roles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { extend } from '@nocobase/database'; - -export default extend({ - name: 'roles', - fields: [ - { - type: 'hasMany', - name: 'routes', - target: 'routes_permissions', - }, - { - interface: 'linkTo', - type: 'belongsToMany', - name: 'pages', - title: '可访问的页面', - through: 'routes_permissions', - foreignKey: 'role_id', - otherKey: 'routable_id', - morphType: 'routable', // 现在没有多态关联的设置,暂时先这么写了 - constraints: false, // 多态关联建立外键约束会有问题 - } - ], -}); diff --git a/packages/plugin-pages/src/collections/routes_permissions.ts b/packages/plugin-pages/src/collections/routes_permissions.ts deleted file mode 100644 index 68cb3114d..000000000 --- a/packages/plugin-pages/src/collections/routes_permissions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'routes_permissions', - title: '页面权限', - developerMode: true, - internal: true, - fields: [ - { - type: 'integer', - name: 'id', - primaryKey: true, - autoIncrement: true, - }, - { - type: 'string', - name: 'routable_type', - title: '关联的表', // 仅 pages 和 collections - }, - { - type: 'integer', - name: 'routable_id', - title: '关联的对象' - }, - { - type: 'belongsTo', - name: 'role' - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/system_settings.ts b/packages/plugin-pages/src/collections/system_settings.ts deleted file mode 100644 index 2ce38aa6f..000000000 --- a/packages/plugin-pages/src/collections/system_settings.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'system_settings', - title: '系统配置', - internal: true, - // model: 'CollectionModel', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '系统名称', - }, - { - interface: 'attachment', - type: 'belongsTo', - name: 'logo', - filterable: false, - target: 'attachments', - title: 'LOGO', - component: { - 'x-component-props': { - multiple: false, - }, - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - ], - views_v2: [ - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: ['title', 'logo'], - }, - { - developerMode: true, - type: 'descriptions', - name: 'descriptions', - title: '详情', - fields: ['title', 'logo'], - actions: [ - { - name: 'update', - type: 'update', - title: '编辑', - viewName: 'form', - }, - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/views_actions_v2.ts b/packages/plugin-pages/src/collections/views_actions_v2.ts deleted file mode 100644 index 7d06deb47..000000000 --- a/packages/plugin-pages/src/collections/views_actions_v2.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'views_actions_v2', - title: '视图操作配置', - internal: true, - // model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '按钮名称', - component: { - type: 'string', - }, - }, - { - interface: 'radio', - type: 'string', - name: 'type', - title: '操作类型', - dataSource: [ - { label: '筛选', value: 'filter' }, - { label: '打印', value: 'print' }, - { label: '导出', value: 'export' }, - { label: '新增', value: 'create' }, - { label: '编辑', value: 'update' }, - { label: '删除', value: 'destroy' }, - ], - component: { - type: 'radio', - 'x-linkages': [ - { - "type": "value:visible", - "target": "view", - "condition": "{{ $self.value === 'create' || $self.value === 'update' }}" - }, - // { - // "type": "value:visible", - // "target": "fields", - // "condition": "{{ $self.value === 'filter' }}" - // }, - ], - }, - }, - { - interface: 'string', - type: 'string', - name: 'name', - title: '操作ID', - component: { - type: 'string', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'view', - target: 'views_v2', - title: '操作绑定的视图', - labelField: 'title', - valueField: 'id', - multiple: false, - required: true, - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'views_v2.table', - resourceName: 'views_v2', - labelField: 'title', - valueField: 'id', - }, - }, - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - draggable: true, - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: [ - 'title', - 'type', - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'type', - 'view' - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/views_details_v2.ts b/packages/plugin-pages/src/collections/views_details_v2.ts deleted file mode 100644 index 1b74c377e..000000000 --- a/packages/plugin-pages/src/collections/views_details_v2.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'views_details_v2', - title: '详情子视图', - internal: true, - // model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '标签页名称', - component: { - type: 'string', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'view', - target: 'views_v2', - title: '标签页显示的视图', - labelField: 'title', - valueField: 'id', - multiple: false, - required: true, - viewName: 'views_v2.form', - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'views_v2.table', - resourceName: 'views_v2', - labelField: 'title', - valueField: 'id', - }, - }, - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - draggable: true, - actions: [ - { - name: 'add', - type: 'add', - title: '选择', - transform: { - 'data': 'view', - 'data.title': 'title', - }, - viewName: 'collections.views_v2.table', - componentProps: { - type: 'primary', - }, - filter: { - or: [ - { 'type': 'form' }, - { 'type': 'descriptions' }, - { 'data_source_type': 'association' }, - ] - }, - }, - { - name: 'create', - type: 'create', - title: '新增', - transform: { - 'data': 'view', - 'data.title': 'title', - }, - viewName: 'collections.views_v2.form', - componentProps: { - type: 'default', - }, - }, - { - name: 'destroy', - type: 'destroy', - title: '移除', - }, - ], - fields: [ - 'title', - 'view', - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'view', - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/views_fields_v2.ts b/packages/plugin-pages/src/collections/views_fields_v2.ts deleted file mode 100644 index 17bc37afa..000000000 --- a/packages/plugin-pages/src/collections/views_fields_v2.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'views_fields_v2', - title: '视图字段配置', - internal: true, - // model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '字段名称', - component: { - type: 'string', - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'field', - target: 'fields', - title: '字段', - labelField: 'title', - valueField: 'id', - multiple: false, - viewName: 'fields.form', - component: { - type: 'drawerSelect', - 'x-component-props': { - viewName: 'fields.table', - resourceName: 'fields', - labelField: 'title', - valueField: 'id', - }, - "x-linkages": [ - { - "type": "value:schema", - "target": "field", - "schema": { - "x-component-props": { - "filter": { - "collection_name": "{{ $self.value && $self.value.collection_name }}" - } - } - } - } - ] - }, - }, - { - interface: 'textarea', - type: 'text', - name: 'tooltip', - title: '提示信息', - component: { - type: 'textarea', - }, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'required', - title: '必填项', - component: { - type: 'checkbox', - }, - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - draggable: true, - actions: [ - { - name: 'add', - type: 'add', - title: '选择', - transform: { - 'data': 'field', - 'data.title': 'title', - }, - viewName: 'collections.fields.table', - componentProps: { - type: 'primary', - }, - }, - { - name: 'destroy', - type: 'destroy', - title: '移除', - }, - ], - fields: [ - 'title', - 'field', - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'field', - 'tooltip', - ], - }, - { - developerMode: true, - type: 'table', - name: 'tableForForm', - title: '全部数据', - labelField: 'title', - draggable: true, - actions: [ - { - name: 'add', - type: 'add', - title: '选择', - transform: { - 'data': 'field', - 'data.title': 'title', - }, - viewName: 'collections.fields.table', - componentProps: { - type: 'primary', - }, - }, - { - name: 'destroy', - type: 'destroy', - title: '移除', - }, - ], - fields: [ - 'title', - 'field', - 'required', - ], - detailsOpenMode: 'drawer', // window - details: ['formForForm'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'formForForm', - title: '表单', - fields: [ - 'title', - 'field', - 'tooltip', - 'required', - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/collections/views_v2.ts b/packages/plugin-pages/src/collections/views_v2.ts deleted file mode 100644 index f4d32104f..000000000 --- a/packages/plugin-pages/src/collections/views_v2.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { TableOptions } from '@nocobase/database'; -import { getTypeFieldOptions, getViewFields } from '../views'; - -const fields = getViewFields(); -const associatedKeyValue = "{{ $form.values && $form.values.collection && $form.values.collection.name }}"; - -export default { - name: 'views_v2', - title: '视图配置', - internal: true, - model: 'BaseModelV2', - developerMode: true, - createdAt: false, - updatedAt: false, - fields: [ - { - interface: 'string', - type: 'string', - name: 'title', - title: '视图名称', - required: true, - }, - getTypeFieldOptions(), - { - interface: 'string', - type: 'randomString', - name: 'name', - title: '缩略名', - required: true, - createOnly: true, - randomString: { - length: 6, - characters: 'abcdefghijklmnopqrstuvwxyz0123456789', - }, - developerMode: true, - }, - { - interface: 'radio', - type: 'string', - name: 'dataSourceType', - title: '数据来源', - defaultValue: 'collection', - dataSource: [ - { label: '所属数据表', value: 'collection' }, - { label: '所属数据表的相关数据', value: 'association' }, - ], - linkages: [ - { - "type": "value:visible", - "target": "targetField", - "condition": "{{ $self.value === 'association' }}" - }, - ...['form', 'descriptions', 'table', 'kanban', 'calendar'].map(type => { - return { - "type": "value:visible", - "target": `x-${type}-props.*`, - "condition": `{{ $form.values.type === '${type}' && $self.value === 'collection' }}` - } - }), - ], - }, - { - interface: 'linkTo', - type: 'belongsTo', - title: '相关数据', - name: 'targetField', - target: 'fields', - required: true, - multiple: false, - component: { - type: 'remoteSelect', - 'x-component-props': { - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'id', - objectValue: true, - filter: { - interface: 'linkTo', - }, - multiple: false, - }, - 'x-linkages': [ - { - "type": "value:visible", - "target": "targetView", - "condition": "{{ !!$self.value }}" - }, - { - type: 'value:schema', - target: 'targetView', - "condition": "{{ !!$self.value }}", - schema: { - 'x-component-props': { - associatedKey: "{{ $self.value && $self.value.target }}" - }, - }, - }, - ], - }, - }, - { - interface: 'linkTo', - type: 'belongsTo', - title: '相关数据表视图', - name: 'targetView', - target: 'views_v2', - required: true, - multiple: false, - component: { - type: 'remoteSelect', - 'x-component-props': { - resourceName: 'collections.views_v2', - labelField: 'title', - valueField: 'id', - multiple: false, - }, - }, - }, - // { - // interface: 'select', - // type: 'virtual', - // title: '相关数据表的视图', - // name: 'targetViewName', - // required: true, - // component: { - // type: 'remoteSelect', - // resourceName: 'collections.views', - // labelField: 'title', - // valueField: 'name', - // 'x-component-props': { - // resourceName: 'collections.views', - // labelField: 'title', - // valueField: 'name', - // multiple: false, - // }, - // }, - // }, - ...fields, - { - interface: 'linkTo', - type: 'belongsTo', - name: 'collection', - target: 'collections', - targetKey: 'name', - title: '所属数据表', - labelField: 'title', - valueField: 'name', - multiple: false, - component: { - type: 'drawerSelect', - showInDetail: true, - showInForm: true, - 'x-component-props': { - resourceName: 'collections', - labelField: 'title', - valueField: 'name', - }, - 'x-linkages': [ - { - type: 'value:schema', - target: '*', - schema: { - 'x-component-props': { - associatedKey: associatedKeyValue, - }, - }, - }, - ], - }, - }, - { - interface: 'json', - type: 'json', - name: 'options', - title: '配置信息', - defaultValue: {}, - developerMode: true, - }, - { - interface: 'boolean', - type: 'boolean', - name: 'developerMode', - title: '开发者模式', - defaultValue: false, - component: { - type: 'boolean', - }, - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: [ - 'title', - 'type', - 'collection', - ], - detailsOpenMode: 'drawer', // window - details: ['form'], - sort: ['id'], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: [ - 'title', - 'type', - 'collection', - 'dataSourceType', - 'targetField', - 'targetView', - ...fields.map(field => field.name), - ], - }, - ], -} as TableOptions; diff --git a/packages/plugin-pages/src/fields/randomString.ts b/packages/plugin-pages/src/fields/randomString.ts deleted file mode 100644 index bb22d4366..000000000 --- a/packages/plugin-pages/src/fields/randomString.ts +++ /dev/null @@ -1,27 +0,0 @@ -import cryptoRandomString from 'crypto-random-string'; -import { STRING, FieldContext } from '@nocobase/database'; -import { - DataTypes -} from 'sequelize'; - -export class RANDOMSTRING extends STRING { - constructor(options: any, context: FieldContext) { - super(options, context); - const Model = context.sourceTable.getModel(); - const { name, randomString } = options; - randomString && Model.addHook('beforeValidate', (model) => { - const { template, ...opts } = randomString; - let value = cryptoRandomString(opts); - if (template && template.includes('%r')) { - value = template.replace('%r', value); - } - if (!model.get(name)) { - model.set(name, value); - } - }); - } - - getDataType() { - return DataTypes.STRING; - } -} diff --git a/packages/plugin-pages/src/models/BaseModel.ts b/packages/plugin-pages/src/models/BaseModel.ts deleted file mode 100644 index aa4eb5a88..000000000 --- a/packages/plugin-pages/src/models/BaseModel.ts +++ /dev/null @@ -1,214 +0,0 @@ -import _ from 'lodash'; -import { getDataTypeKey, Model } from '@nocobase/database'; -import { merge } from '../utils'; - -export function generateName(title?: string): string { - return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; -} - -export class BaseModel extends Model { - - generateName() { - this.set('name', generateName()); - } - - generateNameIfNull() { - if (!this.get('name')) { - this.generateName(); - } - } - - get additionalAttribute() { - const tableOptions = this.database.getTable(this.constructor.name).getOptions(); - return _.get(tableOptions, 'additionalAttribute') || 'options'; - } - - hasGetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 get 方法就直接走 get - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.get; - } - return !!attribute; - } - - hasSetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 set 方法就直接走 set - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.set; - } - return !!attribute; - } - - get(key?: any, options?: any) { - if (typeof key === 'string') { - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.get(column, options); - if (path.length) { - return _.get(value, path); - } - return value; - } - return _.get(super.get(this.additionalAttribute, options) || {}, key); - } - const data = super.get(); - return { - ...(data[this.additionalAttribute] || {}), - ..._.omit(data, [this.additionalAttribute]), - }; - } - - getDataValue(key: any) { - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.getDataValue(column); - if (path.length) { - return _.get(value, path); - } - return value; - } - const options = super.getDataValue(this.additionalAttribute) || {}; - return _.get(options, key); - } - - set(key?: any, value?: any, options: any = {}) { - if (typeof key === 'string') { - // 不处理关系数据 - // @ts-ignore - if (_.get(this.constructor.associations, key)) { - return super.set(key, value, options); - } - // 如果是 object 数据,merge 处理 - if (_.isPlainObject(value)) { - // TODO 需要改进 JSON 字段的内部处理逻辑,暂时这里跳过了特殊的 filter 字段 - if (key !== 'filter') { - // console.log(key, value); - // @ts-ignore - value = merge(this.get(key) || {}, value); - } - } - const [column, ...path] = key.split('.'); - if (!options.raw) { - this.changed(column, true); - } - if (this.hasSetAttribute(column)) { - if (!path.length) { - return super.set(key, value, options); - } - const values = this.get(column, options) || {}; - _.set(values, path, value); - return super.set(column, values, options); - } - // 如果未设置 attribute,存到 additionalAttribute 里 - const opts = this.get(this.additionalAttribute, options) || {}; - _.set(opts, key, value); - if (!options.raw) { - this.changed(this.additionalAttribute, true); - } - return super.set(this.additionalAttribute, opts, options); - } - return super.set(key, value, options); - } - - setDataValue(key: any, value: any) { - // 不处理关系数据 - // @ts-ignore - if (_.get(this.constructor.associations, key)) { - return super.setDataValue(key, value); - } - if (_.isPlainObject(value)) { - // @ts-ignore - value = Utils.merge(this.get(key) || {}, value); - } - const [column, ...path] = key.split('.'); - this.changed(column, true); - if (this.hasSetAttribute(column)) { - if (!path.length) { - return super.setDataValue(key, value); - } - const values = this.get(column) || {}; - _.set(values, path, value); - return super.setDataValue(column, values); - } - const opts = this.get(this.additionalAttribute) || {}; - _.set(opts, key, value); - this.changed(this.additionalAttribute, true); - return super.setDataValue(this.additionalAttribute, opts); - } -} - -export class BaseModel2 extends Model { - - get additionalAttribute() { - const tableOptions = this.database.getTable(this.constructor.name).getOptions(); - return _.get(tableOptions, 'additionalAttribute') || 'options'; - } - - hasGetAttribute(key: string) { - const attribute = this.rawAttributes[key]; - // virtual 如果有 get 方法就直接走 get - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.get; - } - return !!attribute; - } - - hasSetAttribute(key: string) { - // @ts-ignore - if (this.constructor.hasAlias(key)) { - return false; - } - const attribute = this.rawAttributes[key]; - // virtual 如果有 set 方法就直接走 set - if (attribute && attribute.type && getDataTypeKey(attribute.type) === 'VIRTUAL') { - return !!attribute.set; - } - return !!attribute; - } - - get(key?: any, options?: any) { - if (typeof key !== 'string') { - const data = super.get(key); - return { - ..._.omit(data, [this.additionalAttribute]), - ...(data[this.additionalAttribute] || {}), - }; - } - const [column, ...path] = key.split('.'); - if (this.hasGetAttribute(column)) { - const value = super.get(column, options); - if (path.length) { - return _.get(value, path); - } - return value; - } - return _.get(super.get(this.additionalAttribute, options) || {}, key); - } - - set(key?: any, value?: any, options: any = {}) { - if (typeof key !== 'string') { - return super.set(key, value, options); - } - // @ts-ignore - if (this.constructor.hasAlias(key)) { - return this; - } - const [column] = key.split('.'); - if (this.hasSetAttribute(column)) { - return super.set(key, value, options); - } - return super.set(`${this.additionalAttribute}.${key}`, value, options); - } - - // getDataValue(key: any) { - // return super.getDataValue(key); - // } - - // setDataValue(key: any, value: any) { - // return super.setDataValue(key, value); - // } -} - -export default BaseModel; diff --git a/packages/plugin-pages/src/server.ts b/packages/plugin-pages/src/server.ts deleted file mode 100644 index 7d486ac91..000000000 --- a/packages/plugin-pages/src/server.ts +++ /dev/null @@ -1,256 +0,0 @@ -import path from 'path'; -import Database from '@nocobase/database'; -import Resourcer from '@nocobase/resourcer'; -import getCollection from './actions/getCollection'; -import getView from './actions/getView'; -import getRoutes from './actions/getRoutes'; -import getPageInfo from './actions/getPageInfo'; -import * as rolesPagesActions from './actions/roles.pages'; -import getCollections from './actions/getCollections'; -import { list as menusList } from './actions/menus'; -import getTree from './actions/getTree'; -import getInfo from './actions/getInfo'; -import { getInfo as viewGetInfo } from './actions/views_v2'; -import { RANDOMSTRING } from './fields/randomString'; -import { registerFields, registerModels } from '@nocobase/database'; -import { BaseModel } from './models/BaseModel' -import * as rolesMenusActions from './actions/roles.menus'; -import _ from 'lodash'; - -export default async function (options = {}) { - const database: Database = this.database; - const resourcer: Resourcer = this.resourcer; - - registerFields({ - RANDOMSTRING, - }); - - registerModels({ - BaseModelV2: BaseModel, - }); - - database.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - resourcer.use(async (ctx, next) => { - const { actionName, resourceName, resourceKey } = ctx.action.params; - if (resourceName === 'system_settings' && actionName === 'get') { - const SystemSetting = database.getModel('system_settings'); - let model = await SystemSetting.findOne(); - if (!model) { - model = await SystemSetting.create(); - } - ctx.action.mergeParams({ - resourceKey: model.id, - }); - } - await next(); - }); - - resourcer.use(async (ctx, next) => { - const { actionName, resourceName, values } = ctx.action.params; - if (resourceName === 'menus' && ['create', 'update'].includes(actionName)) { - if (values.parent) { - delete values.parent.children; - ctx.action.mergeParams({ - values: {...values}, - }, { - payload: 'replace', - }); - } - } - await next(); - }); - - resourcer.use(async (ctx, next) => { - await next(); - const { actionName, resourceName } = ctx.action.params; - if (resourceName === 'menus' && actionName === 'get') { - const menu = ctx.body; - const items = menu.get('views') || []; - const View = database.getModel('views_v2'); - for (const item of items) { - if (!(item.view && item.view.id)) { - continue; - } - const view = await View.findOne(View.parseApiJson({ - filter: { - id: item.view.id, - }, - fields: { - appends: ['collection', 'targetField', 'targetView'], - }, - })); - if (!view) { - continue; - } - const details = view.get(`options.x-${view.type}-props.details`); - if (!Array.isArray(details)) { - item.view = view; - continue; - } - for (const detail of details) { - if (!(detail.view && detail.view.id)) { - continue; - } - const detailView = await View.findOne(View.parseApiJson({ - filter: { - id: detail.view.id, - }, - fields: { - appends: ['collection', 'targetField', 'targetView'], - }, - })); - if (!detailView) { - continue; - } - detail.view = detailView; - } - view.set(`options.x-${view.type}-props.details`, details); - item.view = view; - } - menu.set('views', items); - } - }); - - resourcer.registerActionHandler('getCollection', getCollection); - resourcer.registerActionHandler('getView', getView); - resourcer.registerActionHandler('getPageInfo', getPageInfo); - resourcer.registerActionHandler('getCollections', getCollections); - resourcer.registerActionHandler('pages:getRoutes', getRoutes); - resourcer.registerActionHandler('menus:getTree', getTree); - resourcer.registerActionHandler('menus:getInfo', getInfo); - resourcer.registerActionHandler('views_v2:getInfo', viewGetInfo); - - resourcer.registerActionHandler('menus:list', menusList); - - Object.keys(rolesPagesActions).forEach(actionName => { - resourcer.registerActionHandler(`roles.pages:${actionName}`, rolesPagesActions[actionName]); - }); - - Object.keys(rolesMenusActions).forEach(actionName => { - resourcer.registerActionHandler(`roles.menus:${actionName}`, rolesMenusActions[actionName]); - }); - - const createDetailsViews = async (model, options) => { - const data = model.get(); - const View = database.getModel('views_v2'); - const types = ['table', 'calendar', 'kanban']; - for (const type of types) { - const items = _.get(data, `x-${type}-props.details`) || []; - if (items.length) { - const details = []; - for (const item of items) { - if (item.view) { - if (!item.view.id) { - const view = await View.create(item.view); - await view.updateAssociations(item.view); - item.view.id = view.id; - } else { - const view = await View.findByPk(item.view.id); - if (view) { - await view.update(item.view); - await view.updateAssociations(item.view); - } - } - const view = await View.findOne(View.parseApiJson({ - filter: { - id: item.view.id, - }, - fields: { - appends: ['collection', 'targetField', 'targetView'], - }, - })); - if (view) { - console.log({view}); - item.view = view.toJSON(); - } - } - details.push(item); - } - model.set(`options.x-${type}-props.details`, details); - } - } - }; - - database.getModel('views_v2').addHook('beforeCreate', createDetailsViews); - database.getModel('views_v2').addHook('beforeUpdate', createDetailsViews); - - database.getModel('views_v2').addHook('beforeSave', async (model, options) => { - const data = model.get(); - if (data.type !== 'kanban') { - return; - } - let groupField = _.get(data, `x-kanban-props.groupField`); - if (!groupField) { - return; - } - if (typeof groupField === 'object' && groupField.name) { - groupField = groupField.name; - } - const Field = database.getModel('fields'); - let field = await Field.findOne({ - where: { - name: `${groupField}_sort`, - collection_name: data.collection_name, - }, - }); - if (field) { - return; - } - await Field.create({ - interface: 'sort', - type: 'sort', - name: `${groupField}_sort`, - // TODO: 不支持相关数据 - collection_name: data.collection_name, - scope: [groupField], - title: '看板分组排序', - developerMode: true, - component: { - type: 'sort', - }, - }); - }); - - database.getModel('menus').addHook('beforeSave', async (model, options) => { - const { transaction } = options; - // console.log('beforeSave', model.get('views')); - const items = model.get('views'); - if (!Array.isArray(items)) { - return; - } - const View = database.getModel('views_v2'); - const views = []; - for (const item of items) { - if (item.view) { - if (!item.view.id) { - const view = await View.create(item.view); - await view.updateAssociations(item.view); - item.view.id = view.id; - } else { - const view = await View.findByPk(item.view.id); - await view.update(item.view); - await view.updateAssociations(item.view); - } - const view = await View.findOne(View.parseApiJson({ - filter: { - id: item.view.id, - }, - fields: { - appends: ['collection', 'targetField', 'targetView'], - }, - })); - if (view) { - console.log({view}); - item.view = view.toJSON(); - } - } - views.push(item); - } - model.set('views', views); - // @ts-ignore - model.changed('views', true); - }); -} diff --git a/packages/plugin-pages/src/utils.ts b/packages/plugin-pages/src/utils.ts deleted file mode 100644 index 3e942f440..000000000 --- a/packages/plugin-pages/src/utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import deepmerge from 'deepmerge'; - -export const flatToTree = (flatArray, options) => { - options = { - id: "id", - parentId: "parentId", - children: "children", - ...options - }; - const dictionary = {}; // a hash table mapping to the specific array objects with their ids as key - const tree = []; - const children = options.children; - flatArray.forEach(node => { - const nodeId = node[options.id]; - const nodeParentId = node[options.parentId]; - // set up current node data in dictionary - dictionary[nodeId] = { - [children]: [], // init a children property - ...node, // add other propertys - ...dictionary[nodeId] // children will be replaced if this node already has children property which was set below - }; - dictionary[nodeParentId] = dictionary[nodeParentId] || { [children]: [] }; // if it's not exist in dictionary, init an object with children property - dictionary[nodeParentId][children].push(dictionary[nodeId]); // add reference to current node object in parent node object - }); - // find root nodes - Object.values(dictionary).forEach(obj => { - if (typeof obj[options.id] === "undefined") { - tree.push(...obj[children]); - } - }); - return treeData(tree); -}; - -function treeData(pages: Array) { - return pages.map(data => { - return { ...data, children: data.children && data.children.length ? treeData(data.children) : undefined } - }); -} - -const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray - -export function merge(obj1: any, obj2: any) { - return deepmerge(obj1, obj2, { - arrayMerge: overwriteMerge, - }); -} diff --git a/packages/plugin-pages/src/views/index.ts b/packages/plugin-pages/src/views/index.ts deleted file mode 100644 index c5f2e1036..000000000 --- a/packages/plugin-pages/src/views/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as types from './types'; -import _ from 'lodash'; - -export const views = new Map(); - -export function registerView(type: string, value: any) { - views.set(type, value); -} - -export function registerViews(values: any) { - Object.keys(values).forEach(type => { - registerView(type, { - ...values[type], - type, - }); - }); -} - -registerViews(types); - -export function getOptions() { - const options = []; - for (const [type, view] of views) { - options.push({ - key: type, - value: type, - label: view.title, - disabled: !!view.disabled, - }); - } - return options; -} - -export function getViewTypeLinkages() { - let xlinkages = []; - for (const [key, item] of views) { - const { linkages = {}, properties = {} } = _.cloneDeep(item); - if (key === 'markdown') { - xlinkages.push({ - "type": "value:visible", - "target": `x-${key}-props.*`, - "condition": `{{ $self.value === '${key}' }}`, - }); - } else { - xlinkages.push({ - "type": "value:visible", - "target": `x-${key}-props.*`, - "condition": `{{ $self.value === '${key}' && $form.values.dataSourceType === 'collection' }}`, - }); - } - if (linkages.type) { - xlinkages.push(...linkages.type); - } - } - return xlinkages; -} - -export function getTypeFieldOptions() { - return { - interface: 'select', - type: 'string', - name: 'type', - title: '视图类型', - required: true, - dataSource: getOptions(), - createOnly: false, - component: { - type: 'select', - }, - linkages: getViewTypeLinkages(), - }; -} - -export function getViewFields() { - const fields = new Map(); - for (const [key, item] of views) { - const { properties = {}, linkages = {} } = _.cloneDeep(item); - Object.keys(properties).forEach(name => { - const property = { - ...properties[name], - name, - }; - if (!property.type) { - property.type = 'virtual'; - } - if (property.type === 'virtual') { - property.name = `x-${key}-props.${name}`; - } - if (linkages[name]) { - property.linkages = linkages[name].map((linkage: any) => { - linkage.target = `x-${key}-props.${linkage.target}`; - return linkage; - }); - } - fields.set(`x-${key}-props.${name}`, property); - }); - } - return [...fields.values()]; -} diff --git a/packages/plugin-pages/src/views/types.ts b/packages/plugin-pages/src/views/types.ts deleted file mode 100644 index 057927976..000000000 --- a/packages/plugin-pages/src/views/types.ts +++ /dev/null @@ -1,492 +0,0 @@ -const fields = { - interface: 'json', - type: 'virtual', - title: '要显示的字段', - target: 'views_fields_v2', - component: { - type: 'subTable', - }, -}; - -const actions = { - interface: 'json', - type: 'virtual', - title: '操作按钮配置', - target: 'views_actions_v2', - component: { - type: 'subTable', - }, -}; - -const details = { - interface: 'json', - type: 'virtual', - title: '单条数据页面的标签页和视图', - target: 'views_details_v2', - component: { - type: 'subTable', - }, -}; - -const detailsOpenMode = { - interface: 'radio', - // type: 'string', - title: '单条数据页面的打开方式', - required: true, - dataSource: [ - { - label: '{{ markdown(\'常规页面 点击数据进入独立的页面\') }}', - value: 'window', - style: { - display: 'block', - lineHeight: '32px', - }, - }, - { - label: '{{ markdown(\'快捷抽屉 点击数据不离开当前页面,在右侧抽屉里打开操作界面\') }}', - value: 'drawer', - style: { - display: 'block', - lineHeight: '32px', - }, - }, - ], - defaultValue: 'drawer', - component: { - type: 'radio', - default: 'default', - }, -}; - -export const form = { - // fields, - title: '表单', - options: { - // fields, - }, - properties: { - info1: { - interface: 'description', - type: 'virtual', - title: '表单配置', - component: { - type: 'description', - }, - }, - fields: { - ...fields, - viewName: 'tableForForm', - title: '显示在表单里的字段' - }, - }, - linkages: { - - }, -}; - -export const descriptions = { - title: '详情', - options: { - // actions, - // fields, - }, - properties: { - info1: { - interface: 'description', - type: 'virtual', - title: '详情配置', - component: { - type: 'description', - }, - }, - fields: { - ...fields, - title: '显示在详情里的字段' - }, - info2: { - interface: 'description', - type: 'virtual', - title: '操作按钮配置', - component: { - type: 'description', - }, - }, - actions, - }, - linkages: { - - }, -}; - -export const table = { - title: '表格', - options: { - defaultPerPage: 20, - draggable: false, - filter: {}, - sort: [], - detailsOpenMode: 'drawer', - // actions, - // fields, - // details, - // labelField, - }, - properties: { - info1: { - interface: 'description', - type: 'virtual', - title: '表格配置', - component: { - type: 'description', - }, - }, - // 表格配置 - labelField: { - interface: 'select', - type: 'virtual', - title: '作为单条数据标题的字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - fields: { - ...fields, - title: '显示在表格里的字段' - }, - defaultPerPage: { - interface: 'radio', - type: 'virtual', - name: 'defaultPerPage', - title: '每页默认显示几条数据', - defaultValue: 50, - dataSource: [ - { label: '10', value: 10 }, - { label: '20', value: 20 }, - { label: '50', value: 50 }, - { label: '100', value: 100 }, - ], - }, - draggable: { - interface: 'boolean', - type: 'virtual', - title: '表格里支持拖拽数据排序', - }, - info2: { - interface: 'description', - type: 'virtual', - title: '数据配置', - component: { - type: 'description', - }, - }, - filter: { - interface: 'json', - type: 'virtual', - title: '只显示符合以下条件的数据', - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - }, - }, - // sort: { - // interface: 'json', - // type: 'virtual', - // title: '默认排序', - // mode: 'replace', - // defaultValue: [], - // component: { - // type: 'string', - // }, - // }, - info3: { - interface: 'description', - type: 'virtual', - title: '操作按钮配置', - component: { - type: 'description', - }, - }, - actions, - info4: { - interface: 'description', - type: 'virtual', - title: '单条数据页面配置', - component: { - type: 'description', - }, - }, - detailsOpenMode, - details, - }, - linkages: { - }, -}; - -export const calendar = { - title: '日历', - options: { - // filter, - // labelField, - // startDateField, - // endDateField, - // openMode, - // details, - }, - properties: { - info1: { - interface: 'description', - type: 'virtual', - title: '日历配置', - component: { - type: 'description', - }, - }, - // 日历配置 - labelField: { - interface: 'select', - type: 'virtual', - title: '作为单条数据标题的字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - startDateField: { - interface: 'select', - type: 'virtual', - title: '开始日期字段', - required: true, - component: { - type: 'remoteSelect', - placeholder: '默认为创建时间字段', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - endDateField: { - interface: 'select', - type: 'virtual', - title: '结束日期字段', - // required: true, - component: { - type: 'remoteSelect', - placeholder: '默认为创建时间字段', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'date', - }, - }, - }, - info2: { - interface: 'description', - type: 'virtual', - title: '数据配置', - component: { - type: 'description', - }, - }, - filter: { - interface: 'json', - type: 'virtual', - title: '只显示符合以下条件的数据', - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - }, - }, - info3: { - interface: 'description', - type: 'virtual', - title: '操作按钮配置', - component: { - type: 'description', - }, - }, - actions, - info4: { - interface: 'description', - type: 'virtual', - title: '单条数据页面配置', - component: { - type: 'description', - }, - }, - detailsOpenMode, - details, - }, - linkages: { - - }, -}; - -export const kanban = { - title: '看板', - options: { - }, - properties: { - info1: { - interface: 'description', - type: 'virtual', - title: '看板配置', - component: { - type: 'description', - }, - }, - labelField: { - interface: 'select', - type: 'virtual', - title: '作为单条数据标题的字段', - name: 'labelField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - groupField: { - interface: 'select', - type: 'virtual', - title: '看板分组字段', - name: 'groupField', - required: true, - component: { - type: 'remoteSelect', - resourceName: 'collections.fields', - labelField: 'title', - valueField: 'name', - filter: { - type: 'string', - }, - }, - }, - fields: { - ...fields, - title: '显示在看板里的字段' - }, - info2: { - interface: 'description', - type: 'virtual', - title: '数据配置', - component: { - type: 'description', - }, - }, - filter: { - interface: 'json', - type: 'virtual', - title: '只显示符合以下条件的数据', - mode: 'replace', - defaultValue: {}, - component: { - type: 'filter', - }, - }, - info3: { - interface: 'description', - type: 'virtual', - title: '操作按钮配置', - component: { - type: 'description', - }, - }, - actions, - info4: { - interface: 'description', - type: 'virtual', - title: '单条数据页面配置', - component: { - type: 'description', - }, - }, - detailsOpenMode, - details, - }, -}; - -export const markdown = { - title: 'Markdown', - options: { - // html, - }, - properties: { - // 数据配置 - html: { - interface: 'markdown', - type: 'virtual', - title: 'Markdown 内容', - component: { - type: 'markdown', - }, - }, - }, - linkages: { - type: [ - { - type: "value:visible", - target: 'collection', - condition: `{{ $self.value && $self.value !== 'markdown' }}`, - }, - { - type: "value:visible", - target: 'dataSourceType', - condition: `{{ $self.value && $self.value !== 'markdown' }}`, - }, - ], - }, -}; - -export const map = { - title: '地图', - disabled: true, - options: {}, - properties: {}, -}; - -export const chart = { - title: '图表', - disabled: true, - options: {}, - properties: {}, -}; - -export const report = { - title: '报表', - disabled: true, - options: {}, - properties: {}, -}; - -export const aggregate = { - title: '汇总指标', - disabled: true, - options: {}, - properties: {}, -}; diff --git a/packages/plugin-permissions-v04/.npmignore b/packages/plugin-permissions-v04/.npmignore deleted file mode 100644 index 461574b2f..000000000 --- a/packages/plugin-permissions-v04/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -*.log -docs -__tests__ -tsconfig.json -src -.fatherrc.ts \ No newline at end of file diff --git a/packages/plugin-permissions-v04/package.json b/packages/plugin-permissions-v04/package.json deleted file mode 100644 index 237fe0147..000000000 --- a/packages/plugin-permissions-v04/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@nocobase/plugin-permissions-v04", - "version": "0.4.0-alpha.7", - "main": "lib/index.js", - "license": "MIT", - "dependencies": { - "@nocobase/actions": "^0.4.0-alpha.7", - "@nocobase/database": "^0.4.0-alpha.7", - "@nocobase/resourcer": "^0.4.0-alpha.7", - "@nocobase/server": "^0.4.0-alpha.7" - }, - "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" -} diff --git a/packages/plugin-permissions-v04/src/AccessController.ts b/packages/plugin-permissions-v04/src/AccessController.ts deleted file mode 100644 index 372c817fe..000000000 --- a/packages/plugin-permissions-v04/src/AccessController.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { Op } from 'sequelize'; - -import { ROLE_TYPE_ANONYMOUS, ROLE_TYPE_ROOT, ROLE_TYPE_USER } from './constants'; - - - -function getPermissions(roles) { - return roles.reduce((permissions, role) => permissions.concat(role.permissions), []); -} - -function getActionPermissions(permissions) { - const actionsMap = new Map(); - - permissions.forEach(permission => { - permission.get('actions').forEach(action => { - // 如果没有同名 action - if (!actionsMap.has(action.name)) { - actionsMap.set(action.name, action); - return; - } - - const existedScope = actionsMap.get(action.name).get('scope'); - // 如果之前的同名 action 没有 scope 或 filter 为空 - if (!existedScope || !existedScope.get('filter') || !Object.keys(existedScope.get('filter')).length) { - actionsMap.set(action.name, action); - return; - } - - const newScope = action.get('scope'); - // 如果新 action 没有 scope 或 filter 为空 - if (!newScope || !newScope.get('filter') || !Object.keys(newScope.get('filter')).length) { - return; - } - - // 以 or 关系合并两个 scope 中的 filter - existedScope.set('filter', { 'or': [existedScope.get('filter'), newScope.get('filter')] }); - }); - }); - - return Array.from(actionsMap.values()); -} - -function getFieldPermissions(permissions) { - const fieldsMap = new Map(); - permissions.forEach(permission => { - permission.get('fields_permissions').forEach(field => { - if (!fieldsMap.has(field.field_id)) { - fieldsMap.set(field.field_id, field); - return; - } - - const existedActions = fieldsMap.get(field.field_id).get('actions'); - if (!existedActions || !existedActions.length) { - fieldsMap.set(field.field_id, field); - return; - } - - const newActions = field.get('actions'); - if (!newActions || !newActions.length) { - return; - } - - const actions = new Set(existedActions); - newActions.forEach(item => actions.add(item)); - fieldsMap.get(field.field_id).set('actions', Array.from(actions)); - }); - }); - - return Array.from(fieldsMap.values()); -} - -function getTabPemissions(permissions) { - const tabs = new Set(); - permissions.forEach(permission => { - permission.get('tabs_permissions').forEach(tabPermission => tabs.add(tabPermission.tab_id)); - }); - - return Array.from(tabs); -} - -export type CollectionPermissions = { - actions: any[]; - fields: any[]; - tabs: any[] -}; - -export type PermissionParams = boolean | { - filter: any, - fields: any[] -}; - -// ctx.can = new AccessController(ctx).can; -// ctx.can('collection').permissions() -// => [{ name: 'list', scope: { id: 1, filter: {} } }] -// ctx.can('collection').act('list').any() -// => { filter: {}, fields: [] } -// ctx.can('collection').act('update').any() -// ctx.can('collection').act('update').one(resourceKey) -// ctx.can('collection').act('get').one(resourceKey) -// ctx.as(roles).can('collection').permissions() - -// TODO(optimize): 需要优化链式调用结构和上下文,以避免顺序或重置等问题 -export default class AccessController { - static isRoot(roles): boolean { - return (Array.isArray(roles) ? roles : [roles]).some(role => role.type === ROLE_TYPE_ROOT); - } - - context; - roles; - resourceName: string | null = null; - actionName: string | null = null; - - constructor(ctx) { - this.context = ctx; - } - - /** - * 用于临时创建新的身份进行相关操作 - * @param roles Array - */ - as(roles) { - const instance = new (this.constructor as T)(this.context); - instance.roles = Array.isArray(roles) ? roles : [roles]; - return instance; - } - - can(resourceName: string | null) { - this.resourceName = resourceName; - this.actionName = null; - return this; - } - - act(name: string | null) { - this.actionName = name; - return this; - } - - async permissions(): Promise { - const roles = await this.getRolesWithPermissions(); - if ((this.constructor as T).isRoot(roles)) { - return this.getRootPermissions(); - } - - const permissions = getPermissions(roles); - - return { - actions: getActionPermissions(permissions), - fields: getFieldPermissions(permissions), - tabs: getTabPemissions(permissions) - }; - } - - async any(): Promise { - const roles = await this.getRolesWithPermissions(); - if ((this.constructor as T).isRoot(roles)) { - return true; - } - if (this.resourceName === 'action_logs' && this.actionName === 'list') { - console.log(`skip ${this.resourceName}:${this.actionName}`); - return true; - } - if (this.resourceName === 'china_regions' && this.actionName === 'list') { - console.log(`skip ${this.resourceName}:${this.actionName}`); - return true; - } - // 只处理 actions 表里的权限,其余跳过 - const getActionNames = await this.getActionNames(); - if (!getActionNames.includes(this.actionName)) { - console.log(`skip ${this.resourceName}:${this.actionName}`); - return true; - } - const permissions = getPermissions(roles); - const actionPermissions = getActionPermissions(permissions); - - if (!actionPermissions.length) { - // 如果找不到可用的 action 记录 - // 则认为没有权限 - return false; - } - - const filters = actionPermissions - .filter(item => Boolean(item.scope) && Object.keys(item.scope.filter).length) - .map(item => item.scope.filter); - - const fields = getFieldPermissions(permissions); - - return { - filter: filters.length - ? filters.length > 1 ? { or: filters } : filters[0] - : {}, - fields - }; - } - - async one(resourceKey): Promise { - const any = await this.any(); - - if (typeof any === 'boolean') { - return any; - } - - const { filter } = any; - - const Collection = this.context.db.getModel(this.resourceName); - const existed = await Collection.count({ - where: { - ...Collection.parseApiJson({ filter, context: this.context }).where, - [Collection.primaryKeyAttribute]: resourceKey - } - }); - - return existed ? any : false; - } - - async getPermissions(): Promise { - const [Collection, Permission] = this.context.db.getModels(['collections', 'permissions']); - const roles = await this.getRoles(); - const permissions = await Permission.findAll({ - where: { - role_id: roles.map(role => role.id), - } - }); - return permissions; - } - - async getCollections(): Promise { - const [Collection, Permission] = this.context.db.getModels(['collections', 'permissions']); - const isRoot = await this.isRoot(); - if (isRoot) { - const collections = await Collection.findAll(); - return collections.map(collection => collection.name); - } - const roles = await this.getRoles(); - const permissions = await Permission.findAll({ - where: { - role_id: roles.map(role => role.id), - } - }); - return permissions.map(permission => permission.collection_name); - } - - async isRoot(): Promise { - const { context } = this; - const { currentUser } = context.state; - if (!currentUser) { - return false; - } - - const count = await currentUser.countRoles({ - where: { - type: ROLE_TYPE_ROOT - } - }); - - return !!count; - } - - async getRoles() { - if (this.roles) { - return this.roles; - } - const { context } = this; - let userRoles = []; - const { currentUser } = context.state; - if (currentUser) { - const rootRoles = await currentUser.getRoles({ - where: { - type: ROLE_TYPE_ROOT - } - }); - if (rootRoles.length) { - return rootRoles; - } - - userRoles = await currentUser.getRoles({ - where: { - type: ROLE_TYPE_USER - } - }); - } - - const Role = context.db.getModel('roles'); - const anonymousRoles = await Role.findAll({ - where: { - type: ROLE_TYPE_ANONYMOUS - } - }); - - return [...userRoles, ...anonymousRoles]; - } - - async getRolesWithPermissions() { - const { context, resourceName, actionName = null } = this; - if (!resourceName) { - throw new Error('resource name must be set first by `can(resourceName)`'); - } - - const permissionOptions = { - where: { - collection_name: resourceName - }, - include: [ - { - association: 'actions', - ...(actionName ? { - where: { - name: `${resourceName}:${actionName}` - }, - required: true, - } : {}), - // 对 hasMany 关系可以进行拆分查询,避免联表过多标识符超过 PG 的 64 字符限制 - separate: true, - include: [ - { - association: 'scope' - } - ] - }, - { - association: 'fields_permissions', - include: [ - { - association: 'field' - } - ], - separate: true, - }, - { - association: 'tabs_permissions', - separate: true, - } - ] - }; - const permissionInclusion = { - ...permissionOptions, - association: 'permissions', - required: true - }; - - if (this.roles) { - if (!(this.constructor as T).isRoot(this.roles)) { - for (const role of this.roles) { - role.permissions = await role.getPermissions(permissionOptions); - role.set('permissions', role.permissions); - } - } - return this.roles; - } - - let userRoles = []; - // 获取登入用户的角色及权限 - const { currentUser } = context.state; - if (currentUser) { - const rootRoles = await currentUser.getRoles({ - where: { - type: ROLE_TYPE_ROOT - } - }); - if (rootRoles.length) { - return rootRoles; - } - - userRoles = await currentUser.getRoles({ - where: { - type: ROLE_TYPE_USER - }, - include: [ - permissionInclusion - ] - }); - } - - // 获取匿名用户的角色及权限 - const Role = context.db.getModel('roles'); - const anonymousRoles = await Role.findAll({ - where: { - type: ROLE_TYPE_ANONYMOUS - }, - include: [ - permissionInclusion - ] - }); - - return [...anonymousRoles, ...userRoles]; - } - - async getActionNames() { - const { context, resourceName = null } = this; - const Action = context.db.getModel('actions'); - const actions = await Action.findAll({ - where: { - collection_name: { [Op.or]: [resourceName, null] } - } - }); - return actions.map(action => action.name); - } - - async getRootPermissions(): Promise { - const { context, resourceName = null } = this; - const Action = context.db.getModel('actions'); - const actions = await Action.findAll({ - where: { - collection_name: { [Op.or]: [resourceName, null] } - } - }); - - const Field = context.db.getModel('fields'); - const fields = await Field.findAll({ - where: { - collection_name: resourceName - } - }); - - const Tab = context.db.getModel('tabs'); - const tabs = await Tab.findAll({ - where: { - collection_name: resourceName - }, - attribute: [Tab.primaryKeyAttribute] - }); - - const actionsNames = actions.map(action => ({ - name: `${resourceName}:${action.name}` - })); - - return { - actions: actionsNames, - fields: fields.map(field => ({ - field, - field_id: field.id, - actions: actionsNames.map(({ name }) => name) - })), - tabs: tabs.map(tab => tab[Tab.primaryKeyAttribute]) - } - } -} diff --git a/packages/plugin-permissions-v04/src/__tests__/AccessController.test.ts b/packages/plugin-permissions-v04/src/__tests__/AccessController.test.ts deleted file mode 100644 index e189ca863..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/AccessController.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { getApp } from '.'; -import AccessController from '../AccessController'; - -describe('AccessController', () => { - let app; - let db; - const ac: any = {}; - - beforeEach(async () => { - app = await getApp(); - db = app.database; - - ac.anonymous = new AccessController({ - state: {}, - db - }); - - const User = db.getModel('users'); - const user = await User.findByPk(1); - ac.user = new AccessController({ - state: { currentUser: user }, - db - }); - - const root = await User.findByPk(4); - ac.root = new AccessController({ - state: { currentUser: root }, - db - }); - }); - - afterEach(() => db.close()); - - describe('can().permissions()', () => { - it('anonymous', async () => { - const result = await ac.anonymous.can('posts').permissions(); - expect(result).toMatchObject({ - actions: [ - { name: 'posts:list', scope: { filter: { status: 'published' }, collection_name: 'posts' } } - ], - fields: [ - { actions: ['posts:list'] } - ], - tabs: [] - }); - }); - }); - - describe('can().act()', () => { - it('normal user invoke can().permissions() invoked after .act()', async () => { - const actionResult = await ac.user.can('posts').act('list').any(); - expect(actionResult.filter).toEqual({ - or: [ - { status: 'published' }, - { status: 'draft', 'created_by_id.$currentUser': true } - ] - }); - - const permissionsResult = await ac.user.can('posts').permissions(); - expect(permissionsResult).toMatchObject({ - actions: [ - { - name: 'posts:list', - scope: { filter: { or: [{ status: 'published' }, { status: 'draft', 'created_by_id.$currentUser': true }] } } - }, - { - name: 'posts:update', - scope: { filter: { status: 'draft', 'created_by_id.$currentUser': true }, collection_name: 'posts' }, - } - ], - tabs: [] - }); - }); - }); -}); diff --git a/packages/plugin-permissions-v04/src/__tests__/index.ts b/packages/plugin-permissions-v04/src/__tests__/index.ts deleted file mode 100644 index fcb7d5b52..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/index.ts +++ /dev/null @@ -1,149 +0,0 @@ -import path from 'path'; -import qs from 'qs'; -import supertest from 'supertest'; -import bodyParser from 'koa-bodyparser'; -import { Dialect } from 'sequelize'; -import Database from '@nocobase/database'; -import { actions, middlewares } from '@nocobase/actions'; -import { Application } from '@nocobase/server'; -import middleware from '@nocobase/server/src/middleware'; -import plugin from '../server'; -import seed from './seed'; - -function getTestKey() { - const { id } = require.main; - const key = id - .replace(`${process.env.PWD}/packages`, '') - .replace(/src\/__tests__/g, '') - .replace('.test.ts', '') - .replace(/[^\w]/g, '_') - .replace(/_+/g, '_'); - return key -} - -const config = { - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - host: process.env.DB_HOST, - port: Number.parseInt(process.env.DB_PORT, 10), - dialect: process.env.DB_DIALECT as Dialect, - logging: process.env.DB_LOG_SQL === 'on', - sync: { - force: true, - alter: { - drop: true, - }, - }, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - }, -}; - -export function getDatabase() { - return new Database(config); -}; - -export async function getApp() { - const app = new Application({ - database: config, - resourcer: { - prefix: '/api', - }, - }); - app.resourcer.use(middlewares.associated); - app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common }); - app.registerPlugin({ - collections: path.resolve(__dirname, '../../../plugin-collections'), - users: path.resolve(__dirname, '../../../plugin-users'), - permissions: plugin - }); - await app.loadPlugins(); - const testTables = app.database.import({ - directory: path.resolve(__dirname, './tables') - }); - try { - await app.database.sync(); - } catch (err) { - console.error(err); - } - - for (const table of testTables.values()) { - // TODO(bug): 由于每个用例结束后不会清理用于测试的数据表,导致再次创建和更新 - // 创建和更新里面仍会再次创建 fields,导致创建相关的数据重复,数据库报错。 - await app.database.getModel('collections').import(table.getOptions(), { update: true, migrate: false }); - } - - await seed(app.database); - - app.context.db = app.database; - app.use(bodyParser()); - app.use(middleware({ - prefix: '/api', - resourcer: app.resourcer, - database: app.database, - })); - return app; -} - -interface ActionParams { - resourceKey?: string | number; - // resourceName?: string; - // associatedName?: string; - associatedKey?: string | number; - fields?: any; - filter?: any; - values?: any; - [key: string]: any; -} - -interface Handler { - get: (params?: ActionParams) => Promise; - list: (params?: ActionParams) => Promise; - create: (params?: ActionParams) => Promise; - update: (params?: ActionParams) => Promise; - destroy: (params?: ActionParams) => Promise; - [name: string]: (params?: ActionParams) => Promise; -} - -export interface Agent { - resource: (name: string) => Handler; -} - -export function getAgent(app: Application) { - return supertest.agent(app.callback()); -} - -export function getAPI(app: Application) { - const agent = getAgent(app); - return { - resource(name: string): any { - return new Proxy({}, { - get(target, method: string, receiver) { - return (params: ActionParams = {}) => { - const { associatedKey, resourceKey, values = {}, filePath, ...restParams } = params; - let url = `/api/${name}`; - if (associatedKey) { - url = `/api/${name.split('.').join(`/${associatedKey}/`)}`; - } - url += `:${method as string}`; - if (resourceKey) { - url += `/${resourceKey}`; - } - - switch (method) { - case 'list': - case 'get': - return agent.get(`${url}?${qs.stringify(restParams)}`); - - default: - return agent.post(`${url}?${qs.stringify(restParams)}`).send(values); - } - } - } - }); - } - }; -} diff --git a/packages/plugin-permissions-v04/src/__tests__/list.test.ts b/packages/plugin-permissions-v04/src/__tests__/list.test.ts deleted file mode 100644 index a2297e752..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/list.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { getApp, getAgent, getAPI } from '.'; -import AccessController from '../AccessController'; - -describe('list', () => { - let app; - let agent; - let api; - let db; - let userAgents; - - beforeEach(async () => { - app = await getApp(); - agent = getAgent(app); - db = app.database; - - const User = db.getModel('users'); - const users = await User.findAll({ order: [['id', 'ASC']] }); - userAgents = users.map(user => { - const userAgent = getAgent(app); - userAgent.set('Authorization', `Bearer ${user.token}`); - return userAgent; - }); - - }); - - afterEach(() => db.close()); - - describe('anonymous', () => { - it('fetch all drafts only get empty list', async () => { - const response = await agent.get('/api/posts'); - expect(response.status).toBe(200); - expect(response.body.count).toBe(0); - }); - }); - - describe('normal user', () => { - it('user could get posts created by self and limited fields', async () => { - const response = await userAgents[0].get('/api/posts?sort=title'); - expect(response.body.count).toBe(2); - // TODO(bug): fields 过滤暂时先跳过了 - // expect(response.body.rows).toEqual([ - // { title: 'title1', status: 'draft', category: null }, - // { title: 'title2', status: 'draft', category: null } - // ]); - }); - }); -}); diff --git a/packages/plugin-permissions-v04/src/__tests__/seed.ts b/packages/plugin-permissions-v04/src/__tests__/seed.ts deleted file mode 100644 index 6af7e1431..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/seed.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { ROLE_TYPE_ANONYMOUS, ROLE_TYPE_ROOT, ROLE_TYPE_USER } from '../constants'; - -export default async function (db) { - const User = db.getModel('users'); - const users = await User.bulkCreate([ - { username: 'user1', token: 'token1' }, - { username: 'user2', token: 'token2' }, - { username: 'user3', token: 'token3' }, - { username: 'user4', token: 'token4' }, - ]); - - const Role = db.getModel('roles'); - const roles = await Role.bulkCreate([ - { title: '匿名用户', type: ROLE_TYPE_ANONYMOUS }, - { title: '普通用户' }, - { title: '编辑' }, - { title: '管理员', type: ROLE_TYPE_ROOT }, - ]); - - const Scope = db.getModel('scopes'); - const scopePublished = await Scope.create({ collection_name: 'posts', filter: { status: 'published' } }, { logging: true }); - // TODO(bug): 字段应使用 'created_by' 名称,通过程序映射成外键 - const scopeCreatedBy = await Scope.create({ collection_name: 'posts', filter: { status: 'draft', 'created_by_id.$currentUser': true } }); - - const Field = db.getModel('fields'); - const postTitleField = await Field.findOne({ - where: { - name: 'title', - collection_name: 'posts' - } - }); - const postStatusField = await Field.findOne({ - where: { - name: 'status', - collection_name: 'posts' - } - }); - const postCategoryField = await Field.findOne({ - where: { - name: 'category', - collection_name: 'posts' - } - }); - - // 匿名用户 - await roles[0].updateAssociations({ - permissions: [ - { - collection_name: 'posts', - actions: [ - { - name: 'posts:list', - scope: scopePublished, - } - ], - fields_permissions: [ - { - field_id: postTitleField[Field.primaryKeyAttribute], - actions: ['posts:list'] - } - ] - }, - { - collection_name: 'categories', - actions: [ - { name: 'posts:list' } - ] - } - ] - }, { migrate: false }); - - // 普通用户 - await roles[1].updateAssociations({ - users: [users[0], users[3]], - permissions: [ - { - collection_name: 'posts', - actions: [ - { - name: 'posts:list', - scope: scopeCreatedBy, - }, - { - name: 'posts:update', - scope: scopeCreatedBy, - } - ], - fields: [ - { - id: postTitleField.id, - fields_permissions: { actions: ['posts:list', 'posts:create', 'posts:update'] } - }, - { - id: postStatusField.id, - fields_permissions: { actions: ['posts:list'] } - }, - { - id: postCategoryField.id, - fields_permissions: { actions: ['posts:list'] } - }, - ] - } - ] - }, { migrate: false }); - - // 编辑 - await roles[2].updateAssociations({ - users: [users[1], users[3]], - permissions: [ - { - collection_name: 'posts', - actions: [ - { - name: 'posts:update' - } - ], - fields_permissions: [ - { - field_id: postTitleField[Field.primaryKeyAttribute], - actions: ['posts:update'] - }, - { - field_id: postStatusField[Field.primaryKeyAttribute], - actions: ['posts:update'] - }, - { - field_id: postCategoryField[Field.primaryKeyAttribute], - actions: ['posts:update'] - }, - ] - }, - { - collection_name: 'categories', - actions: [ - { name: 'posts:create' }, - { name: 'posts:update' }, - { name: 'posts:destroy' }, - ] - } - ] - }, { migrate: false }); - - // 管理员 - - const Post = db.getModel('posts'); - await Post.bulkCreate([ - { title: 'title1', created_by_id: users[0].id, status: 'draft' }, - { title: 'title2', created_by_id: users[0].id, status: 'draft' }, - { title: 'title3', created_by_id: users[1].id, status: 'draft' }, - { title: 'title4', created_by_id: users[3].id, status: 'draft' }, - ]); -} diff --git a/packages/plugin-permissions-v04/src/__tests__/tables/categories.ts b/packages/plugin-permissions-v04/src/__tests__/tables/categories.ts deleted file mode 100644 index 7d1e0f501..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/tables/categories.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TableOptions } from "@nocobase/database"; - -export default { - name: 'categories', - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'hasMany', - name: 'posts', - }, - ] -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/__tests__/tables/comments.ts b/packages/plugin-permissions-v04/src/__tests__/tables/comments.ts deleted file mode 100644 index 1253e4da4..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/tables/comments.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TableOptions } from "@nocobase/database"; - -export default { - name: 'comments', - fields: [ - { - type: 'string', - name: 'content', - }, - { - type: 'belongsTo', - name: 'post', - }, - { - type: 'belongsTo', - name: 'user', - }, - ] -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/__tests__/tables/posts.ts b/packages/plugin-permissions-v04/src/__tests__/tables/posts.ts deleted file mode 100644 index 7c6c1ddbd..000000000 --- a/packages/plugin-permissions-v04/src/__tests__/tables/posts.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TableOptions } from "@nocobase/database"; - -export default { - name: 'posts', - // 目前默认就带了 - // createdBy: true, - statusable: false, - fields: [ - { - type: 'string', - name: 'title', - }, - { - type: 'string', - name: 'status', - // TODO(BUG): 默认值无效 - defaultValue: 'draft', - }, - { - type: 'belongsTo', - name: 'category', - }, - { - type: 'hasMany', - name: 'comments', - }, - ] -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/actions/collections.roles.ts b/packages/plugin-permissions-v04/src/actions/collections.roles.ts deleted file mode 100644 index eab23ed54..000000000 --- a/packages/plugin-permissions-v04/src/actions/collections.roles.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { actions, middlewares } from '@nocobase/actions'; -import * as rolesCollectionsActions from './roles.collections'; - -export async function list(ctx: actions.Context, next: actions.Next) { - // TODO: 暂时 action 中间件就这么写了 - ctx.action.mergeParams({ - associated: null, - }); - return actions.common.list(ctx, next); -} - -export async function get(ctx: actions.Context, next: actions.Next) { - const { resourceName, resourceKey, associatedName, associatedKey } = ctx.action.params; - ctx.action.mergeParams({ - associated: null, - resourceName: associatedName, - resourceKey: associatedKey, - associatedName: resourceName, - associatedKey: resourceKey, - }); - await middlewares.associated(ctx, async () => { - }); - return rolesCollectionsActions.get(ctx, next); -} - -export async function update(ctx: actions.Context, next: actions.Next) { - const { resourceName, resourceKey, associatedName, associatedKey } = ctx.action.params; - ctx.action.mergeParams({ - associated: null, - resourceName: associatedName, - resourceKey: associatedKey, - associatedName: resourceName, - associatedKey: resourceKey, - }); - await middlewares.associated(ctx, async () => { - }); - return rolesCollectionsActions.update(ctx, next); -} diff --git a/packages/plugin-permissions-v04/src/actions/roles.collections.ts b/packages/plugin-permissions-v04/src/actions/roles.collections.ts deleted file mode 100644 index 655c35da0..000000000 --- a/packages/plugin-permissions-v04/src/actions/roles.collections.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Op } from 'sequelize'; -import { actions } from '@nocobase/actions'; -import _ from 'lodash'; - -export async function list(ctx: actions.Context, next: actions.Next) { - const { associated } = ctx.action.params; - // TODO: 暂时 action 中间件就这么写了 - ctx.action.mergeParams({ - associated: null - }); - await actions.common.list(ctx, async () => { - const permissions = await associated.getPermissions(); - ctx.body.rows.forEach(item => { - const permission = permissions.find(p => p.collection_name === item.get('name')); - if (permission) { - // item.permissions = [permission]; // 不输出 - item.set('permissions', [permission]); // 输出 - } - }); - }); - await next(); -} - -export async function get(ctx: actions.Context, next: actions.Next) { - const { - resourceKey, - associated - } = ctx.action.params; - - const permissions = await ctx.ac.as(associated).can(resourceKey).permissions(); - - const permission = await associated.getPermissions({ - where: { - collection_name: resourceKey, - }, - plain: true, - limit: 1, - }); - - ctx.body = { - ...permissions, - description: _.get(permission, 'description'), - }; - - await next(); -} - -export async function update(ctx: actions.Context, next: actions.Next) { - const { - resourceKey, - associated, - values - } = ctx.action.params; - - const transaction = await ctx.db.sequelize.transaction(); - - // role.getPermissions - let [permission] = await associated.getPermissions({ - where: { - collection_name: resourceKey - }, - transaction - }); - - if (!permission) { - // 不存在则创建 - permission = await associated.createPermission({ - collection_name: resourceKey, - description: values.description - }, { transaction }); - } else { - // 存在则更新描述 - await permission.update({ description: values.description }, { transaction }); - } - - // 查找对应 collection 下所有“可用”的 actions - const ActionModel = ctx.db.getModel('actions'); - const availableActions = await ActionModel.findAll({ - where: { - // null 代表全局 action - collection_name: { - [Op.or]: [resourceKey, null] - }, - ...(ctx.state.developerMode ? {} : { developerMode: false }) - }, - transaction - }); - - // 需要移除的 = 所有可用的 - 要添加的 - const toRemoveActionNames = new Set(); - availableActions.forEach(action => { - const name = `${action.collection_name || resourceKey}:${action.name}`; - if (!values.actions.find(item => item.name === name)) { - toRemoveActionNames.add(name); - } - }); - - // 移除没有设置的 - // 因为只知道 name(不知道 id),所以只能从 Model 上调用移除 - const ActionPermission = ctx.db.getModel('actions_permissions'); - await ActionPermission.destroy({ - where: { - permission_id: permission.id, - name: Array.from(toRemoveActionNames) - }, - transaction - }); - // 更新或创建 - const existedActions = await permission.getActions({ transaction }); - for (const actionItem of values.actions) { - const action = existedActions.find(item => item.name === actionItem.name); - if (action) { - await action.update(actionItem, { transaction }); - } else { - await permission.createAction(actionItem, { transaction }); - } - }; - - const fieldsValue = values.fields.filter(field => field.actions && field.actions.length); - const existedFields = await permission.getFields_permissions({ - include: [ - { association: 'field' } - ], - transaction - }); - const toRemoveFields = []; - const fieldsLeft = []; - existedFields.forEach(field => { - if ( - !fieldsValue.find(({ field_id }) => field_id === field.field_id) - && !(field.field.developerMode ^ ctx.state.developerMode) - ) { - toRemoveFields.push(field.field); - } else { - fieldsLeft.push(field); - } - }); - if (toRemoveFields.length) { - await permission.removeFields(toRemoveFields, { transaction }); - } - for (const fieldItem of fieldsValue) { - const field = fieldsLeft.find(item => item.field_id === fieldItem.field_id); - if (field) { - await field.update(fieldItem, { transaction }); - } else { - await permission.createFields_permission(fieldItem, { transaction }); - } - } - - const TabModel = ctx.db.getModel('tabs'); - const existedTabs = await permission.getTabs({ transaction }); - const toRemoveTabs = existedTabs.filter(tab => ( - // 如果没找到 - !values.tabs.find(id => tab[TabModel.primaryKeyAttribute] === id) - // 且开发者模式匹配 - && !(tab.developerMode ^ ctx.state.developerMode))); - await permission.removeTabs(toRemoveTabs, { transaction }); - await permission.addTabs(values.tabs, { transaction }); - - await transaction.commit(); - - ctx.body = permission; - - await next(); -} diff --git a/packages/plugin-permissions-v04/src/collections/actions_permissions.ts b/packages/plugin-permissions-v04/src/collections/actions_permissions.ts deleted file mode 100644 index 14e4d9775..000000000 --- a/packages/plugin-permissions-v04/src/collections/actions_permissions.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'actions_permissions', - title: '表操作权限', - developerMode: true, - internal: true, - fields: [ - { - comment: '程序操作名称("list", "create" 等)', - type: 'string', - name: 'name', - }, - { - comment: '操作范围', - type: 'belongsTo', - name: 'scope', - target: 'scopes' - }, - { - type: 'belongsTo', - name: 'permission', - onDelete: 'CASCADE' - }, - ], -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/collections/collections.ts b/packages/plugin-permissions-v04/src/collections/collections.ts deleted file mode 100644 index ce9a951ca..000000000 --- a/packages/plugin-permissions-v04/src/collections/collections.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { extend } from '@nocobase/database'; - -export default extend({ - name: 'collections', - fields: [ - { - interface: 'linkTo', - title: '权限', - type: 'belongsToMany', - name: 'roles', - through: 'permissions', - sourceKey: 'name' - }, - // { - // type: 'hasMany', - // name: 'permissions', - // sourceKey: 'name', - // foreignKey: 'collection_name' - // } - ], -}); diff --git a/packages/plugin-permissions-v04/src/collections/fields_permissions.ts b/packages/plugin-permissions-v04/src/collections/fields_permissions.ts deleted file mode 100644 index c22a2a0d5..000000000 --- a/packages/plugin-permissions-v04/src/collections/fields_permissions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'fields_permissions', - title: '列操作权限', - developerMode: true, - internal: true, - fields: [ - { - type: 'belongsTo', - name: 'permission', - }, - { - type: 'belongsTo', - name: 'field', - onDelete: 'CASCADE', - }, - { - type: 'jsonb', - name: 'actions' - } - ], -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/collections/permissions.ts b/packages/plugin-permissions-v04/src/collections/permissions.ts deleted file mode 100644 index 71e9e8565..000000000 --- a/packages/plugin-permissions-v04/src/collections/permissions.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'permissions', - title: '权限集', - developerMode: true, - internal: true, - fields: [ - // TODO(feature): 黑白名单控制 - // { - // comment: '白名单或黑名单', - // type: 'boolean', - // name: 'mode', - // defaultValue: false - // }, - { - type: 'integer', - name: 'id', - primaryKey: true, - autoIncrement: true - }, - { - type: 'string', - name: 'description', - }, - { - comment: '关联的角色', - type: 'belongsTo', - name: 'role', - }, - { - comment: '关联的表名', - type: 'belongsTo', - name: 'collection', - targetKey: 'name' - }, - { - comment: '允许的操作集', - type: 'hasMany', - name: 'actions', - target: 'actions_permissions' - }, - { - type: 'belongsToMany', - name: 'fields', - through: 'fields_permissions', - }, - { - type: 'hasMany', - name: 'fields_permissions' - }, - // { - // comment: '允许的 views 列表', - // type: 'belongsToMany', - // name: 'views', - // through: 'views_permissions' - // }, - // { - // comment: '视图集(方便访问)', - // type: 'hasMany', - // name: 'views_permissions', - // }, - { - comment: '允许的 tabs 列表', - type: 'belongsToMany', - name: 'tabs', - through: 'tabs_permissions' - }, - { - comment: '标签页集(方便访问)', - type: 'hasMany', - name: 'tabs_permissions', - }, - ], -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/collections/roles.ts b/packages/plugin-permissions-v04/src/collections/roles.ts deleted file mode 100644 index a88df62ba..000000000 --- a/packages/plugin-permissions-v04/src/collections/roles.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { TableOptions } from '@nocobase/database'; -import { ROLE_TYPE_ROOT, ROLE_TYPE_USER, ROLE_TYPE_ANONYMOUS } from '../constants'; - -export default { - name: 'roles', - title: '权限组配置', - developerMode: true, - internal: true, - fields: [ - { - interface: 'string', - title: '权限组名称', - comment: '角色名称', - type: 'string', - name: 'title', - component: { - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - { - interface: 'select', - title: '角色类型', - type: 'integer', - name: 'type', - developerMode: true, - dataSource: [ - { value: ROLE_TYPE_ROOT, label: '系统角色' }, - { value: ROLE_TYPE_ANONYMOUS, label: '匿名角色' }, - { value: ROLE_TYPE_USER, label: '自定义角色' }, - ], - defaultValue: ROLE_TYPE_USER, - component: { - showInTable: true, - showInDetail: true, - } - }, - { - interface: 'boolean', - title: '默认权限组', - type: 'radio', - name: 'default', - component: { - showInTable: true, - showInForm: true, - showInDetail: true, - } - }, - { - interface: 'textarea', - title: '描述', - type: 'text', - name: 'description', - component: { - showInTable: true, - showInForm: true, - showInDetail: true, - }, - }, - // TODO(feature): 用户组后续考虑 - // TODO(feature): 用户表应通过插件配置关联,考虑到今后会有多账户系统的情况 - { - interface: 'linkTo', - title: '用户', - comment: '关联的用户表', - type: 'belongsToMany', - name: 'users', - target: 'users', - through: 'users_roles' - }, - { - interface: 'linkTo', - title: '数据表', - comment: '包含的以数据表分组的权限集', - type: 'belongsToMany', - name: 'collections', - through: 'permissions', - targetKey: 'name' - }, - { - interface: 'linkTo', - title: '页面', - type: 'belongsToMany', - name: 'menus', - target: 'menus', - through: 'menus_permissions', - }, - { - comment: '权限集(方便访问)', - type: 'hasMany', - name: 'permissions' - }, - ], - actions: [ - { - type: 'list', - name: 'list', - title: '查看', - }, - // { - // type: 'get', - // name: 'get', - // title: '详情', - // }, - { - type: 'create', - name: 'create', - title: '新增', - viewName: 'form', - }, - { - type: 'update', - name: 'update', - title: '编辑', - viewName: 'form', - }, - { - type: 'destroy', - name: 'destroy', - title: '删除', - filter: { - type: ROLE_TYPE_USER, - default: false - } - }, - ], - views_v2: [ - { - developerMode: true, - type: 'table', - name: 'table', - title: '全部数据', - labelField: 'title', - actions: [ - { - name: 'create', - type: 'create', - title: '新增', - viewName: 'form', - }, - { - name: 'destroy', - type: 'destroy', - title: '删除', - }, - ], - fields: ['title', 'description', 'default'], - detailsOpenMode: 'drawer', // window - details: ['descriptions', 'collections', 'pages'], - sort: ['id'], - }, - { - developerMode: true, - type: 'descriptions', - name: 'descriptions', - title: '详情', - fields: ['title', 'description', 'default'], - actions: [ - { - name: 'update', - type: 'update', - title: '编辑', - viewName: 'form', - }, - ], - }, - { - developerMode: true, - type: 'form', - name: 'form', - title: '表单', - fields: ['title', 'description', 'default'], - }, - { - developerMode: true, - type: 'table', - dataSourceType: 'association', - name: 'collections', - title: '数据操作权限', - targetViewName: 'permissions_table', - targetFieldName: 'collections', - }, - { - developerMode: true, - type: 'table', - dataSourceType: 'association', - name: 'pages', - title: '菜单和页面权限', - targetViewName: 'permissions_table', - targetFieldName: 'menus', - }, - ], -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/collections/tabs_permissions.ts b/packages/plugin-permissions-v04/src/collections/tabs_permissions.ts deleted file mode 100644 index 381df42e2..000000000 --- a/packages/plugin-permissions-v04/src/collections/tabs_permissions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'tabs_permissions', - title: '标签页访问权限', - developerMode: true, - internal: true, - fields: [ - { - type: 'belongsTo', - name: 'permission', - }, - { - type: 'belongsTo', - name: 'tab' - }, - ], -} as TableOptions; diff --git a/packages/plugin-permissions-v04/src/collections/users.ts b/packages/plugin-permissions-v04/src/collections/users.ts deleted file mode 100644 index 40eaab63f..000000000 --- a/packages/plugin-permissions-v04/src/collections/users.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TableOptions } from '@nocobase/database'; - -export default { - name: 'users', - fields: [ - { - interface: 'linkTo', - type: 'belongsToMany', - name: 'roles', - title: '权限组', - target: 'roles', - labelField: 'title', - through: 'users_roles', - component: { - type: 'drawerSelect', - showInTable: true, - showInForm: true, - showInDetail: true, - 'x-component-props': { - resourceName: 'collections.roles', - labelField: 'title', - valueField: 'id', - }, - }, - }, - ], -}; diff --git a/packages/plugin-permissions-v04/src/constants.ts b/packages/plugin-permissions-v04/src/constants.ts deleted file mode 100644 index 915a34903..000000000 --- a/packages/plugin-permissions-v04/src/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const ROLE_TYPE_ROOT = -1; -export const ROLE_TYPE_ANONYMOUS = 0; -export const ROLE_TYPE_USER = 1; diff --git a/packages/plugin-permissions-v04/src/index.ts b/packages/plugin-permissions-v04/src/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/plugin-permissions-v04/src/server.ts b/packages/plugin-permissions-v04/src/server.ts deleted file mode 100644 index a7eddaaae..000000000 --- a/packages/plugin-permissions-v04/src/server.ts +++ /dev/null @@ -1,166 +0,0 @@ -import path from 'path'; -import { Op } from 'sequelize'; -import { Application } from '@nocobase/server'; -import { Operator } from '@nocobase/database'; -import * as collectionsRolesActions from './actions/collections.roles'; -import * as rolesCollectionsActions from './actions/roles.collections'; -import AccessController, { PermissionParams } from './AccessController'; - -// API -// const permissions = ctx.app.getPluginInstance('permissions'); -// const result: boolean = permissions.can(key, options); - - - -export class Permissions { - readonly app: Application; - readonly options: any; - - constructor(app: Application, options) { - this.app = app; - this.options = options; - - const { database, resourcer } = app; - - database.import({ - directory: path.resolve(__dirname, 'collections'), - }); - - Object.keys(collectionsRolesActions).forEach(actionName => { - resourcer.registerActionHandler(`collections.roles:${actionName}`, collectionsRolesActions[actionName]); - }); - - Object.keys(rolesCollectionsActions).forEach(actionName => { - resourcer.registerActionHandler(`roles.collections:${actionName}`, rolesCollectionsActions[actionName]); - }); - - database.getModel('collections').addHook('afterCreate', async (model: any, options) => { - await model.updateAssociations({ - scopes: [ - { - title: '全部数据', - filter: {}, - locked: true - }, - { - title: '用户自己的数据', - filter: { - "created_by_id.$currentUser": true, - }, - locked: true - }, - ] - }, options); - }); - - database.getModel('users').addHook('afterCreate', async(model, options) => { - const { transaction = await database.sequelize.transaction() } = options; - const Role = database.getModel('roles'); - const defaultRole = await Role.findOne({ where: { default: true }, transaction }); - if (defaultRole) { - // @ts-ignore - await model.addRole(defaultRole, { transaction }); - } - if (!options.transaction) { - await transaction.commit(); - } - }); - - // 针对“自己创建的” scope 添加特殊的操作符以生成查询条件 - if (!Operator.has('$currentUser')) { - Operator.register('$currentUser', (value, { ctx }) => { - const user = ctx.state.currentUser; - return { [Op.eq]: user[user.constructor.primaryKeyAttribute] }; - }); - } - - resourcer.use(this.injection); - resourcer.use(this.middleware); - - resourcer.use(async (ctx, next) => { - const { resourceName } = ctx.action.params; - if (resourceName === 'action_logs' && !await ctx.ac.isRoot()) { - const collections = await ctx.ac.getCollections(); - ctx.action.mergeParams({ - filter: { - 'collection_name.in': collections - }, - }); - } - await next(); - }); - } - - injection = async (ctx, next) => { - ctx.ac = new AccessController(ctx); - - return next(); - }; - - middleware = async (ctx, next) => { - const { - associatedName, - resourceField, - resourceName, - actionName - } = ctx.action.params; - - let result: PermissionParams = false; - - // 关系数据的权限 - if (associatedName && resourceField) { - if (resourceField.options.id && resourceField.options.interface === 'subTable') { - if (await ctx.ac.isRoot()) { - return next(); - } - const permissions = await ctx.ac.getPermissions(); - const FieldPermission = ctx.db.getModel('fields_permissions'); - const fps = await FieldPermission.findAll({ - where: { - field_id: resourceField.options.id, - permission_id: { - [Op.in]: permissions.map(p => p.id), - } - }, - }); - if (fps.length) { - for (const fp of fps) { - if (Array.isArray(fp.actions) && fp.actions.includes(`${resourceField.options.collection_name}:${actionName}`)) { - return next(); - } - } - return this.reject(ctx); - } - } - result = await ctx.ac.can(resourceField.options.target).act(actionName).any(); - } else { - result = await ctx.ac.can(resourceName).act(actionName).any(); - } - - if (!result) { - return this.reject(ctx); - } - - if (result === true) { - return next(); - } - - ctx.action.mergeParams({ - filter: result.filter, - // TODO: 在 fields 改进之前,先注释掉 - // fields: result.fields.map(item => item.get('field').get('name')) - }); - - return next(); - }; - - reject(ctx) { - ctx.throw(404); - } -} - -export default async function (options = {}) { - const instance = new Permissions(this, options); - - return instance; -} diff --git a/packages/plugin-system-settings/src/server.ts b/packages/plugin-system-settings/src/server.ts index b1992f49b..87f3b6ba3 100644 --- a/packages/plugin-system-settings/src/server.ts +++ b/packages/plugin-system-settings/src/server.ts @@ -9,10 +9,11 @@ export default async function (this: Application, options = {}) { directory: path.resolve(__dirname, 'collections'), }); + const SystemSetting = database.getModel('system_settings'); + resourcer.use(async (ctx, next) => { const { actionName, resourceName, resourceKey } = ctx.action.params; if (resourceName === 'system_settings' && actionName === 'get') { - const SystemSetting = database.getModel('system_settings'); let model = await SystemSetting.findOne(); if (!model) { model = await SystemSetting.create(); @@ -23,4 +24,19 @@ export default async function (this: Application, options = {}) { } await next(); }); + + this.on('system-settings.init', async () => { + const setting = await SystemSetting.create({ + title: 'NocoBase', + }); + await setting.updateAssociations({ + logo: { + title: 'nocobase-logo', + filename: '682e5ad037dd02a0fe4800a3e91c283b.png', + extname: '.png', + mimetype: 'image/png', + url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png', + }, + }); + }); } diff --git a/packages/plugin-ui-router/src/actions/getAccessible.ts b/packages/plugin-ui-router/src/actions/getAccessible.ts index 7ca131e5b..3338baf14 100644 --- a/packages/plugin-ui-router/src/actions/getAccessible.ts +++ b/packages/plugin-ui-router/src/actions/getAccessible.ts @@ -1,5 +1,5 @@ import { Model, ModelCtor } from '@nocobase/database'; -import { actions } from '@nocobase/actions'; +import { Context, Next } from '@nocobase/actions'; import FlatToNested from 'flat-to-nested'; const flatToNested = new FlatToNested({ @@ -8,8 +8,7 @@ const flatToNested = new FlatToNested({ children: 'routes', }); -export default async (ctx: actions.Context, next: actions.Next) => { - const { resourceKey } = ctx.action.params; +export default async (ctx: Context, next: Next) => { const Route = ctx.db.getModel('routes'); const routes = await Route.findAll(Route.parseApiJson({ sort: 'sort', diff --git a/packages/plugin-ui-router/src/server.ts b/packages/plugin-ui-router/src/server.ts index 2c9a3496f..089a1e7e3 100644 --- a/packages/plugin-ui-router/src/server.ts +++ b/packages/plugin-ui-router/src/server.ts @@ -3,6 +3,7 @@ import { Application } from '@nocobase/server'; import { registerModels } from '@nocobase/database'; import * as models from './models'; import getAccessible from './actions/getAccessible'; +import * as uiSchema from './ui-schema'; export default async function (this: Application, options = {}) { const database = this.database; @@ -13,4 +14,48 @@ export default async function (this: Application, options = {}) { }); this.resourcer.registerActionHandler('routes:getAccessible', getAccessible); + + const Route = database.getModel('routes'); + + this.on('ui-router.init', async () => { + const data = [ + { + type: 'redirect', + from: '/', + to: '/admin', + exact: true, + }, + { + type: 'route', + path: '/admin/:name(.+)?', + component: 'AdminLayout', + title: `后台`, + uiSchema: uiSchema.menu, + }, + { + type: 'route', + component: 'AuthLayout', + children: [ + { + type: 'route', + path: '/login', + component: 'RouteSchemaRenderer', + title: `登录`, + uiSchema: uiSchema.login, + }, + { + type: 'route', + path: '/register', + component: 'RouteSchemaRenderer', + title: `注册`, + uiSchema: uiSchema.register, + }, + ], + }, + ]; + for (const item of data) { + const route = await Route.create(item); + await route.updateAssociations(item); + } + }); } diff --git a/packages/plugin-ui-router/src/ui-schema/index.ts b/packages/plugin-ui-router/src/ui-schema/index.ts new file mode 100644 index 000000000..6bef8bfe2 --- /dev/null +++ b/packages/plugin-ui-router/src/ui-schema/index.ts @@ -0,0 +1,3 @@ +export * from './login'; +export * from './menu'; +export * from './register'; diff --git a/packages/plugin-ui-router/src/ui-schema/login.ts b/packages/plugin-ui-router/src/ui-schema/login.ts new file mode 100644 index 000000000..2ccf1da4d --- /dev/null +++ b/packages/plugin-ui-router/src/ui-schema/login.ts @@ -0,0 +1,64 @@ +export const login = { + key: 'dtf9j0b8p9u', + type: 'object', + title: '登录', + properties: { + email: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '电子邮箱', + style: { + // width: 240, + }, + }, + }, + password: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + placeholder: '密码', + style: { + // width: 240, + }, + }, + }, + actions: { + type: 'void', + 'x-component': 'Div', + properties: { + submit: { + type: 'void', + 'x-component': 'Action', + 'x-component-props': { + block: true, + type: 'primary', + useAction: '{{ useLogin }}', + style: { + width: '100%', + }, + }, + title: '登录', + }, + }, + }, + registerlink: { + type: 'void', + 'x-component': 'Div', + properties: { + link: { + type: 'void', + 'x-component': 'Action.Link', + 'x-component-props': { + to: '/register', + }, + title: '注册账号', + }, + }, + }, + }, +}; diff --git a/packages/plugin-ui-router/src/ui-schema/menu.ts b/packages/plugin-ui-router/src/ui-schema/menu.ts new file mode 100644 index 000000000..6a6660631 --- /dev/null +++ b/packages/plugin-ui-router/src/ui-schema/menu.ts @@ -0,0 +1,15 @@ +export const menu = { + key: 'qqzzjakwkwl', + name: 'qqzzjakwkwl', + type: 'void', + 'x-component': 'Menu', + 'x-designable-bar': 'Menu.DesignableBar', + 'x-component-props': { + mode: 'mix', + theme: 'dark', + defaultSelectedKeys: '{{ selectedKeys }}', + sideMenuRef: '{{ sideMenuRef }}', + onSelect: '{{ onSelect }}', + onRemove: '{{ onMenuItemRemove }}', + }, +}; diff --git a/packages/plugin-ui-router/src/ui-schema/register.ts b/packages/plugin-ui-router/src/ui-schema/register.ts new file mode 100644 index 000000000..1c183a376 --- /dev/null +++ b/packages/plugin-ui-router/src/ui-schema/register.ts @@ -0,0 +1,100 @@ +export const register = { + key: '46qlxqam3xk', + type: 'object', + title: '注册', + properties: { + email: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-component-props': { + placeholder: '电子邮箱', + style: { + // width: 240, + }, + }, + }, + password: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + placeholder: '密码', + checkStrength: true, + style: { + // width: 240, + }, + }, + 'x-reactions': [ + { + dependencies: ['.confirm_password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + confirm_password: { + type: 'string', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Password', + 'x-component-props': { + placeholder: '确认密码', + checkStrength: true, + style: { + // width: 240, + }, + }, + 'x-reactions': [ + { + dependencies: ['.password'], + fulfill: { + state: { + errors: + '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}', + }, + }, + }, + ], + }, + actions: { + type: 'void', + 'x-component': 'Div', + properties: { + submit: { + type: 'void', + title: '注册', + 'x-component': 'Action', + 'x-component-props': { + block: true, + type: 'primary', + useAction: '{{ useRegister }}', + style: { + width: '100%', + }, + }, + }, + }, + }, + registerlink: { + type: 'void', + 'x-component': 'Div', + properties: { + link: { + type: 'void', + 'x-component': 'Action.Link', + 'x-component-props': { + to: '/login', + }, + title: '使用已有账号登录', + }, + }, + }, + }, +} diff --git a/packages/plugin-ui-schema/package.json b/packages/plugin-ui-schema/package.json index 96b75f8de..8b8a3430a 100644 --- a/packages/plugin-ui-schema/package.json +++ b/packages/plugin-ui-schema/package.json @@ -10,7 +10,8 @@ "deepmerge": "^4.2.2" }, "devDependencies": { - "@nocobase/actions": "^0.4.0-alpha.7" + "@nocobase/actions": "^0.4.0-alpha.7", + "@nocobase/test": "^0.4.0-alpha.7" }, "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" } diff --git a/packages/plugin-ui-schema/src/__tests__/index.ts b/packages/plugin-ui-schema/src/__tests__/index.ts deleted file mode 100644 index e0574424b..000000000 --- a/packages/plugin-ui-schema/src/__tests__/index.ts +++ /dev/null @@ -1,144 +0,0 @@ -import qs from 'qs'; -import plugin from '../server'; -import supertest from 'supertest'; -import bodyParser from 'koa-bodyparser'; -import { Dialect } from 'sequelize'; -import Database from '@nocobase/database'; -import { actions, middlewares } from '@nocobase/actions'; -import { Application, middleware } from '@nocobase/server'; - -function getTestKey() { - const { id } = require.main; - const key = id - .replace(`${process.env.PWD}/packages`, '') - .replace(/src\/__tests__/g, '') - .replace('.test.ts', '') - .replace(/[^\w]/g, '_') - .replace(/_+/g, '_'); - return key -} - -const config = { - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_DATABASE, - host: process.env.DB_HOST, - port: Number.parseInt(process.env.DB_PORT, 10), - dialect: process.env.DB_DIALECT as Dialect, - define: { - hooks: { - beforeCreate(model, options) { - - }, - }, - }, - logging: false, - sync: { - force: true, - alter: { - drop: true, - }, - }, -}; - -export async function getApp() { - const app = new Application({ - database: { - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - }, - }, - resourcer: { - prefix: '/api', - }, - }); - app.resourcer.use(middlewares.associated); - app.resourcer.registerActionHandlers({ ...actions.associate, ...actions.common }); - app.registerPlugin('collections', [plugin]); - await app.loadPlugins(); - await app.database.sync(); - // 表配置信息存到数据库里 - // const tables = app.database.getTables([]); - // for (const table of tables) { - // const Collection = app.database.getModel('collections'); - // await Collection.import(table.getOptions(), { hooks: false }); - // } - app.use(async (ctx, next) => { - ctx.db = app.database; - await next(); - }); - app.use(bodyParser()); - app.use(middleware({ - prefix: '/api', - resourcer: app.resourcer, - database: app.database, - })); - return app; -} - -interface ActionParams { - resourceKey?: string | number; - // resourceName?: string; - // associatedName?: string; - associatedKey?: string | number; - fields?: any; - filter?: any; - values?: any; - [key: string]: any; -} - -interface Handler { - get: (params?: ActionParams) => Promise; - list: (params?: ActionParams) => Promise; - create: (params?: ActionParams) => Promise; - update: (params?: ActionParams) => Promise; - destroy: (params?: ActionParams) => Promise; - [name: string]: (params?: ActionParams) => Promise; -} - -export interface Agent { - resource: (name: string) => Handler; -} - -export function getAgent(app: Application): Agent { - const agent = supertest.agent(app.callback()); - return { - resource(name: string): any { - return new Proxy({}, { - get(target, method, receiver) { - return (params: ActionParams = {}) => { - const { associatedKey, resourceKey, values = {}, ...restParams } = params; - let url = `/api/${name}`; - if (associatedKey) { - url = `/api/${name.split('.').join(`/${associatedKey}/`)}`; - } - url += `:${method as string}`; - if (resourceKey) { - url += `/${resourceKey}`; - } - console.log(url); - if (['list', 'get'].indexOf(method as string) !== -1) { - return agent.get(`${url}?${qs.stringify(restParams)}`); - } else { - return agent.post(`${url}?${qs.stringify(restParams)}`).send(values); - } - } - } - }); - } - }; -} - -export function getDatabase() { - return new Database({ - ...config, - hooks: { - beforeDefine(columns, model) { - model.tableName = `${getTestKey()}_${model.tableName || model.name.plural}`; - } - } - }); -}; diff --git a/packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts b/packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts index 57afe8109..34879d32d 100644 --- a/packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts +++ b/packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts @@ -1,50 +1,20 @@ -import { Agent, getAgent, getApp } from '.'; -import { Application } from '@nocobase/server'; -import Database from '@nocobase/database'; +import { mockServer, MockServer } from '@nocobase/test'; +import { registerActions } from '@nocobase/actions'; describe('ui_schemas', () => { - let app: Application; - let agent: Agent; - let db: Database; - + let api: MockServer; beforeEach(async () => { - app = await getApp(); - db = app.database; - agent = getAgent(app); + api = mockServer(); + registerActions(api); + api.registerPlugin('ui-schema', require('../server').default); + await api.loadPlugins(); }); - afterEach(() => app.database.close()); + afterEach(async () => { + await api.destroy(); + }); it('create ui_schemas', async () => { - const UISchema = db.getModel('ui_schemas'); - const schema = await UISchema.create({ - type: 'void', - name: 'abc', - properties: { - field1: { - type: 'string', - 'x-component': 'Input', - }, - field2: { - type: 'string', - 'x-component': 'Input', - }, - field3: { - type: 'array', - properties: { - field11: { - type: 'string', - 'x-component': 'Input', - }, - field12: { - type: 'string', - 'x-component': 'Input', - }, - }, - }, - }, - }); - const properties = await schema.getProperties(); - console.log(JSON.stringify(properties, null, 2)); + expect(1).toBe(1); }); }); diff --git a/packages/plugin-ui-schema/src/actions/index.ts b/packages/plugin-ui-schema/src/actions/index.ts index adc70a1bb..552849941 100644 --- a/packages/plugin-ui-schema/src/actions/index.ts +++ b/packages/plugin-ui-schema/src/actions/index.ts @@ -1,24 +1,24 @@ -import { Model, ModelCtor } from '@nocobase/database'; -import { actions, middlewares } from '@nocobase/actions'; -import { sort } from '@nocobase/actions/lib/actions/common'; +import { actions, middlewares, Context, Next } from '@nocobase/actions'; import { cloneDeep, omit } from 'lodash'; -export const create = async (ctx: actions.Context, next: actions.Next) => { +export const create = async (ctx: Context, next: Next) => { const values = cloneDeep(ctx.action.params.values); ctx.action.mergeParams( { - values: cloneDeep(omit(values, [ - '__insertAfter__', - '__insertBefore__', - '__prepend__', - '_isJSONSchemaObject', - ])), + values: cloneDeep( + omit(values, [ + '__insertAfter__', + '__insertBefore__', + '__prepend__', + '_isJSONSchemaObject', + ]), + ), }, { payload: 'replace', }, ); - await actions.common.create(ctx, async () => { }); + await actions.create(ctx, async () => {}); const sticky = values['__prepend__']; const targetKey = values['__insertAfter__'] || values['__insertBefore__']; if (sticky || targetKey) { @@ -27,46 +27,43 @@ export const create = async (ctx: actions.Context, next: actions.Next) => { { associatedKey: values.parentKey, resourceKey: body.key, - values: sticky ? { - field: 'sort', - sticky: true, - target: {}, - } : { - field: 'sort', - insertAfter: !!values['__insertAfter__'], - target: { - key: targetKey, - }, - }, + ...(sticky + ? { + sticky: true, + } + : { + method: values['__insertAfter__'] ? 'insertAfter' : null, + targetId: targetKey, + }), }, { payload: 'replace', }, ); - // console.log(ctx.action.params.values); - await middlewares.associated(ctx, async () => { }); - await sort(ctx, async () => { }); - // console.log(ctx.body.toJSON()); + await middlewares.associated(ctx, async () => {}); + await actions.sort(ctx, async () => {}); } await next(); }; -export const update = async (ctx: actions.Context, next: actions.Next) => { +export const update = async (ctx: Context, next: Next) => { const values = cloneDeep(ctx.action.params.values); ctx.action.mergeParams( { - values: cloneDeep(omit(values, [ - '__insertAfter__', - '__insertBefore__', - '__prepend__', - '_isJSONSchemaObject', - ])), + values: cloneDeep( + omit(values, [ + '__insertAfter__', + '__insertBefore__', + '__prepend__', + '_isJSONSchemaObject', + ]), + ), }, { payload: 'replace', }, ); - await actions.common.update(ctx, async () => { }); + await actions.update(ctx, async () => {}); const sticky = values['__prepend__']; const targetKey = values['__insertAfter__'] || values['__insertBefore__']; if (sticky || targetKey) { @@ -75,31 +72,29 @@ export const update = async (ctx: actions.Context, next: actions.Next) => { { associatedKey: values.parentKey, resourceKey: body.key, - values: sticky ? { - field: 'sort', - sticky: true, - target: {}, - } : { - field: 'sort', - insertAfter: !!values['__insertAfter__'], - target: { - key: targetKey, - }, - }, + ...(sticky + ? { + sticky: true, + } + : { + method: values['__insertAfter__'] ? 'insertAfter' : null, + // insertAfter: !!values['__insertAfter__'], + targetId: targetKey, + }), }, { payload: 'replace', }, ); // console.log(ctx.action.params.values); - await middlewares.associated(ctx, async () => { }); - await sort(ctx, async () => { }); + await middlewares.associated(ctx, async () => {}); + await actions.sort(ctx, async () => {}); // console.log(ctx.body.toJSON()); } await next(); }; -export const getTree = async (ctx: actions.Context, next: actions.Next) => { +export const getTree = async (ctx: Context, next: Next) => { const { resourceKey, filter } = ctx.action.params; const UISchema = ctx.db.getModel('ui_schemas'); if (resourceKey) { @@ -136,12 +131,12 @@ export const getTree = async (ctx: actions.Context, next: actions.Next) => { await next(); }; -export const getMenuItems = async (ctx: actions.Context, next: actions.Next) => { +export const getMenuItems = async (ctx: Context, next: Next) => { const UISchema = ctx.db.getModel('ui_schemas'); const schema = await UISchema.findOne({ where: { 'x-component': 'Menu', - } + }, }); ctx.body = await schema.getHierarchy(); await next(); diff --git a/packages/plugin-ui-schema/src/server.ts b/packages/plugin-ui-schema/src/server.ts index 436e8c3e6..a40247510 100644 --- a/packages/plugin-ui-schema/src/server.ts +++ b/packages/plugin-ui-schema/src/server.ts @@ -1,6 +1,6 @@ import path from 'path'; import { Application } from '@nocobase/server'; -import { registerModels, Table } from '@nocobase/database'; +import { registerModels } from '@nocobase/database'; import * as models from './models'; import * as actions from './actions'; diff --git a/packages/plugin-users/src/server.ts b/packages/plugin-users/src/server.ts index 42580c3f1..21fb2d525 100644 --- a/packages/plugin-users/src/server.ts +++ b/packages/plugin-users/src/server.ts @@ -4,13 +4,23 @@ import Resourcer from '@nocobase/resourcer'; import * as fields from './fields'; import * as usersActions from './actions/users'; import * as middlewares from './middlewares'; +import Application from '@nocobase/server'; -export default async function (options = {}) { +export default async function (this: Application, options = {}) { const database: Database = this.database; const resourcer: Resourcer = this.resourcer; registerFields(fields); + this.on('users.init', async () => { + const User = database.getModel('users'); + await User.create({ + nickname: '超级管理员', + email: process.env.ADMIN_EMAIL, + password: process.env.ADMIN_PASSWORD, + }); + }); + database.on('afterTableInit', (table: Table) => { let { createdBy, updatedBy } = table.getOptions(); if (createdBy !== false) { diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore new file mode 100644 index 000000000..1be219988 --- /dev/null +++ b/packages/utils/.gitignore @@ -0,0 +1,4 @@ +node_modules +yarn-error.log +.env +src2 \ No newline at end of file diff --git a/packages/plugin-collections-v04/.npmignore b/packages/utils/.npmignore similarity index 100% rename from packages/plugin-collections-v04/.npmignore rename to packages/utils/.npmignore diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 000000000..46b2ff6de --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,13 @@ +{ + "name": "@nocobase/utils", + "version": "0.4.0-alpha.7", + "main": "lib/index.js", + "types": "./lib/index.d.ts", + "license": "MIT", + "scripts": {}, + "dependencies": { + "deepmerge": "^4.2.2", + "flat-to-nested": "^1.1.1" + }, + "gitHead": "f0b335ac30f29f25c95d7d137655fa64d8d67f1e" +} diff --git a/packages/plugin-collections-v04/src/utils.ts b/packages/utils/src/index.ts similarity index 100% rename from packages/plugin-collections-v04/src/utils.ts rename to packages/utils/src/index.ts