mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-29 18:58:26 +08:00
lots of updates
This commit is contained in:
parent
d39a7d822d
commit
af02a895c3
@ -3,6 +3,13 @@ networks:
|
|||||||
nocobase:
|
nocobase:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
services:
|
services:
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- nocobase
|
||||||
|
ports:
|
||||||
|
- ${ADMINER_PORT}:8080
|
||||||
verdaccio:
|
verdaccio:
|
||||||
image: verdaccio/verdaccio
|
image: verdaccio/verdaccio
|
||||||
networks:
|
networks:
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
"build2": "lerna run build",
|
"build2": "lerna run build",
|
||||||
"build": "npm run build-father-build && node packages/father-build/bin/father-build.js",
|
"build": "npm run build-father-build && node packages/father-build/bin/father-build.js",
|
||||||
"build-father-build": "cd packages/father-build && npm run build",
|
"build-father-build": "cd packages/father-build && npm run build",
|
||||||
|
"db-migrate": "ts-node -r dotenv/config ./packages/api/src/migrate.ts",
|
||||||
"lint": "eslint --ext .ts,.tsx,.js \"packages/*/src/**.@(ts|tsx|js)\" --fix",
|
"lint": "eslint --ext .ts,.tsx,.js \"packages/*/src/**.@(ts|tsx|js)\" --fix",
|
||||||
"test": "npm run lint && jest"
|
"test": "npm run lint && jest"
|
||||||
},
|
},
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
"@nocobase/plugin-file-manager": "^0.4.0-alpha.7",
|
"@nocobase/plugin-file-manager": "^0.4.0-alpha.7",
|
||||||
"@nocobase/plugin-pages": "^0.4.0-alpha.7",
|
"@nocobase/plugin-pages": "^0.4.0-alpha.7",
|
||||||
"@nocobase/plugin-permissions": "^0.4.0-alpha.7",
|
"@nocobase/plugin-permissions": "^0.4.0-alpha.7",
|
||||||
|
"@nocobase/plugin-routes": "^0.4.0-alpha.7",
|
||||||
|
"@nocobase/plugin-ui-schema": "^0.4.0-alpha.7",
|
||||||
"@nocobase/plugin-users": "^0.4.0-alpha.7",
|
"@nocobase/plugin-users": "^0.4.0-alpha.7",
|
||||||
"@nocobase/server": "^0.4.0-alpha.7",
|
"@nocobase/server": "^0.4.0-alpha.7",
|
||||||
"koa-static": "^5.0.0"
|
"koa-static": "^5.0.0"
|
||||||
|
@ -39,13 +39,15 @@ const api = Api.create({
|
|||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
'@nocobase/plugin-collections',
|
'@nocobase/plugin-collections',
|
||||||
'@nocobase/plugin-action-logs',
|
'@nocobase/plugin-routes',
|
||||||
'@nocobase/plugin-pages',
|
'@nocobase/plugin-ui-schema',
|
||||||
'@nocobase/plugin-users',
|
// '@nocobase/plugin-action-logs',
|
||||||
'@nocobase/plugin-file-manager',
|
// '@nocobase/plugin-pages',
|
||||||
'@nocobase/plugin-permissions',
|
// '@nocobase/plugin-users',
|
||||||
'@nocobase/plugin-automations',
|
// '@nocobase/plugin-file-manager',
|
||||||
'@nocobase/plugin-china-region',
|
// '@nocobase/plugin-permissions',
|
||||||
|
// '@nocobase/plugin-automations',
|
||||||
|
// '@nocobase/plugin-china-region',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
|
@ -12,12 +12,6 @@ import { middlewares } from '@nocobase/server';
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.database.getModel('collections').load({skipExisting: true});
|
|
||||||
await api.database.getModel('collections').load({where: {
|
|
||||||
name: 'users',
|
|
||||||
}});
|
|
||||||
await api.database.getModel('automations').load();
|
|
||||||
|
|
||||||
api.use(middlewares.appDistServe({
|
api.use(middlewares.appDistServe({
|
||||||
root: process.env.APP_DIST,
|
root: process.env.APP_DIST,
|
||||||
useStaticServer: !(process.env.APP_USE_STATIC_SERVER === 'false' || !process.env.APP_USE_STATIC_SERVER),
|
useStaticServer: !(process.env.APP_USE_STATIC_SERVER === 'false' || !process.env.APP_USE_STATIC_SERVER),
|
||||||
|
@ -8,57 +8,7 @@ global.sync = {
|
|||||||
|
|
||||||
import Database from '@nocobase/database';
|
import Database from '@nocobase/database';
|
||||||
import api from '../app';
|
import api from '../app';
|
||||||
|
import * as uiSchema from './ui-schema';
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
title: '后台应用',
|
|
||||||
path: '/',
|
|
||||||
type: 'layout',
|
|
||||||
template: 'TopMenuLayout',
|
|
||||||
sort: 10,
|
|
||||||
redirect: '/admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '后台',
|
|
||||||
path: '/admin',
|
|
||||||
type: 'page',
|
|
||||||
inherit: false,
|
|
||||||
template: 'AdminLoader',
|
|
||||||
order: 230,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '登录页面',
|
|
||||||
path: '/login',
|
|
||||||
type: 'page',
|
|
||||||
inherit: false,
|
|
||||||
template: 'login',
|
|
||||||
order: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '注册页面',
|
|
||||||
path: '/register',
|
|
||||||
type: 'page',
|
|
||||||
inherit: false,
|
|
||||||
template: 'register',
|
|
||||||
order: 130,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '忘记密码',
|
|
||||||
path: '/lostpassword',
|
|
||||||
type: 'page',
|
|
||||||
inherit: false,
|
|
||||||
template: 'lostpassword',
|
|
||||||
order: 140,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '重置密码',
|
|
||||||
path: '/resetpassword',
|
|
||||||
type: 'page',
|
|
||||||
inherit: false,
|
|
||||||
template: 'resetpassword',
|
|
||||||
order: 150,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await api.loadPlugins();
|
await api.loadPlugins();
|
||||||
@ -66,196 +16,47 @@ const data = [
|
|||||||
await database.sync({
|
await database.sync({
|
||||||
// tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
|
// tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
|
||||||
});
|
});
|
||||||
const [Collection, Page, User] = database.getModels(['collections', 'pages', 'users']);
|
|
||||||
const tables = database.getTables([]);
|
|
||||||
for (let table of tables) {
|
|
||||||
// console.log(table.getName());
|
|
||||||
if (table.getName() === 'roles') {
|
|
||||||
// console.log('roles', table.getOptions())
|
|
||||||
}
|
|
||||||
await Collection.import(table.getOptions(), { update: true, migrate: false });
|
|
||||||
}
|
|
||||||
await Page.import(data);
|
|
||||||
|
|
||||||
const user = await User.create({
|
const Route = database.getModel('routes');
|
||||||
email: process.env.ADMIN_EMAIL,
|
|
||||||
password: process.env.ADMIN_PASSWORD,
|
|
||||||
});
|
|
||||||
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',
|
|
||||||
});
|
|
||||||
const Role = database.getModel('roles');
|
|
||||||
if (Role) {
|
|
||||||
const roles = await Role.bulkCreate([
|
|
||||||
{ title: '系统开发组', type: -1 },
|
|
||||||
// { title: '匿名用户组', type: 0 },
|
|
||||||
{ title: '普通用户组', default: true },
|
|
||||||
]);
|
|
||||||
await roles[0].updateAssociations({
|
|
||||||
users: user
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Action = database.getModel('actions');
|
const data = [
|
||||||
// 全局
|
|
||||||
await Action.bulkCreate([
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 导入地域数据
|
|
||||||
const ChinaRegion = database.getModel('china_regions');
|
|
||||||
ChinaRegion && await ChinaRegion.importData();
|
|
||||||
|
|
||||||
const Menu = database.getModel('menus');
|
|
||||||
|
|
||||||
const menus = [
|
|
||||||
{
|
{
|
||||||
title: '仪表盘',
|
type: 'redirect',
|
||||||
icon: 'DashboardOutlined',
|
from: '/',
|
||||||
type: 'group',
|
to: '/admin',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/:name(.+)?',
|
||||||
|
component: 'AdminLayout',
|
||||||
|
title: `后台`,
|
||||||
|
uiSchema: uiSchema.menu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'AuthLayout',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
title: '欢迎光临',
|
name: 'login',
|
||||||
icon: 'DatabaseOutlined',
|
path: '/login',
|
||||||
type: 'page',
|
component: 'DefaultPage',
|
||||||
views: [],
|
title: `登录`,
|
||||||
name: 'welcome',
|
uiSchema: uiSchema.login,
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '数据',
|
|
||||||
icon: 'DatabaseOutlined',
|
|
||||||
type: 'group',
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '用户',
|
|
||||||
icon: 'TeamOutlined',
|
|
||||||
type: 'group',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: '用户管理',
|
|
||||||
icon: 'DatabaseOutlined',
|
|
||||||
type: 'page',
|
|
||||||
views: ['users.table'],
|
|
||||||
name: 'users',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '日志',
|
|
||||||
icon: 'NotificationOutlined',
|
|
||||||
type: 'group',
|
|
||||||
developerMode: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: '操作记录',
|
|
||||||
icon: 'DatabaseOutlined',
|
|
||||||
type: 'group',
|
|
||||||
developerMode: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
title: '全部数据',
|
|
||||||
type: 'page',
|
|
||||||
views: ['action_logs.table'],
|
|
||||||
developerMode: true,
|
|
||||||
name: 'auditing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '新增数据',
|
|
||||||
type: 'page',
|
|
||||||
views: ['action_logs.create'],
|
|
||||||
developerMode: true,
|
|
||||||
name: 'create-auditing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '更新数据',
|
|
||||||
type: 'page',
|
|
||||||
views: ['action_logs.update'],
|
|
||||||
developerMode: true,
|
|
||||||
name: 'update-auditing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '删除数据',
|
|
||||||
type: 'page',
|
|
||||||
views: ['action_logs.destroy'],
|
|
||||||
developerMode: true,
|
|
||||||
name: 'destroy-auditing',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '配置',
|
|
||||||
icon: 'SettingOutlined',
|
|
||||||
type: 'group',
|
|
||||||
developerMode: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'menus',
|
|
||||||
title: '菜单和页面配置',
|
|
||||||
icon: 'MenuOutlined',
|
|
||||||
type: 'page',
|
|
||||||
views: ['menus.table'],
|
|
||||||
developerMode: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'collections',
|
name: 'register',
|
||||||
title: '数据表配置',
|
path: '/register',
|
||||||
icon: 'DatabaseOutlined',
|
component: 'DefaultPage',
|
||||||
type: 'page',
|
title: `注册`,
|
||||||
views: ['collections.table'],
|
uiSchema: uiSchema.register,
|
||||||
developerMode: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'permissions',
|
|
||||||
title: '权限配置',
|
|
||||||
icon: 'MenuOutlined',
|
|
||||||
type: 'page',
|
|
||||||
views: ['roles.table'],
|
|
||||||
developerMode: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'automations',
|
|
||||||
title: '自动化配置',
|
|
||||||
icon: 'MenuOutlined',
|
|
||||||
type: 'page',
|
|
||||||
views: ['automations.table'],
|
|
||||||
developerMode: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'system_settings',
|
|
||||||
title: '系统配置',
|
|
||||||
icon: 'DatabaseOutlined',
|
|
||||||
type: 'page',
|
|
||||||
views: ['system_settings.descriptions'],
|
|
||||||
developerMode: true,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const item of menus) {
|
for (const item of data) {
|
||||||
const menu = await Menu.create(item);
|
const route = await Route.create(item);
|
||||||
await menu.updateAssociations(item);
|
await route.updateAssociations(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.close();
|
await database.close();
|
||||||
})();
|
})();
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import api from '../app';
|
|
||||||
import Database from '@nocobase/database';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await api.loadPlugins();
|
|
||||||
await api.database.getModel('collections').load({ skipExisting: true });
|
|
||||||
const database: Database = api.database;
|
|
||||||
const [Field] = database.getModels(['fields']);
|
|
||||||
|
|
||||||
const fields = await Field.findAll({
|
|
||||||
where: {
|
|
||||||
interface: 'multipleSelect',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const field of fields) {
|
|
||||||
const M = database.getModel(field.collection_name);
|
|
||||||
const models = await M.findAll();
|
|
||||||
for (const model of models) {
|
|
||||||
let value = model.get(field.name);
|
|
||||||
if (!value) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
value = [value];
|
|
||||||
}
|
|
||||||
model.set(field.name, value);
|
|
||||||
await model.save();
|
|
||||||
console.log(field.name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
@ -1,18 +0,0 @@
|
|||||||
import api from '../app';
|
|
||||||
import Database from '@nocobase/database';
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await api.loadPlugins();
|
|
||||||
const database: Database = api.database;
|
|
||||||
await api.database.sync({
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Collection] = database.getModels(['collections']);
|
|
||||||
|
|
||||||
const tables = database.getTables();
|
|
||||||
|
|
||||||
for (let table of tables) {
|
|
||||||
console.log(table.getName());
|
|
||||||
await Collection.import(table.getOptions(), { migrate: false });
|
|
||||||
}
|
|
||||||
})();
|
|
3
packages/api/src/migrations/ui-schema/index.ts
Normal file
3
packages/api/src/migrations/ui-schema/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './login';
|
||||||
|
export * from './menu';
|
||||||
|
export * from './register';
|
66
packages/api/src/migrations/ui-schema/login.ts
Normal file
66
packages/api/src/migrations/ui-schema/login.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
|
||||||
|
export const login: ISchema = {
|
||||||
|
key: 'dtf9j0b8p9u',
|
||||||
|
name: 'dtf9j0b8p9u',
|
||||||
|
type: 'object',
|
||||||
|
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: '注册账号',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
14
packages/api/src/migrations/ui-schema/menu.ts
Normal file
14
packages/api/src/migrations/ui-schema/menu.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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 }}',
|
||||||
|
},
|
||||||
|
};
|
78
packages/api/src/migrations/ui-schema/register.ts
Normal file
78
packages/api/src/migrations/ui-schema/register.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
export const register = {
|
||||||
|
key: '46qlxqam3xk',
|
||||||
|
name: '46qlxqam3xk',
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '用户名',
|
||||||
|
style: {
|
||||||
|
// width: 240,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pwd1: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '密码',
|
||||||
|
checkStrength: true,
|
||||||
|
style: {
|
||||||
|
// width: 240,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pwd2: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Password',
|
||||||
|
'x-component-props': {
|
||||||
|
placeholder: '密码',
|
||||||
|
checkStrength: true,
|
||||||
|
style: {
|
||||||
|
// width: 240,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: '使用已有账号登录',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -1,18 +1,20 @@
|
|||||||
const api = {
|
const api = {
|
||||||
'/api/routes:getAccessible': require('./routes-getAccessible').default,
|
'/api/routes:getAccessible': require('./routes-getAccessible').default,
|
||||||
'/api/blocks:getSchema/login': require('./blocks-getSchema/login').default,
|
// '/api/blocks:getSchema/login': require('./blocks-getSchema/login').default,
|
||||||
'/api/blocks:getSchema/register': require('./blocks-getSchema/register').default,
|
// '/api/blocks:getSchema/register': require('./blocks-getSchema/register').default,
|
||||||
'/api/blocks:getSchema/item1': require('./blocks-getSchema/item1').default,
|
// '/api/blocks:getSchema/item1': require('./blocks-getSchema/item1').default,
|
||||||
'/api/blocks:getSchema/item2': require('./blocks-getSchema/item2').default,
|
// '/api/blocks:getSchema/item2': require('./blocks-getSchema/item2').default,
|
||||||
'/api/blocks:getSchema/item22': require('./blocks-getSchema/item22').default,
|
// '/api/blocks:getSchema/item22': require('./blocks-getSchema/item22').default,
|
||||||
'/api/blocks:getSchema/item3': require('./blocks-getSchema/item3').default,
|
// '/api/blocks:getSchema/item3': require('./blocks-getSchema/item3').default,
|
||||||
'/api/blocks:getSchema/item4': require('./blocks-getSchema/item4').default,
|
// '/api/blocks:getSchema/item4': require('./blocks-getSchema/item4').default,
|
||||||
'/api/blocks:getSchema/item5': require('./blocks-getSchema/item5').default,
|
// '/api/blocks:getSchema/item5': require('./blocks-getSchema/item5').default,
|
||||||
'/api/blocks:getSchema/menu': require('./blocks-getSchema/menu').default,
|
// '/api/blocks:getSchema/menu': require('./blocks-getSchema/menu').default,
|
||||||
|
'/api/ui-schemas:getTree/login': require('./ui-schemas-getTree/login').default,
|
||||||
|
'/api/ui-schemas:getTree/register': require('./ui-schemas-getTree/register').default,
|
||||||
|
'/api/ui-schemas:getTree/menu': require('./ui-schemas-getTree/menu').default,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(service) {
|
export function request(service) {
|
||||||
console.log({ service })
|
|
||||||
let url = null;
|
let url = null;
|
||||||
if (typeof service === 'string') {
|
if (typeof service === 'string') {
|
||||||
url = service;
|
url = service;
|
||||||
@ -20,6 +22,8 @@ export function request(service) {
|
|||||||
url = service.url;
|
url = service.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('request.url', url)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (api[url]) {
|
if (api[url]) {
|
||||||
|
@ -17,7 +17,7 @@ export default [
|
|||||||
path: '/admin/:name(.+)?',
|
path: '/admin/:name(.+)?',
|
||||||
component: 'AdminLayout',
|
component: 'AdminLayout',
|
||||||
title: `后台 - ${Mock.mock('@string')}`,
|
title: `后台 - ${Mock.mock('@string')}`,
|
||||||
blockId: 'menu',
|
schemaName: 'menu',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'AuthLayout',
|
component: 'AuthLayout',
|
||||||
@ -27,14 +27,14 @@ export default [
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
component: 'DefaultPage',
|
component: 'DefaultPage',
|
||||||
title: `登录 - ${Mock.mock('@string')}`,
|
title: `登录 - ${Mock.mock('@string')}`,
|
||||||
blockId: 'login',
|
schemaName: 'login',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'register',
|
name: 'register',
|
||||||
path: '/register',
|
path: '/register',
|
||||||
component: 'DefaultPage',
|
component: 'DefaultPage',
|
||||||
title: `注册 - ${Mock.mock('@string')}`,
|
title: `注册 - ${Mock.mock('@string')}`,
|
||||||
blockId: 'register',
|
schemaName: 'register',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -212,6 +212,7 @@ Action.Popover = observer((props) => {
|
|||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
|
placement={'bottom'}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onVisibleChange={(visible) => {
|
onVisibleChange={(visible) => {
|
||||||
setVisible(visible);
|
setVisible(visible);
|
||||||
|
@ -58,10 +58,46 @@ const schema = {
|
|||||||
'x-component': 'Form',
|
'x-component': 'Form',
|
||||||
properties: {
|
properties: {
|
||||||
filter: {
|
filter: {
|
||||||
type: 'string',
|
name: 'filter',
|
||||||
title: '字段1',
|
type: 'object',
|
||||||
'x-decorator': 'FormItem',
|
'x-component': 'Filter',
|
||||||
'x-component': 'Input',
|
properties: {
|
||||||
|
column1: {
|
||||||
|
type: 'void',
|
||||||
|
title: '字段1',
|
||||||
|
'x-component': 'Filter.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
operations: [
|
||||||
|
{ label: '等于', value: 'eq' },
|
||||||
|
{ label: '不等于', value: 'ne' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
field1: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
title: '字段2',
|
||||||
|
'x-component': 'Filter.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
operations: [
|
||||||
|
{ label: '大于', value: 'gt' },
|
||||||
|
{ label: '小于', value: 'lt' },
|
||||||
|
{ label: '非空', value: 'notNull', noValue: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
field1: {
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
@ -195,7 +195,7 @@ function LayoutWithMenu({ schema }) {
|
|||||||
|
|
||||||
function Content({ activeKey }) {
|
function Content({ activeKey }) {
|
||||||
const { data = {}, loading } = useRequest(
|
const { data = {}, loading } = useRequest(
|
||||||
`/api/blocks:getSchema/${activeKey}`,
|
`/api/ui-schemas:getTree/${activeKey}?filter[parentId]=${activeKey}`,
|
||||||
{
|
{
|
||||||
refreshDeps: [activeKey],
|
refreshDeps: [activeKey],
|
||||||
formatResult: (result) => result?.data,
|
formatResult: (result) => result?.data,
|
||||||
@ -212,12 +212,13 @@ function Content({ activeKey }) {
|
|||||||
export function AdminLayout({ route, children }: any) {
|
export function AdminLayout({ route, children }: any) {
|
||||||
|
|
||||||
const { data = {}, loading } = useRequest(
|
const { data = {}, loading } = useRequest(
|
||||||
`/api/blocks:getSchema/${route.blockId}`,
|
`/api/ui-schemas:getTree/${route.schemaName}`,
|
||||||
{
|
{
|
||||||
refreshDeps: [route],
|
refreshDeps: [route],
|
||||||
formatResult: (result) => result?.data,
|
formatResult: (result) => result?.data,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Spin />;
|
return <Spin />;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { SchemaRenderer } from '../../schemas';
|
|||||||
|
|
||||||
export function DefaultPage({ route }) {
|
export function DefaultPage({ route }) {
|
||||||
const { data = {}, loading } = useRequest(
|
const { data = {}, loading } = useRequest(
|
||||||
`/api/blocks:getSchema/${route.blockId}`,
|
`/api/ui-schemas:getTree/${route.schemaName}`,
|
||||||
{
|
{
|
||||||
refreshDeps: [route],
|
refreshDeps: [route],
|
||||||
formatResult: (result) => result?.data,
|
formatResult: (result) => result?.data,
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { getDatabase } from '..';
|
||||||
|
import Database from '../..';
|
||||||
|
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
db = getDatabase();
|
||||||
|
db.table({
|
||||||
|
name: 'routes',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'key',
|
||||||
|
prefix: 'r_',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'uiSchema',
|
||||||
|
target: 'ui_schemas',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
db.table({
|
||||||
|
name: 'ui_schemas',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'key',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
await db.sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateAssociations', () => {
|
||||||
|
it('belongsTo', async () => {
|
||||||
|
const [Route, Schema] = db.getModels(['routes', 'ui_schemas']);
|
||||||
|
const route = await Route.create();
|
||||||
|
await route.updateAssociations({
|
||||||
|
uiSchema: {
|
||||||
|
key: '6kyo0t1jnpw',
|
||||||
|
// name: '6kyo0t1jnpw'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const schema = await Schema.findByPk('6kyo0t1jnpw');
|
||||||
|
expect(schema).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
@ -18,7 +18,7 @@ import { getDataTypeKey } from '.';
|
|||||||
import Table from '../table';
|
import Table from '../table';
|
||||||
import Database from '../database';
|
import Database from '../database';
|
||||||
import Model, { ModelCtor } from '../model';
|
import Model, { ModelCtor } from '../model';
|
||||||
import { whereCompare, isNumber } from '../utils';
|
import { whereCompare, isNumber, uid } from '../utils';
|
||||||
|
|
||||||
export interface IField {
|
export interface IField {
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ export class ARRAY extends Column {
|
|||||||
|
|
||||||
export class JSON extends Column {
|
export class JSON extends Column {
|
||||||
public getDataType() {
|
public getDataType() {
|
||||||
return DataTypes.JSONB;
|
return DataTypes.JSON;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +352,33 @@ export class JSONB extends Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UUID extends Column {
|
export class UUID extends Column {
|
||||||
|
public getDataType() {
|
||||||
|
return DataTypes.UUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UID extends Column {
|
||||||
|
|
||||||
|
constructor(options: Options.StringOptions, context: FieldContext) {
|
||||||
|
super(options, context);
|
||||||
|
const { name, prefix = '' } = options;
|
||||||
|
const Model = context.sourceTable.getModel();
|
||||||
|
Model.addHook('beforeCreate', (model) => {
|
||||||
|
if (!model.get(name)) {
|
||||||
|
model.set(name, `${prefix}${uid()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDataType() {
|
||||||
|
return DataTypes.STRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UUIDV4 extends Column {
|
||||||
|
public getDataType() {
|
||||||
|
return DataTypes.UUIDV4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HasOneAccessors {
|
export interface HasOneAccessors {
|
||||||
|
@ -310,17 +310,22 @@ export abstract class Model extends SequelizeModel {
|
|||||||
? association.options.targetKey
|
? association.options.targetKey
|
||||||
: association.options.sourceKey;
|
: association.options.sourceKey;
|
||||||
if (data[targetAttribute]) {
|
if (data[targetAttribute]) {
|
||||||
await this[accessors.set](data[targetAttribute], opts);
|
if (Object.keys(data).length > 0) {
|
||||||
if (Object.keys(data).length > 1) {
|
|
||||||
const target = await Target.findOne({
|
const target = await Target.findOne({
|
||||||
where: {
|
where: {
|
||||||
[targetAttribute]: data[targetAttribute],
|
[targetAttribute]: data[targetAttribute],
|
||||||
},
|
},
|
||||||
transaction
|
transaction
|
||||||
});
|
});
|
||||||
await target.update(data, opts);
|
if (target) {
|
||||||
// @ts-ignore
|
await this[accessors.set](data[targetAttribute], opts);
|
||||||
await target.updateAssociations(data, opts);
|
await target.update(data, opts);
|
||||||
|
// @ts-ignore
|
||||||
|
await target.updateAssociations(data, opts);
|
||||||
|
} else {
|
||||||
|
const t = await this[accessors.create](data, opts);
|
||||||
|
await t.updateAssociations(data, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const t = await this[accessors.create](data, opts);
|
const t = await this[accessors.create](data, opts);
|
||||||
@ -513,8 +518,8 @@ export abstract class Model extends SequelizeModel {
|
|||||||
*/
|
*/
|
||||||
async updateAssociations(data: any, options: UpdateAssociationOptions = {}) {
|
async updateAssociations(data: any, options: UpdateAssociationOptions = {}) {
|
||||||
const { transaction = await this.sequelize.transaction() } = options;
|
const { transaction = await this.sequelize.transaction() } = options;
|
||||||
const table = this.database.getTable(this.constructor.name);
|
// @ts-ignore 判断 Model.associations 更准确
|
||||||
for (const key of table.getAssociations().keys()) {
|
for (const key of Object.keys(this.constructor.associations)) {
|
||||||
// 如果 key 不存在才跳过
|
// 如果 key 不存在才跳过
|
||||||
if (!Object.keys(data).includes(key)) {
|
if (!Object.keys(data).includes(key)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -336,3 +336,14 @@ export function isNumber(num) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let IDX = 36,
|
||||||
|
HEX = ''
|
||||||
|
while (IDX--) HEX += IDX.toString(36)
|
||||||
|
|
||||||
|
export function uid(len?: number) {
|
||||||
|
let str = '',
|
||||||
|
num = len || 11
|
||||||
|
while (num--) str += HEX[(Math.random() * 36) | 0]
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
7
packages/plugin-collections-v04/.npmignore
Normal file
7
packages/plugin-collections-v04/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
16
packages/plugin-collections-v04/package.json
Normal file
16
packages/plugin-collections-v04/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
144
packages/plugin-collections-v04/src/__tests__/index.ts
Normal file
144
packages/plugin-collections-v04/src/__tests__/index.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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<supertest.Response>;
|
||||||
|
list: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
create: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
update: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
destroy: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
[name: string]: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
379
packages/plugin-collections-v04/src/collections/collections.ts
Normal file
379
packages/plugin-collections-v04/src/collections/collections.ts
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
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;
|
690
packages/plugin-collections-v04/src/collections/fields.ts
Normal file
690
packages/plugin-collections-v04/src/collections/fields.ts
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
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;
|
241
packages/plugin-collections-v04/src/models/collection.ts
Normal file
241
packages/plugin-collections-v04/src/models/collection.ts
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
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<TableOptions> {
|
||||||
|
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<CollectionModel> {
|
||||||
|
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;
|
263
packages/plugin-collections-v04/src/models/field.ts
Normal file
263
packages/plugin-collections-v04/src/models/field.ts
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
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<FieldOptions> {
|
||||||
|
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<any> {
|
||||||
|
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;
|
7
packages/plugin-collections-v04/src/models/index.ts
Normal file
7
packages/plugin-collections-v04/src/models/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * from './base';
|
||||||
|
export * from './action';
|
||||||
|
export * from './collection';
|
||||||
|
export * from './field';
|
||||||
|
export * from './tab';
|
||||||
|
export * from './view';
|
||||||
|
export * from './page';
|
77
packages/plugin-collections-v04/src/server.ts
Normal file
77
packages/plugin-collections-v04/src/server.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
9
packages/plugin-collections-v04/src/utils.ts
Normal file
9
packages/plugin-collections-v04/src/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
|
||||||
|
const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray
|
||||||
|
|
||||||
|
export function merge(obj1: any, obj2: any) {
|
||||||
|
return deepmerge(obj1, obj2, {
|
||||||
|
arrayMerge: overwriteMerge,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Agent, getAgent, getApp } from '.';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
|
||||||
|
describe('collection hooks', () => {
|
||||||
|
let app: Application;
|
||||||
|
let agent: Agent;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await getApp();
|
||||||
|
db = app.database;
|
||||||
|
agent = getAgent(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => app.database.close());
|
||||||
|
|
||||||
|
it('create table', async () => {
|
||||||
|
const Collection = db.getModel('collections');
|
||||||
|
const collection = await Collection.create({
|
||||||
|
title: 'tests',
|
||||||
|
// name: 'tests',
|
||||||
|
});
|
||||||
|
await collection.updateAssociations({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
dataType: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'content',
|
||||||
|
dataType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataType: 'integer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,377 +3,28 @@ import { TableOptions } from '@nocobase/database';
|
|||||||
export default {
|
export default {
|
||||||
name: 'collections',
|
name: 'collections',
|
||||||
title: '数据表配置',
|
title: '数据表配置',
|
||||||
internal: true,
|
model: 'Collection',
|
||||||
sortable: true,
|
|
||||||
draggable: true,
|
|
||||||
model: 'CollectionModel',
|
|
||||||
developerMode: true,
|
|
||||||
createdAt: 'createdTime',
|
|
||||||
updatedAt: 'updatedTime',
|
|
||||||
fields: [
|
fields: [
|
||||||
// {
|
|
||||||
// interface: 'sort',
|
|
||||||
// type: 'sort',
|
|
||||||
// name: 'sort',
|
|
||||||
// title: '排序',
|
|
||||||
// component: {
|
|
||||||
// type: 'sort',
|
|
||||||
// className: 'drag-visible',
|
|
||||||
// width: 60,
|
|
||||||
// showInTable: true,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
interface: 'string',
|
type: 'uid',
|
||||||
|
name: 'name',
|
||||||
|
primaryKey: true,
|
||||||
|
prefix: 't_',
|
||||||
|
},
|
||||||
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'title',
|
name: 'title',
|
||||||
title: '数据表名称',
|
|
||||||
required: true,
|
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',
|
type: 'json',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
title: '配置信息',
|
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
component: {
|
|
||||||
type: 'hidden',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'boolean',
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'internal',
|
|
||||||
title: '系统内置',
|
|
||||||
defaultValue: false,
|
|
||||||
developerMode: true,
|
|
||||||
component: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'linkTo',
|
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
name: 'fields',
|
name: 'fields',
|
||||||
title: '字段',
|
|
||||||
sourceKey: 'name',
|
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;
|
} as TableOptions;
|
||||||
|
@ -1,690 +1,55 @@
|
|||||||
import { TableOptions } from '@nocobase/database';
|
import { TableOptions } from '@nocobase/database';
|
||||||
import { types, getOptions } from '../interfaces';
|
import { DataTypes } from 'sequelize';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'fields',
|
name: 'fields',
|
||||||
title: '字段配置',
|
title: '字段配置',
|
||||||
internal: true,
|
model: 'Field',
|
||||||
draggable: true,
|
|
||||||
model: 'FieldModel',
|
|
||||||
developerMode: true,
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
interface: 'sort',
|
type: 'uid',
|
||||||
type: 'sort',
|
name: 'key',
|
||||||
name: 'sort',
|
primaryKey: true,
|
||||||
scope: ['collection'],
|
prefix: 'f_',
|
||||||
title: '排序',
|
// autoIncrement: false,
|
||||||
component: {
|
// defaultValue: DataTypes.UUIDV4,
|
||||||
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',
|
type: 'string',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
title: '标识',
|
|
||||||
required: true,
|
|
||||||
createOnly: true,
|
|
||||||
developerMode: true,
|
|
||||||
component: {
|
|
||||||
type: 'string',
|
|
||||||
showInTable: true,
|
|
||||||
showInDetail: true,
|
|
||||||
showInForm: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'select',
|
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'interface',
|
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',
|
type: 'string',
|
||||||
name: 'type',
|
name: 'dataType',
|
||||||
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',
|
type: 'hasMany',
|
||||||
name: 'children',
|
name: 'children',
|
||||||
target: 'fields',
|
target: 'fields',
|
||||||
sourceKey: 'id',
|
sourceKey: 'key',
|
||||||
foreignKey: 'parent_id',
|
foreignKey: 'parentKey',
|
||||||
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',
|
interface: 'linkTo',
|
||||||
type: 'belongsTo',
|
type: 'belongsTo',
|
||||||
name: 'collection',
|
name: 'collection',
|
||||||
title: '所属数据表',
|
|
||||||
target: 'collections',
|
target: 'collections',
|
||||||
targetKey: 'name',
|
targetKey: 'name',
|
||||||
labelField: 'title',
|
|
||||||
component: {
|
|
||||||
type: 'drawerSelect',
|
|
||||||
// showInTable: true,
|
|
||||||
'x-component-props': {
|
|
||||||
resourceName: 'collections.fields',
|
|
||||||
labelField: 'title',
|
|
||||||
valueField: 'name',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'boolean',
|
type: 'belongsTo',
|
||||||
type: 'boolean',
|
name: 'uiSchema',
|
||||||
name: 'developerMode',
|
target: 'ui_schemas',
|
||||||
title: '开发者模式',
|
|
||||||
defaultValue: false,
|
|
||||||
component: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
interface: 'json',
|
|
||||||
type: 'json',
|
|
||||||
name: 'component',
|
|
||||||
title: '前端组件',
|
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
component: {
|
|
||||||
type: 'hidden',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'json',
|
|
||||||
type: 'json',
|
type: 'json',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
title: '配置信息',
|
|
||||||
defaultValue: {},
|
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;
|
} as TableOptions;
|
||||||
|
@ -1,241 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import BaseModel from './base';
|
import { Model } from '@nocobase/database';
|
||||||
import Field from './field';
|
|
||||||
import { TableOptions } from '@nocobase/database';
|
export class Collection extends Model {
|
||||||
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<TableOptions> {
|
|
||||||
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<CollectionModel> {
|
|
||||||
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;
|
|
||||||
|
@ -1,263 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import BaseModel from './base';
|
import { Model } from '@nocobase/database';
|
||||||
import { FieldOptions, BELONGSTO, BELONGSTOMANY, HASMANY } from '@nocobase/database';
|
|
||||||
import * as types from '../interfaces/types';
|
export class Field extends Model {
|
||||||
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<FieldOptions> {
|
|
||||||
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<any> {
|
|
||||||
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;
|
|
||||||
|
@ -1,7 +1,2 @@
|
|||||||
export * from './base';
|
|
||||||
export * from './action';
|
|
||||||
export * from './collection';
|
export * from './collection';
|
||||||
export * from './field';
|
export * from './field';
|
||||||
export * from './tab';
|
|
||||||
export * from './view';
|
|
||||||
export * from './page';
|
|
||||||
|
@ -1,77 +1,19 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Application } from '@nocobase/server';
|
import { Application } from '@nocobase/server';
|
||||||
import hooks from './hooks';
|
|
||||||
import { registerModels, Table } from '@nocobase/database';
|
import { registerModels, Table } from '@nocobase/database';
|
||||||
import * as models from './models';
|
import * as models from './models';
|
||||||
|
|
||||||
export default async function (this: Application, options = {}) {
|
export default async function (this: Application, options = {}) {
|
||||||
const database = this.database;
|
const database = this.database;
|
||||||
const resourcer = this.resourcer;
|
|
||||||
// 提供全局的 models 注册机制
|
|
||||||
registerModels(models);
|
registerModels(models);
|
||||||
|
|
||||||
database.import({
|
database.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
|
||||||
database.addHook('afterUpdateAssociations', async function (model, options) {
|
database.getModel('fields').beforeCreate((model) => {
|
||||||
if (model instanceof models.FieldModel) {
|
if (!model.get('name')) {
|
||||||
if (model.get('interface') === 'subTable') {
|
model.set('name', model.get('key'));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
7
packages/plugin-routes/.npmignore
Normal file
7
packages/plugin-routes/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
16
packages/plugin-routes/package.json
Normal file
16
packages/plugin-routes/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-routes",
|
||||||
|
"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"
|
||||||
|
}
|
144
packages/plugin-routes/src/__tests__/index.ts
Normal file
144
packages/plugin-routes/src/__tests__/index.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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<supertest.Response>;
|
||||||
|
list: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
create: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
update: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
destroy: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
[name: string]: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
82
packages/plugin-routes/src/__tests__/routes.test.ts
Normal file
82
packages/plugin-routes/src/__tests__/routes.test.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Agent, getAgent, getApp } from '.';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
|
||||||
|
describe('routes', () => {
|
||||||
|
let app: Application;
|
||||||
|
let agent: Agent;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await getApp();
|
||||||
|
db = app.database;
|
||||||
|
agent = getAgent(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => app.database.close());
|
||||||
|
|
||||||
|
it.only('create route', async () => {
|
||||||
|
const Route = db.getModel('routes');
|
||||||
|
const item = {
|
||||||
|
path: '/admin/:name(.+)?',
|
||||||
|
component: 'AdminLayout',
|
||||||
|
title: `后台`,
|
||||||
|
uiSchema: {
|
||||||
|
name: 'menu',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(Route.associations);
|
||||||
|
const route = await Route.create(item);
|
||||||
|
await route.updateAssociations(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create route', async () => {
|
||||||
|
const Route = db.getModel('routes');
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
type: 'redirect',
|
||||||
|
from: '/',
|
||||||
|
to: '/admin',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/:name(.+)?',
|
||||||
|
component: 'AdminLayout',
|
||||||
|
title: `后台`,
|
||||||
|
uiSchema: {
|
||||||
|
key: 'qqzzjakwkwl',
|
||||||
|
name: 'qqzzjakwkwl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'AuthLayout',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'login',
|
||||||
|
path: '/login',
|
||||||
|
component: 'DefaultPage',
|
||||||
|
title: `登录`,
|
||||||
|
uiSchema: {
|
||||||
|
key: 'dtf9j0b8p9u',
|
||||||
|
name: 'dtf9j0b8p9u',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'register',
|
||||||
|
path: '/register',
|
||||||
|
component: 'DefaultPage',
|
||||||
|
title: `注册`,
|
||||||
|
uiSchema: {
|
||||||
|
key: '46qlxqam3xk',
|
||||||
|
name: '46qlxqam3xk',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for (const item of data) {
|
||||||
|
const route = await Route.create(item);
|
||||||
|
await route.updateAssociations(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
35
packages/plugin-routes/src/collections/routes.ts
Normal file
35
packages/plugin-routes/src/collections/routes.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'routes',
|
||||||
|
title: '路由表',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'key',
|
||||||
|
prefix: 'r_',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'children',
|
||||||
|
target: 'routes',
|
||||||
|
sourceKey: 'key',
|
||||||
|
foreignKey: 'parentKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'belongsTo',
|
||||||
|
name: 'uiSchema',
|
||||||
|
target: 'ui_schemas',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'json',
|
||||||
|
name: 'options',
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as TableOptions;
|
1
packages/plugin-routes/src/models/index.ts
Normal file
1
packages/plugin-routes/src/models/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './route';
|
6
packages/plugin-routes/src/models/route.ts
Normal file
6
packages/plugin-routes/src/models/route.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
|
export class Route extends Model {
|
||||||
|
|
||||||
|
}
|
13
packages/plugin-routes/src/server.ts
Normal file
13
packages/plugin-routes/src/server.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import { registerModels } from '@nocobase/database';
|
||||||
|
import * as models from './models';
|
||||||
|
|
||||||
|
export default async function (this: Application, options = {}) {
|
||||||
|
const database = this.database;
|
||||||
|
registerModels(models);
|
||||||
|
|
||||||
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
}
|
9
packages/plugin-routes/src/utils.ts
Normal file
9
packages/plugin-routes/src/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
|
||||||
|
const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray
|
||||||
|
|
||||||
|
export function merge(obj1: any, obj2: any) {
|
||||||
|
return deepmerge(obj1, obj2, {
|
||||||
|
arrayMerge: overwriteMerge,
|
||||||
|
});
|
||||||
|
}
|
7
packages/plugin-ui-schema/.npmignore
Normal file
7
packages/plugin-ui-schema/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
docs
|
||||||
|
__tests__
|
||||||
|
tsconfig.json
|
||||||
|
src
|
||||||
|
.fatherrc.ts
|
16
packages/plugin-ui-schema/package.json
Normal file
16
packages/plugin-ui-schema/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-ui-schema",
|
||||||
|
"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"
|
||||||
|
}
|
144
packages/plugin-ui-schema/src/__tests__/index.ts
Normal file
144
packages/plugin-ui-schema/src/__tests__/index.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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<supertest.Response>;
|
||||||
|
list: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
create: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
update: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
destroy: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
[name: string]: (params?: ActionParams) => Promise<supertest.Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
50
packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts
Normal file
50
packages/plugin-ui-schema/src/__tests__/ui_schemas.test.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Agent, getAgent, getApp } from '.';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import Database from '@nocobase/database';
|
||||||
|
|
||||||
|
describe('ui_schemas', () => {
|
||||||
|
let app: Application;
|
||||||
|
let agent: Agent;
|
||||||
|
let db: Database;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await getApp();
|
||||||
|
db = app.database;
|
||||||
|
agent = getAgent(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => app.database.close());
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
38
packages/plugin-ui-schema/src/collections/ui_schemas.ts
Normal file
38
packages/plugin-ui-schema/src/collections/ui_schemas.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { TableOptions } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ui_schemas',
|
||||||
|
title: '字段配置',
|
||||||
|
model: 'UISchema',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'uid',
|
||||||
|
name: 'key',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'json',
|
||||||
|
name: 'options',
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'hasMany',
|
||||||
|
name: 'children',
|
||||||
|
target: 'ui_schemas',
|
||||||
|
sourceKey: 'key',
|
||||||
|
foreignKey: 'parentKey',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as TableOptions;
|
1
packages/plugin-ui-schema/src/models/index.ts
Normal file
1
packages/plugin-ui-schema/src/models/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ui-schema';
|
62
packages/plugin-ui-schema/src/models/ui-schema.ts
Normal file
62
packages/plugin-ui-schema/src/models/ui-schema.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { Model } from '@nocobase/database';
|
||||||
|
|
||||||
|
export class UISchema extends Model {
|
||||||
|
static async create(value?: any, options?: any): Promise<any> {
|
||||||
|
// console.log({ value });
|
||||||
|
const attributes = this.toAttributes(value);
|
||||||
|
// @ts-ignore
|
||||||
|
const model: Model = await super.create(attributes, options);
|
||||||
|
if (!attributes.children) {
|
||||||
|
attributes.children = this.properties2children(attributes.properties);
|
||||||
|
await model.updateAssociation('children', attributes.children, options);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static toAttributes(value = {}): any {
|
||||||
|
const data = _.cloneDeep(value);
|
||||||
|
const keys = [
|
||||||
|
'properties',
|
||||||
|
...Object.keys(this.rawAttributes),
|
||||||
|
...Object.keys(this.associations),
|
||||||
|
];
|
||||||
|
const attrs = _.pick(data, keys);
|
||||||
|
const options = _.omit(data, keys);
|
||||||
|
return { ...attrs, options };
|
||||||
|
}
|
||||||
|
|
||||||
|
static properties2children(properties = []) {
|
||||||
|
const children = [];
|
||||||
|
for (const [name, property] of Object.entries(properties)) {
|
||||||
|
if (property.properties) {
|
||||||
|
property.children = this.properties2children(property.properties);
|
||||||
|
}
|
||||||
|
children.push({
|
||||||
|
name,
|
||||||
|
...property,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
toProperty() {
|
||||||
|
const options = this.get('options') || {};
|
||||||
|
const data = _.omit(this.toJSON(), ['created_at', 'updated_at', 'options', 'parentKey']);
|
||||||
|
return { ...data, ...options };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProperties() {
|
||||||
|
const properties = {};
|
||||||
|
const children: UISchema[] = await this.getChildren();
|
||||||
|
for (const child of children) {
|
||||||
|
const property = child.toProperty();
|
||||||
|
const childProperties = await child.getProperties();
|
||||||
|
if (Object.keys(childProperties).length) {
|
||||||
|
property['properties'] = childProperties;
|
||||||
|
}
|
||||||
|
properties[child.name] = property;
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
19
packages/plugin-ui-schema/src/server.ts
Normal file
19
packages/plugin-ui-schema/src/server.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { Application } from '@nocobase/server';
|
||||||
|
import { registerModels, Table } from '@nocobase/database';
|
||||||
|
import * as models from './models';
|
||||||
|
|
||||||
|
export default async function (this: Application, options = {}) {
|
||||||
|
const database = this.database;
|
||||||
|
registerModels(models);
|
||||||
|
|
||||||
|
database.import({
|
||||||
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
database.getModel('ui_schemas').beforeCreate((model) => {
|
||||||
|
if (!model.get('name')) {
|
||||||
|
model.set('name', model.get('key'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
9
packages/plugin-ui-schema/src/utils.ts
Normal file
9
packages/plugin-ui-schema/src/utils.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import deepmerge from 'deepmerge';
|
||||||
|
|
||||||
|
const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray
|
||||||
|
|
||||||
|
export function merge(obj1: any, obj2: any) {
|
||||||
|
return deepmerge(obj1, obj2, {
|
||||||
|
arrayMerge: overwriteMerge,
|
||||||
|
});
|
||||||
|
}
|
13
uid.js
Normal file
13
uid.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
let IDX = 36,
|
||||||
|
HEX = ''
|
||||||
|
while (IDX--) HEX += IDX.toString(36)
|
||||||
|
|
||||||
|
function uid(len = 11) {
|
||||||
|
let str = '',
|
||||||
|
num = len || 11
|
||||||
|
while (num--) str += HEX[(Math.random() * 36) | 0]
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(uid());
|
161
yarn.lock
161
yarn.lock
@ -4136,7 +4136,7 @@
|
|||||||
"@umijs/deps" "3.4.25"
|
"@umijs/deps" "3.4.25"
|
||||||
"@umijs/utils" "3.4.25"
|
"@umijs/utils" "3.4.25"
|
||||||
|
|
||||||
"@umijs/test@^3.0.7", "@umijs/test@^3.4.15", "@umijs/test@^3.4.25":
|
"@umijs/test@^3.0.7", "@umijs/test@^3.4.15":
|
||||||
version "3.4.25"
|
version "3.4.25"
|
||||||
resolved "https://registry.npmjs.org/@umijs/test/-/test-3.4.25.tgz#9df95dc25cecaee9f45cdb62a227634defb5ae87"
|
resolved "https://registry.npmjs.org/@umijs/test/-/test-3.4.25.tgz#9df95dc25cecaee9f45cdb62a227634defb5ae87"
|
||||||
integrity sha512-VfaQyvF3vkHkHROS3LYQjRvx3dwk1Lv56gfgDIHGCX2n2ARSGyaFTbXsv/QafrKENDTDp3FTcTIUG6oY6mHH2g==
|
integrity sha512-VfaQyvF3vkHkHROS3LYQjRvx3dwk1Lv56gfgDIHGCX2n2ARSGyaFTbXsv/QafrKENDTDp3FTcTIUG6oY6mHH2g==
|
||||||
@ -4319,14 +4319,6 @@ agentkeepalive@^3.4.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
humanize-ms "^1.2.1"
|
humanize-ms "^1.2.1"
|
||||||
|
|
||||||
aggregate-error@^3.0.0:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
|
|
||||||
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
|
|
||||||
dependencies:
|
|
||||||
clean-stack "^2.0.0"
|
|
||||||
indent-string "^4.0.0"
|
|
||||||
|
|
||||||
ahooks@^2.10.2:
|
ahooks@^2.10.2:
|
||||||
version "2.10.6"
|
version "2.10.6"
|
||||||
resolved "https://registry.npmjs.org/ahooks/-/ahooks-2.10.6.tgz#05fad82f77a308886c174a61b7e1232cb16516ac"
|
resolved "https://registry.npmjs.org/ahooks/-/ahooks-2.10.6.tgz#05fad82f77a308886c174a61b7e1232cb16516ac"
|
||||||
@ -4479,7 +4471,7 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
|
|||||||
resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||||
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
||||||
|
|
||||||
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
|
ansi-escapes@^4.2.1:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
|
||||||
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
|
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
|
||||||
@ -5874,11 +5866,6 @@ classnames@2.x, classnames@^2.2.0, classnames@^2.2.1, classnames@^2.2.3, classna
|
|||||||
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||||
|
|
||||||
clean-stack@^2.0.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
|
||||||
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
|
|
||||||
|
|
||||||
cli-boxes@^2.2.0:
|
cli-boxes@^2.2.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
|
resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
|
||||||
@ -5891,21 +5878,6 @@ cli-cursor@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
restore-cursor "^2.0.0"
|
restore-cursor "^2.0.0"
|
||||||
|
|
||||||
cli-cursor@^3.1.0:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
|
|
||||||
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
|
|
||||||
dependencies:
|
|
||||||
restore-cursor "^3.1.0"
|
|
||||||
|
|
||||||
cli-truncate@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
|
||||||
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
|
||||||
dependencies:
|
|
||||||
slice-ansi "^3.0.0"
|
|
||||||
string-width "^4.2.0"
|
|
||||||
|
|
||||||
cli-width@^2.0.0:
|
cli-width@^2.0.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
||||||
@ -6128,11 +6100,6 @@ commander@^2.19.0, commander@^2.20.0:
|
|||||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
|
|
||||||
commander@^6.2.0:
|
|
||||||
version "6.2.1"
|
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
|
||||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
|
||||||
|
|
||||||
commondir@^1.0.1:
|
commondir@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||||
@ -6918,7 +6885,7 @@ debug@3.X, debug@^3.1.0, debug@^3.2.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1:
|
debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||||
@ -7513,7 +7480,7 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.1:
|
|||||||
memory-fs "^0.5.0"
|
memory-fs "^0.5.0"
|
||||||
tapable "^1.0.0"
|
tapable "^1.0.0"
|
||||||
|
|
||||||
enquirer@^2.3.5, enquirer@^2.3.6:
|
enquirer@^2.3.5:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||||
@ -8056,7 +8023,7 @@ execa@^1.0.0:
|
|||||||
signal-exit "^3.0.0"
|
signal-exit "^3.0.0"
|
||||||
strip-eof "^1.0.0"
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
execa@^4.0.0, execa@^4.1.0:
|
execa@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||||
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
||||||
@ -10224,11 +10191,6 @@ is-unc-path@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
unc-path-regex "^0.1.2"
|
unc-path-regex "^0.1.2"
|
||||||
|
|
||||||
is-unicode-supported@^0.1.0:
|
|
||||||
version "0.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
|
||||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
|
||||||
|
|
||||||
is-url@1.2.4:
|
is-url@1.2.4:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||||
@ -11660,40 +11622,6 @@ lines-and-columns@^1.1.6:
|
|||||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||||
|
|
||||||
lint-staged@^10.0.7:
|
|
||||||
version "10.5.4"
|
|
||||||
resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665"
|
|
||||||
integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==
|
|
||||||
dependencies:
|
|
||||||
chalk "^4.1.0"
|
|
||||||
cli-truncate "^2.1.0"
|
|
||||||
commander "^6.2.0"
|
|
||||||
cosmiconfig "^7.0.0"
|
|
||||||
debug "^4.2.0"
|
|
||||||
dedent "^0.7.0"
|
|
||||||
enquirer "^2.3.6"
|
|
||||||
execa "^4.1.0"
|
|
||||||
listr2 "^3.2.2"
|
|
||||||
log-symbols "^4.0.0"
|
|
||||||
micromatch "^4.0.2"
|
|
||||||
normalize-path "^3.0.0"
|
|
||||||
please-upgrade-node "^3.2.0"
|
|
||||||
string-argv "0.3.1"
|
|
||||||
stringify-object "^3.3.0"
|
|
||||||
|
|
||||||
listr2@^3.2.2:
|
|
||||||
version "3.10.0"
|
|
||||||
resolved "https://registry.npmjs.org/listr2/-/listr2-3.10.0.tgz#58105a53ed7fa1430d1b738c6055ef7bb006160f"
|
|
||||||
integrity sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==
|
|
||||||
dependencies:
|
|
||||||
cli-truncate "^2.1.0"
|
|
||||||
colorette "^1.2.2"
|
|
||||||
log-update "^4.0.0"
|
|
||||||
p-map "^4.0.0"
|
|
||||||
rxjs "^6.6.7"
|
|
||||||
through "^2.3.8"
|
|
||||||
wrap-ansi "^7.0.0"
|
|
||||||
|
|
||||||
load-json-file@^1.0.0:
|
load-json-file@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
||||||
@ -11897,24 +11825,6 @@ lodash@4.x, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.19,
|
|||||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
log-symbols@^4.0.0:
|
|
||||||
version "4.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
|
|
||||||
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
|
|
||||||
dependencies:
|
|
||||||
chalk "^4.1.0"
|
|
||||||
is-unicode-supported "^0.1.0"
|
|
||||||
|
|
||||||
log-update@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
|
|
||||||
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
|
|
||||||
dependencies:
|
|
||||||
ansi-escapes "^4.3.0"
|
|
||||||
cli-cursor "^3.1.0"
|
|
||||||
slice-ansi "^4.0.0"
|
|
||||||
wrap-ansi "^6.2.0"
|
|
||||||
|
|
||||||
long-timeout@0.1.1:
|
long-timeout@0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
|
resolved "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514"
|
||||||
@ -13467,13 +13377,6 @@ p-map@^2.1.0:
|
|||||||
resolved "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
resolved "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
||||||
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
|
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
|
||||||
|
|
||||||
p-map@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
|
||||||
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
|
|
||||||
dependencies:
|
|
||||||
aggregate-error "^3.0.0"
|
|
||||||
|
|
||||||
p-pipe@^1.2.0:
|
p-pipe@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
|
resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
|
||||||
@ -13965,13 +13868,6 @@ platform@^1.3.1:
|
|||||||
resolved "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
|
resolved "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7"
|
||||||
integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
|
integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==
|
||||||
|
|
||||||
please-upgrade-node@^3.2.0:
|
|
||||||
version "3.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
|
||||||
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
|
|
||||||
dependencies:
|
|
||||||
semver-compare "^1.0.0"
|
|
||||||
|
|
||||||
plugin-error@^0.1.2:
|
plugin-error@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
|
resolved "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace"
|
||||||
@ -16383,14 +16279,6 @@ restore-cursor@^2.0.0:
|
|||||||
onetime "^2.0.0"
|
onetime "^2.0.0"
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
restore-cursor@^3.1.0:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
|
||||||
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
|
|
||||||
dependencies:
|
|
||||||
onetime "^5.1.0"
|
|
||||||
signal-exit "^3.0.2"
|
|
||||||
|
|
||||||
ret@~0.1.10:
|
ret@~0.1.10:
|
||||||
version "0.1.15"
|
version "0.1.15"
|
||||||
resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||||
@ -16754,7 +16642,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
aproba "^1.1.1"
|
aproba "^1.1.1"
|
||||||
|
|
||||||
rxjs@^6.4.0, rxjs@^6.6.7:
|
rxjs@^6.4.0:
|
||||||
version "6.6.7"
|
version "6.6.7"
|
||||||
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
||||||
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
|
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
|
||||||
@ -16876,11 +16764,6 @@ select@^1.1.2:
|
|||||||
resolved "https://registry.npmjs.org/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
|
resolved "https://registry.npmjs.org/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
|
||||||
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
|
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
|
||||||
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
|
|
||||||
|
|
||||||
semver-diff@^2.0.0:
|
semver-diff@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||||
@ -17095,15 +16978,6 @@ slash@^3.0.0:
|
|||||||
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||||
|
|
||||||
slice-ansi@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
|
|
||||||
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
astral-regex "^2.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
|
|
||||||
slice-ansi@^4.0.0:
|
slice-ansi@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
|
||||||
@ -17459,11 +17333,6 @@ strict-uri-encode@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
|
||||||
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
|
||||||
|
|
||||||
string-argv@0.3.1:
|
|
||||||
version "0.3.1"
|
|
||||||
resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
|
|
||||||
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
|
|
||||||
|
|
||||||
string-convert@^0.2.0:
|
string-convert@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
resolved "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
||||||
@ -18021,7 +17890,7 @@ through2@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "3"
|
readable-stream "3"
|
||||||
|
|
||||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3:
|
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||||
@ -18398,11 +18267,6 @@ typescript@^3.7.2:
|
|||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
|
||||||
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
|
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
|
||||||
|
|
||||||
typescript@^4.1.2:
|
|
||||||
version "4.3.4"
|
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc"
|
|
||||||
integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==
|
|
||||||
|
|
||||||
ua-parser-js@^0.7.18:
|
ua-parser-js@^0.7.18:
|
||||||
version "0.7.28"
|
version "0.7.28"
|
||||||
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
||||||
@ -18513,7 +18377,7 @@ umi-utils@1.7.2:
|
|||||||
prettier "1.15.3"
|
prettier "1.15.3"
|
||||||
slash2 "2.0.0"
|
slash2 "2.0.0"
|
||||||
|
|
||||||
umi@^3.0.0, umi@^3.4.25:
|
umi@^3.0.0:
|
||||||
version "3.4.25"
|
version "3.4.25"
|
||||||
resolved "https://registry.npmjs.org/umi/-/umi-3.4.25.tgz#e1d53378ef8fa89011a8f54e7c5ff6984875b2de"
|
resolved "https://registry.npmjs.org/umi/-/umi-3.4.25.tgz#e1d53378ef8fa89011a8f54e7c5ff6984875b2de"
|
||||||
integrity sha512-67GMhP40Tz2sbyAzB4at7IUjXEIlG4j3zADfRhepLRWYbhUa55ZHnWyvuws3/dtUfifpraz4ckVcJJESoJ7Xaw==
|
integrity sha512-67GMhP40Tz2sbyAzB4at7IUjXEIlG4j3zADfRhepLRWYbhUa55ZHnWyvuws3/dtUfifpraz4ckVcJJESoJ7Xaw==
|
||||||
@ -19284,15 +19148,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
Loading…
Reference in New Issue
Block a user