mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
fix: 数据源构造器 & Form & CRUD2 & Service相关问题修复 (#8002)
This commit is contained in:
parent
60bacb2cc2
commit
03bf01fc30
@ -5,5 +5,5 @@
|
||||
"packages/amis-ui",
|
||||
"packages/amis"
|
||||
],
|
||||
"version": "3.4.0"
|
||||
"version": "3.4.0-alpha.4"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-core",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"description": "amis-core",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -46,7 +46,7 @@
|
||||
"esm"
|
||||
],
|
||||
"dependencies": {
|
||||
"amis-formula": "^3.4.0",
|
||||
"amis-formula": "^3.4.0-alpha.4",
|
||||
"classnames": "2.3.2",
|
||||
"file-saver": "^2.0.2",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor-core",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.1-alpha.8",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
@ -36,6 +36,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
max-width: 140px;
|
||||
|
||||
& > span {
|
||||
max-width: 100%;
|
||||
@ -85,6 +86,7 @@
|
||||
height: #{px2rem(20px)};
|
||||
margin-right: #{px2rem(8px)};
|
||||
scale: 0.9;
|
||||
max-width: 80px;
|
||||
|
||||
&--cascading {
|
||||
color: #531dab;
|
||||
|
@ -8,5 +8,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
@import './control/_status';
|
||||
@import './control/_icon-button-group-control';
|
||||
@import './control/_flex-setting-control';
|
||||
@import './control/table-column-width-control.scss';
|
||||
@import './control/crud2-control';
|
||||
|
||||
/* 样式控件 */
|
||||
|
@ -230,6 +230,37 @@ export class EditorManager {
|
||||
// 自动加载预先注册的自定义组件
|
||||
autoPreRegisterEditorCustomPlugins();
|
||||
|
||||
/** 在顶层对外部注册的Plugin和builtInPlugins合并去重 */
|
||||
const externalPlugins = (config?.plugins || []).forEach(external => {
|
||||
if (
|
||||
Array.isArray(external) ||
|
||||
!external.priority ||
|
||||
!Number.isInteger(external.priority)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = builtInPlugins.findIndex(
|
||||
builtIn =>
|
||||
!Array.isArray(builtIn) &&
|
||||
!Array.isArray(external) &&
|
||||
builtIn.id === external.id &&
|
||||
builtIn?.prototype instanceof BasePlugin
|
||||
);
|
||||
|
||||
if (~idx) {
|
||||
const current = builtInPlugins[idx] as PluginClass;
|
||||
const currentPriority =
|
||||
current.priority && Number.isInteger(current.priority)
|
||||
? current.priority
|
||||
: 0;
|
||||
/** 同ID Plugin根据优先级决定是否替换掉Builtin中的Plugin */
|
||||
if (external.priority > currentPriority) {
|
||||
builtInPlugins.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.plugins = (config.disableBultinPlugin ? [] : builtInPlugins) // 页面设计器注册的插件列表
|
||||
.concat(this.normalizeScene(config?.plugins))
|
||||
.filter(p => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-editor",
|
||||
"version": "5.5.0",
|
||||
"version": "5.5.1-alpha.8",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
@ -277,7 +277,22 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
)}</pre>`
|
||||
)
|
||||
})
|
||||
: null
|
||||
: null,
|
||||
/** CRUD的快速编辑接口 */
|
||||
...(feat === 'List' && renderer === 'crud' && !inScaffold
|
||||
? [
|
||||
getSchemaTpl('apiControl', {
|
||||
...baseApiSchemaConfig,
|
||||
name: 'quickSaveApi',
|
||||
label: tipedLabel('快速保存', '快速编辑后用来批量保存的 API')
|
||||
}),
|
||||
getSchemaTpl('apiControl', {
|
||||
...baseApiSchemaConfig,
|
||||
name: 'quickSaveItemApi',
|
||||
label: tipedLabel('快速保存单条', '即时保存时使用的 API')
|
||||
})
|
||||
]
|
||||
: [])
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
@ -300,7 +315,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
label: renderLabel === false ? false : '字段',
|
||||
renderer,
|
||||
feat,
|
||||
options: {
|
||||
config: {
|
||||
showInputType:
|
||||
renderer === 'form' ||
|
||||
(renderer === 'crud' &&
|
||||
@ -374,7 +389,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
label: key,
|
||||
name: key,
|
||||
displayType: 'tpl',
|
||||
inputType: inputType,
|
||||
inputType,
|
||||
checked: true
|
||||
});
|
||||
});
|
||||
@ -455,6 +470,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
actions: [
|
||||
{
|
||||
actionType: 'search',
|
||||
groupType: 'component',
|
||||
componentId: componentId
|
||||
}
|
||||
]
|
||||
@ -529,20 +545,21 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
|
||||
buildBaseFormSchema(
|
||||
options: ApiDSBuilderOptions<DSRendererType>,
|
||||
schemaPatch?: GenericSchema
|
||||
schemaPatch?: GenericSchema,
|
||||
componentId?: string
|
||||
) {
|
||||
schemaPatch = schemaPatch || {};
|
||||
const {feat, renderer, scaffoldConfig} = options || {};
|
||||
|
||||
if (!feat) {
|
||||
return {...schemaPatch};
|
||||
return {...schemaPatch, ...(componentId ? {id: componentId} : {})};
|
||||
}
|
||||
|
||||
const fieldsKey = this.getFieldsKey(options);
|
||||
const apiKey = this.getApiKey(options);
|
||||
const fields: ScaffoldField[] = (scaffoldConfig as any)?.[fieldsKey] ?? [];
|
||||
const apiSchema = (scaffoldConfig as any)?.[apiKey];
|
||||
const id = generateNodeId();
|
||||
const id = componentId ?? generateNodeId();
|
||||
let schema: GenericSchema = {
|
||||
id,
|
||||
type: 'form',
|
||||
@ -580,7 +597,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
schema.static = true;
|
||||
}
|
||||
|
||||
return {...schema, ...schemaPatch};
|
||||
return {...schema, ...schemaPatch, id};
|
||||
}
|
||||
|
||||
async buildInsertSchema<T extends DSRendererType>(
|
||||
@ -591,10 +608,10 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
const {insertApi} = scaffoldConfig || {};
|
||||
|
||||
if (renderer === 'form') {
|
||||
return this.buildBaseFormSchema({...options});
|
||||
return this.buildBaseFormSchema({...options}, undefined, componentId);
|
||||
}
|
||||
|
||||
const formId = generateNodeId();
|
||||
const formId = componentId ?? generateNodeId();
|
||||
const formActions = [
|
||||
{
|
||||
type: 'button',
|
||||
@ -693,7 +710,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
const isForm = renderer === 'form';
|
||||
|
||||
if (isForm) {
|
||||
return this.buildBaseFormSchema(options);
|
||||
return this.buildBaseFormSchema(options, undefined, componentId);
|
||||
}
|
||||
|
||||
const {editApi, initApi} = scaffoldConfig || {};
|
||||
@ -750,7 +767,7 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
const isForm = renderer === 'form';
|
||||
|
||||
if (isForm) {
|
||||
return this.buildBaseFormSchema(options);
|
||||
return this.buildBaseFormSchema(options, undefined, componentId);
|
||||
}
|
||||
|
||||
const formId = generateNodeId();
|
||||
@ -816,15 +833,14 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
actions: [
|
||||
{
|
||||
actionType: 'ajax',
|
||||
args: {
|
||||
api: deleteApi,
|
||||
data: {
|
||||
'&': '$$'
|
||||
}
|
||||
api: deleteApi,
|
||||
data: {
|
||||
'&': '$$'
|
||||
}
|
||||
},
|
||||
{
|
||||
actionType: 'search',
|
||||
groupType: 'component',
|
||||
componentId: componentId
|
||||
}
|
||||
]
|
||||
@ -855,12 +871,11 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
actions: [
|
||||
{
|
||||
actionType: 'ajax',
|
||||
args: {
|
||||
api: bulkDeleteApi
|
||||
}
|
||||
api: bulkDeleteApi
|
||||
},
|
||||
{
|
||||
actionType: 'search',
|
||||
groupType: 'component',
|
||||
componentId: componentId
|
||||
}
|
||||
]
|
||||
@ -1265,18 +1280,22 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
const actions = get(host, 'onEvent.click.actions', []);
|
||||
const actionSchema = actions.find(
|
||||
(action: any) =>
|
||||
action?.actionType === 'ajax' && action?.args?.api != null
|
||||
action?.actionType === 'ajax' &&
|
||||
(action?.api != null || action?.args?.api != null)
|
||||
);
|
||||
bulkDeleteApi = get(actionSchema, 'args.api', '');
|
||||
bulkDeleteApi =
|
||||
get(actionSchema, 'api', '') || get(actionSchema, 'args.api', '');
|
||||
} else if (value === 'Delete') {
|
||||
feats.push('Delete');
|
||||
|
||||
const actions = get(host, 'onEvent.click.actions', []);
|
||||
const actionSchema = actions.find(
|
||||
(action: any) =>
|
||||
action?.actionType === 'ajax' && action?.args?.api != null
|
||||
action?.actionType === 'ajax' &&
|
||||
(action?.api != null || action?.args?.api != null)
|
||||
);
|
||||
deleteApi = get(actionSchema, 'args.api', '');
|
||||
deleteApi =
|
||||
get(actionSchema, 'api', '') || get(actionSchema, 'args.api', '');
|
||||
} else if (Array.isArray(value) && value.includes('SimpleQuery')) {
|
||||
feats.push('SimpleQuery');
|
||||
|
||||
@ -1384,13 +1403,14 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
const {feat, scaffoldConfig} = options;
|
||||
const {initApi, __pristineSchema} = scaffoldConfig || {};
|
||||
let formSchema: GenericSchema;
|
||||
const id = __pristineSchema?.id ?? generateNodeId();
|
||||
|
||||
if (feat === 'Insert') {
|
||||
formSchema = await this.buildInsertSchema<'form'>(options);
|
||||
formSchema = await this.buildInsertSchema<'form'>(options, id);
|
||||
} else if (feat === 'Edit') {
|
||||
formSchema = await this.buildEditSchema(options);
|
||||
formSchema = await this.buildEditSchema(options, id);
|
||||
} else {
|
||||
formSchema = await this.buildBulkEditSchema(options);
|
||||
formSchema = await this.buildBulkEditSchema(options, id);
|
||||
}
|
||||
|
||||
const baseSchema = {
|
||||
@ -1400,8 +1420,6 @@ export class ApiDSBuilder extends DSBuilder<
|
||||
};
|
||||
|
||||
if (__pristineSchema && isObject(__pristineSchema)) {
|
||||
const id = __pristineSchema.id ?? generateNodeId();
|
||||
|
||||
return {
|
||||
...__pristineSchema,
|
||||
...baseSchema,
|
||||
|
@ -51,12 +51,12 @@ export interface DSBuilderInterface<
|
||||
/** 是否为默认 */
|
||||
isDefault?: boolean;
|
||||
|
||||
/** 是否默认隐藏 */
|
||||
defaultHidden?: boolean;
|
||||
|
||||
/** 实例获取数据源的key */
|
||||
key: string;
|
||||
|
||||
/** 是否禁用 */
|
||||
disabledOn?: () => boolean;
|
||||
|
||||
/** 获取功能场景的value */
|
||||
getFeatValueByKey(feat: DSFeatureType): string;
|
||||
|
||||
@ -185,7 +185,7 @@ export abstract class DSBuilder<T extends DSBuilderBaseOptions>
|
||||
readonly order: number;
|
||||
/** 是否为默认 */
|
||||
readonly isDefault?: boolean;
|
||||
defaultHidden?: boolean;
|
||||
|
||||
features: DSFeatureType[];
|
||||
|
||||
constructor(readonly manager: EditorManager) {}
|
||||
|
@ -43,7 +43,9 @@ export class DSBuilderManager {
|
||||
}
|
||||
|
||||
getDefaultBuilderKey() {
|
||||
const collections = Array.from(this.builders.entries());
|
||||
const collections = Array.from(this.builders.entries()).filter(
|
||||
([_, builder]) => builder?.disabledOn?.() !== true
|
||||
);
|
||||
const [defaultKey, _] =
|
||||
collections.find(([_, builder]) => builder.isDefault === true) ??
|
||||
collections.sort((lhs, rhs) => {
|
||||
@ -55,17 +57,22 @@ export class DSBuilderManager {
|
||||
}
|
||||
|
||||
getDefaultBuilder() {
|
||||
const collections = Array.from(this.builders.entries()).filter(
|
||||
([_, builder]) => builder?.disabledOn?.() !== true
|
||||
);
|
||||
const [_, defaultBuilder] =
|
||||
Array.from(this.builders.entries()).find(
|
||||
([_, builder]) => builder.isDefault === true
|
||||
) ?? [];
|
||||
collections.find(([_, builder]) => builder.isDefault === true) ??
|
||||
collections.sort((lhs, rhs) => {
|
||||
return (lhs[1].order ?? 0) - (rhs[1].order ?? 0);
|
||||
})?.[0] ??
|
||||
[];
|
||||
|
||||
return defaultBuilder!;
|
||||
return defaultBuilder;
|
||||
}
|
||||
|
||||
getAvailableBuilders() {
|
||||
return Array.from(this.builders.entries())
|
||||
.filter(item => item[1]?.defaultHidden !== true)
|
||||
.filter(([_, builder]) => builder?.disabledOn?.() !== true)
|
||||
.sort((lhs, rhs) => {
|
||||
return (lhs[1].order ?? 0) - (rhs[1].order ?? 0);
|
||||
});
|
||||
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import isFunction from 'lodash/isFunction';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
@ -12,7 +13,7 @@ import uniqBy from 'lodash/uniqBy';
|
||||
import get from 'lodash/get';
|
||||
import uniq from 'lodash/uniq';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {toast, autobind, isObject} from 'amis';
|
||||
import {toast, autobind, isObject, Icon} from 'amis';
|
||||
import {
|
||||
BasePlugin,
|
||||
ScaffoldForm,
|
||||
@ -46,9 +47,8 @@ import type {CRUDScaffoldConfig} from '../../builder/type';
|
||||
/** 需要动态控制的属性 */
|
||||
export type CRUD2DynamicControls = Partial<
|
||||
Record<
|
||||
'columns' | 'toolbar' | 'filters',
|
||||
| Record<string, any>
|
||||
| ((context: BuildPanelEventContext) => Record<string, any>)
|
||||
'columns' | 'toolbar' | 'filters' | 'primaryField',
|
||||
(context: BuildPanelEventContext) => any
|
||||
>
|
||||
>;
|
||||
export class BaseCRUDPlugin extends BasePlugin {
|
||||
@ -214,7 +214,9 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
};
|
||||
}
|
||||
),
|
||||
getSchemaTpl('primaryField')
|
||||
getSchemaTpl('primaryField', {
|
||||
visibleOn: `!data.dsType || data.dsType !== '${ModelDSBuilderKey}'`
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -332,7 +334,8 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const fieldErrors = FieldSetting.validator(form.data[fieldsKey]);
|
||||
const fieldErrors = false;
|
||||
// FieldSetting.validator(form.data[fieldsKey]);
|
||||
|
||||
if (fieldErrors) {
|
||||
errors[fieldsKey] = fieldErrors;
|
||||
@ -441,7 +444,9 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
/** 工具栏配置 */
|
||||
toolbar: context => this.renderToolbarCollapse(context),
|
||||
/** 搜索栏 */
|
||||
filters: context => this.renderFiltersCollapse(context)
|
||||
filters: context => this.renderFiltersCollapse(context),
|
||||
/** 主键 */
|
||||
primaryField: context => getSchemaTpl('primaryField')
|
||||
};
|
||||
|
||||
/** 需要动态控制的控件 */
|
||||
@ -494,7 +499,10 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
/** 其他类别 */
|
||||
this.renderOthersCollapse(context),
|
||||
/** 状态类别 */
|
||||
getSchemaTpl('status', {readonly: false})
|
||||
{
|
||||
title: '状态',
|
||||
body: [getSchemaTpl('hidden'), getSchemaTpl('visible')]
|
||||
}
|
||||
].filter(Boolean)
|
||||
)
|
||||
]
|
||||
@ -503,6 +511,9 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
|
||||
/** 基础配置 */
|
||||
renderBasicPropsCollapse(context: BuildPanelEventContext) {
|
||||
/** 动态加载的配置集合 */
|
||||
const dc = this.dynamicControls;
|
||||
|
||||
return {
|
||||
title: '基本',
|
||||
order: 1,
|
||||
@ -552,7 +563,7 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
};
|
||||
}),
|
||||
/** 主键配置,TODO:支持联合主键 */
|
||||
getSchemaTpl('primaryField'),
|
||||
dc?.primaryField?.(context),
|
||||
{
|
||||
name: 'placeholder',
|
||||
pipeIn: defaultValue('暂无数据'),
|
||||
@ -653,12 +664,15 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
|
||||
/** 分页类别 */
|
||||
renderPaginationCollapse(context: BuildPanelEventContext) {
|
||||
const isPagination = 'data.loadType === "pagination"';
|
||||
const isInfinity = 'data.loadType === "more"';
|
||||
|
||||
return {
|
||||
order: 30,
|
||||
title: '分页设置',
|
||||
body: [
|
||||
{
|
||||
label: '更多模式',
|
||||
label: '分页模式',
|
||||
type: 'select',
|
||||
name: 'loadType',
|
||||
options: [
|
||||
@ -725,20 +739,55 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
getSchemaTpl('switch', {
|
||||
name: 'loadDataOnce',
|
||||
label: '前端分页',
|
||||
visibleOn: 'data.loadType === "pagination"'
|
||||
visibleOn: isPagination
|
||||
}),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'loadDataOnceFetchOnFilter',
|
||||
label: tipedLabel(
|
||||
'过滤时刷新',
|
||||
'在开启前端分页时,表头过滤后是否重新请求初始化 API'
|
||||
),
|
||||
visibleOn: isPagination + ' && data.loadDataOnce'
|
||||
}),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'keepItemSelectionOnPageChange',
|
||||
label: tipedLabel(
|
||||
'保留选择项',
|
||||
'默认切换页面、搜索后,用户选择项会被清空,开启此功能后会保留用户选择,可以实现跨页面批量操作。'
|
||||
),
|
||||
pipeIn: defaultValue(false),
|
||||
visibleOn: isPagination
|
||||
}),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'autoJumpToTopOnPagerChange',
|
||||
label: tipedLabel('翻页后回到顶部', '当切分页的时候,是否自动跳顶部'),
|
||||
pipeIn: defaultValue(true),
|
||||
visibleOn: isPagination
|
||||
}),
|
||||
{
|
||||
name: 'perPage',
|
||||
type: 'input-number',
|
||||
label: tipedLabel(
|
||||
'每页数量',
|
||||
'无限加载时,根据此项设置其每页加载数量,留空即不限制'
|
||||
),
|
||||
clearValueOnEmpty: true,
|
||||
clearable: true,
|
||||
pipeIn: defaultValue(10),
|
||||
visibleOn: isInfinity
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
label: '点击编辑分页组件',
|
||||
block: true,
|
||||
className: 'm-b',
|
||||
className: 'mb-1',
|
||||
level: 'enhance',
|
||||
// icon: 'fa fa-plus',
|
||||
visibleOn: 'data.loadType === "pagination"',
|
||||
onClick: () => {
|
||||
const findPage: any = findSchema(
|
||||
context?.schema ?? context?.node?.schema ?? {},
|
||||
item => item.type === 'pagination',
|
||||
context?.node?.schema ?? {},
|
||||
item =>
|
||||
item.type === 'pagination' || item.behavior === 'Pagination',
|
||||
'headerToolbar',
|
||||
'footerToolbar'
|
||||
);
|
||||
@ -749,26 +798,7 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
}
|
||||
this.manager.setActiveId(findPage.$$id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'perPage',
|
||||
type: 'input-number',
|
||||
label: '每页数量',
|
||||
visibleOn: 'data.loadType === "more"'
|
||||
},
|
||||
getSchemaTpl('switch', {
|
||||
name: 'keepItemSelectionOnPageChange',
|
||||
label: tipedLabel(
|
||||
'保留选择项',
|
||||
'默认切换页面、搜索后,用户选择项会被清空,开启此功能后会保留用户选择,可以实现跨页面批量操作。'
|
||||
),
|
||||
visibleOn: 'data.loadType === "pagination"'
|
||||
}),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'autoJumpToTopOnPagerChange',
|
||||
label: tipedLabel('翻页后回到顶部', '当切分页的时候,是否自动跳顶部'),
|
||||
visibleOn: 'data.loadType === "pagination"'
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
@ -779,27 +809,47 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
order: 25,
|
||||
title: '其他',
|
||||
body: [
|
||||
getSchemaTpl('interval', {
|
||||
formItems: [
|
||||
getSchemaTpl('switch', {
|
||||
name: 'silentPolling',
|
||||
label: '静默拉取',
|
||||
pipeIn: defaultValue(false)
|
||||
})
|
||||
],
|
||||
intervalConfig: {
|
||||
control: {
|
||||
type: 'input-number',
|
||||
name: 'interval'
|
||||
}
|
||||
},
|
||||
switchMoreConfig: {
|
||||
isChecked: (e: any) => {
|
||||
return !!get(e.data, 'interval');
|
||||
},
|
||||
autoFocus: false,
|
||||
trueValue: 10000
|
||||
{
|
||||
type: 'ae-switch-more',
|
||||
mode: 'normal',
|
||||
formType: 'extend',
|
||||
visibleOn: 'data.api',
|
||||
label: tipedLabel(
|
||||
'接口轮询',
|
||||
'开启初始化接口轮询,开启后会按照设定的时间间隔轮询调用接口'
|
||||
),
|
||||
autoFocus: false,
|
||||
form: {
|
||||
body: [
|
||||
{
|
||||
type: 'input-number',
|
||||
name: 'interval',
|
||||
label: tipedLabel('轮询间隔', '定时刷新间隔,单位 ms'),
|
||||
step: 10,
|
||||
min: 1000
|
||||
},
|
||||
getSchemaTpl('tplFormulaControl', {
|
||||
name: 'stopAutoRefreshWhen',
|
||||
label: tipedLabel(
|
||||
'停止条件',
|
||||
'定时刷新停止表达式,条件满足后则停止定时刷新,否则会持续轮询调用初始化接口。'
|
||||
),
|
||||
visibleOn: '!!data.interval'
|
||||
}),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'stopAutoRefreshWhenModalIsOpen',
|
||||
label: tipedLabel(
|
||||
'模态窗口期间停止',
|
||||
'当页面中存在弹窗时停止接口轮询,避免中断操作'
|
||||
)
|
||||
})
|
||||
]
|
||||
}
|
||||
},
|
||||
getSchemaTpl('switch', {
|
||||
name: 'silentPolling',
|
||||
label: tipedLabel('静默拉取', '刷新时是否隐藏加载动画'),
|
||||
pipeIn: defaultValue(false)
|
||||
})
|
||||
]
|
||||
};
|
||||
@ -821,12 +871,12 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'headerToolbarClassName',
|
||||
label: '顶部外层'
|
||||
label: '顶部工具栏'
|
||||
}),
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'footerToolbarClassName',
|
||||
label: '底部外层'
|
||||
label: '底部工具栏'
|
||||
})
|
||||
]
|
||||
})
|
||||
@ -848,6 +898,30 @@ export class BaseCRUDPlugin extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
/** 重新构建 API */
|
||||
panelFormPipeOut = async (schema: any) => {
|
||||
const entity = schema?.api?.entity;
|
||||
|
||||
if (!entity || schema?.dsType !== ModelDSBuilderKey) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const builder = this.dsManager.getBuilderBySchema(schema);
|
||||
|
||||
try {
|
||||
const updatedSchema = await builder.buildApiSchema({
|
||||
schema,
|
||||
renderer: 'crud',
|
||||
sourceKey: 'api'
|
||||
});
|
||||
return updatedSchema;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
emptyContainer = (align?: 'left' | 'right', body: any[] = []) => ({
|
||||
type: 'container',
|
||||
body,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {setVariable} from 'amis-core';
|
||||
import {setVariable, someTree} from 'amis-core';
|
||||
import {
|
||||
BaseEventContext,
|
||||
BasePlugin,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import cx from 'classnames';
|
||||
import flatten from 'lodash/flatten';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {isObject, someTree} from 'amis-core';
|
||||
import {isObject} from 'amis-core';
|
||||
import {
|
||||
BasePlugin,
|
||||
tipedLabel,
|
||||
@ -13,8 +13,6 @@ import {
|
||||
defaultValue,
|
||||
getSchemaTpl,
|
||||
jsonToJsonSchema,
|
||||
BuildPanelEventContext,
|
||||
BasicPanelItem,
|
||||
RendererPluginAction,
|
||||
RendererPluginEvent,
|
||||
EditorNodeType,
|
||||
@ -34,12 +32,17 @@ import {getEventControlConfig} from '../../renderer/event-control/helper';
|
||||
import {FieldSetting} from '../../renderer/FieldSetting';
|
||||
|
||||
import type {FormSchema} from 'amis/lib/Schema';
|
||||
import type {IFormStore, IFormItemStore, RendererConfig, Schema} from 'amis-core';
|
||||
import type {
|
||||
IFormStore,
|
||||
IFormItemStore,
|
||||
Schema,
|
||||
RendererConfig
|
||||
} from 'amis-core';
|
||||
import type {FormScaffoldConfig} from '../../builder';
|
||||
|
||||
export type FormPluginFeat = Extract<
|
||||
DSFeatureType,
|
||||
'Insert' | 'Edit' | 'BulkEdit'
|
||||
'Insert' | 'Edit' | 'BulkEdit' | 'View'
|
||||
>;
|
||||
|
||||
export interface ExtendFormSchema extends FormSchema {
|
||||
@ -70,7 +73,7 @@ export class FormPlugin extends BasePlugin {
|
||||
|
||||
$schema = '/schemas/FormSchema.json';
|
||||
|
||||
tags = ['功能', '数据容器'];
|
||||
tags = ['数据容器'];
|
||||
|
||||
order = -900;
|
||||
|
||||
@ -365,7 +368,7 @@ export class FormPlugin extends BasePlugin {
|
||||
}> = [
|
||||
{label: '新增', value: DSFeatureEnum.Insert},
|
||||
{label: '编辑', value: DSFeatureEnum.Edit},
|
||||
{label: '批量编辑', value: DSFeatureEnum.BulkEdit},
|
||||
{label: '批量编辑', value: DSFeatureEnum.BulkEdit, disabled: true},
|
||||
{label: '查看', value: DSFeatureEnum.View, disabled: true}
|
||||
];
|
||||
|
||||
@ -380,6 +383,8 @@ export class FormPlugin extends BasePlugin {
|
||||
|
||||
/** 表单脚手架 */
|
||||
get scaffoldForm(): ScaffoldForm {
|
||||
const features = this.Features.filter(f => !f.disabled);
|
||||
|
||||
return {
|
||||
title: '表单创建向导',
|
||||
mode: {
|
||||
@ -396,7 +401,7 @@ export class FormPlugin extends BasePlugin {
|
||||
name: 'feat',
|
||||
label: '使用场景',
|
||||
value: DSFeatureEnum.Insert,
|
||||
options: this.Features,
|
||||
options: features,
|
||||
onChange: (
|
||||
value: FormPluginFeat,
|
||||
oldValue: FormPluginFeat,
|
||||
@ -462,7 +467,7 @@ export class FormPlugin extends BasePlugin {
|
||||
}),
|
||||
/** 数据源相关配置 */
|
||||
...flatten(
|
||||
this.Features.map(feat =>
|
||||
features.map(feat =>
|
||||
this.dsManager.buildCollectionFromBuilders(
|
||||
(builder, builderKey) => {
|
||||
return {
|
||||
@ -944,6 +949,24 @@ export class FormPlugin extends BasePlugin {
|
||||
defaultValue: 'normal'
|
||||
}),
|
||||
getSchemaTpl('horizontal'),
|
||||
{
|
||||
name: 'labelAlign',
|
||||
label: '标签对齐方式',
|
||||
type: 'button-group-select',
|
||||
size: 'sm',
|
||||
visibleOn: "${mode === 'horizontal'}",
|
||||
pipeIn: defaultValue('right', false),
|
||||
options: [
|
||||
{
|
||||
label: '左对齐',
|
||||
value: 'left'
|
||||
},
|
||||
{
|
||||
label: '右对齐',
|
||||
value: 'right'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '列数',
|
||||
name: 'columnCount',
|
||||
@ -1033,6 +1056,31 @@ export class FormPlugin extends BasePlugin {
|
||||
];
|
||||
};
|
||||
|
||||
/** 重新构建 API */
|
||||
panelFormPipeOut = async (schema: any) => {
|
||||
const entity = schema?.api?.entity;
|
||||
|
||||
if (!entity || schema?.dsType !== ModelDSBuilderKey) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const builder = this.dsManager.getBuilderBySchema(schema);
|
||||
|
||||
try {
|
||||
const updatedSchema = await builder.buildApiSchema({
|
||||
schema,
|
||||
renderer: 'form',
|
||||
sourceKey: 'api',
|
||||
feat: schema.feat ?? 'Insert'
|
||||
});
|
||||
return updatedSchema;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
afterUpdate(event: PluginEvent<ChangeEventContext>) {
|
||||
const context = event.context;
|
||||
|
||||
|
@ -1064,12 +1064,14 @@ export class TableControlPlugin extends BasePlugin {
|
||||
filterProps(props: any) {
|
||||
const arr = resolveArrayDatasource(props);
|
||||
|
||||
/** 可 */
|
||||
if (!Array.isArray(arr) || !arr.length) {
|
||||
const mockedData: any = {};
|
||||
|
||||
if (Array.isArray(props.columns)) {
|
||||
props.columns.forEach((column: any) => {
|
||||
if (column.name) {
|
||||
/** 可编辑状态下不写入 Mock 数据,避免误导用户 */
|
||||
if (column.name && !props.editable) {
|
||||
setVariable(mockedData, column.name, mockValue(column));
|
||||
}
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
getSchemaTpl,
|
||||
registerEditorPlugin
|
||||
} from 'amis-editor-core';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {getEventControlConfig} from '../renderer/event-control/helper';
|
||||
|
||||
export class PaginationPlugin extends BasePlugin {
|
||||
@ -131,15 +132,29 @@ export class PaginationPlugin extends BasePlugin {
|
||||
}
|
||||
],
|
||||
pipeIn: (value: any) => {
|
||||
if (!value) {
|
||||
value = this.lastLayoutSetting;
|
||||
} else if (typeof value === 'string') {
|
||||
if (typeof value === 'string') {
|
||||
value = (value as string).split(',');
|
||||
} else if (!value || !Array.isArray(value)) {
|
||||
value = this.lastLayoutSetting;
|
||||
}
|
||||
return this.layoutOptions.map(v => ({
|
||||
...v,
|
||||
checked: value.includes(v.value)
|
||||
}));
|
||||
|
||||
return sortBy(
|
||||
this.layoutOptions.map(op => ({
|
||||
...op,
|
||||
checked: value.includes(op.value)
|
||||
})),
|
||||
[
|
||||
item => {
|
||||
const idx = value.findIndex(v => v === item.value);
|
||||
return ~idx ? idx : Infinity;
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// return this.layoutOptions.map(v => ({
|
||||
// ...v,
|
||||
// checked: value.includes(v.value)
|
||||
// }));
|
||||
},
|
||||
pipeOut: (value: any[]) => {
|
||||
this.lastLayoutSetting = value
|
||||
@ -191,7 +206,7 @@ export class PaginationPlugin extends BasePlugin {
|
||||
}),
|
||||
{
|
||||
name: 'perPage',
|
||||
type: 'input-text',
|
||||
type: 'input-number',
|
||||
label: '默认每页条数',
|
||||
visibleOn:
|
||||
'(!data.mode || data.mode === "normal") && data.layout?.includes("perPage")'
|
||||
@ -212,7 +227,11 @@ export class PaginationPlugin extends BasePlugin {
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
body: [getSchemaTpl('disabled')]
|
||||
body: [
|
||||
getSchemaTpl('disabled'),
|
||||
getSchemaTpl('hidden'),
|
||||
getSchemaTpl('visible')
|
||||
]
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -328,6 +328,29 @@ export class ServicePlugin extends BasePlugin {
|
||||
]);
|
||||
};
|
||||
|
||||
panelFormPipeOut = async (schema: any) => {
|
||||
const entity = schema?.api?.entity;
|
||||
|
||||
if (!entity || schema?.dsType !== ModelDSBuilderKey) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const builder = this.dsManager.getBuilderBySchema(schema);
|
||||
|
||||
try {
|
||||
const updatedSchema = await builder.buildApiSchema({
|
||||
schema,
|
||||
renderer: 'service',
|
||||
sourceKey: 'api'
|
||||
});
|
||||
return updatedSchema;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
async buildDataSchemas(
|
||||
node: EditorNodeType,
|
||||
region?: EditorNodeType,
|
||||
@ -358,29 +381,6 @@ export class ServicePlugin extends BasePlugin {
|
||||
return jsonschema;
|
||||
}
|
||||
|
||||
panelFormPipeOut = async (schema: any) => {
|
||||
const entity = schema?.api?.entity;
|
||||
|
||||
if (!entity || schema?.dsType !== ModelDSBuilderKey) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const builder = this.dsManager.getBuilderBySchema(schema);
|
||||
|
||||
try {
|
||||
const updatedSchema = await builder.buildApiSchema({
|
||||
schema,
|
||||
renderer: 'service',
|
||||
sourceKey: 'api'
|
||||
});
|
||||
return updatedSchema;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return schema;
|
||||
};
|
||||
|
||||
rendererBeforeDispatchEvent(node: EditorNodeType, e: any, data: any) {
|
||||
if (e === 'fetchInited') {
|
||||
const scope = this.manager.dataSchema.getScope(`${node.id}-${node.type}`);
|
||||
|
@ -297,6 +297,7 @@ export type Table2DynamicControls = Partial<
|
||||
| 'quickSaveItemApi'
|
||||
| 'draggable'
|
||||
| 'itemDraggableOn'
|
||||
| 'saveOrderApi'
|
||||
| 'columnTogglable',
|
||||
(context: BaseEventContext) => any
|
||||
>
|
||||
@ -654,48 +655,70 @@ export class Table2Plugin extends BasePlugin {
|
||||
}
|
||||
|
||||
protected _dynamicControls: Table2DynamicControls = {
|
||||
primaryField: () => {
|
||||
return getSchemaTpl('primaryField');
|
||||
primaryField: context => {
|
||||
return getSchemaTpl('primaryField', {
|
||||
/** CRUD下,该项配置提升到CRUD中 */
|
||||
hiddenOn: `data.type && (data.type === "crud" || data.type === "crud2")`
|
||||
});
|
||||
},
|
||||
quickSaveApi: () => {
|
||||
quickSaveApi: context => {
|
||||
return getSchemaTpl('apiControl', {
|
||||
label: '快速保存',
|
||||
name: 'quickSaveApi',
|
||||
renderLabel: true
|
||||
renderLabel: false,
|
||||
label: {
|
||||
type: 'tpl',
|
||||
tpl: '快速保存',
|
||||
className: 'flex items-end'
|
||||
}
|
||||
});
|
||||
},
|
||||
quickSaveItemApi: () => {
|
||||
quickSaveItemApi: context => {
|
||||
return getSchemaTpl('apiControl', {
|
||||
label: '快速保存单条',
|
||||
name: 'quickSaveItemApi',
|
||||
renderLabel: true
|
||||
renderLabel: false,
|
||||
label: {
|
||||
type: 'tpl',
|
||||
tpl: '快速保存单条',
|
||||
className: 'flex items-end'
|
||||
}
|
||||
});
|
||||
},
|
||||
rowSelectionKeyField: () => {
|
||||
rowSelectionKeyField: context => {
|
||||
return {
|
||||
type: 'input-text',
|
||||
name: 'rowSelection.keyField',
|
||||
label: '数据源key'
|
||||
};
|
||||
},
|
||||
expandableKeyField: () => {
|
||||
expandableKeyField: context => {
|
||||
return {
|
||||
type: 'input-text',
|
||||
name: 'rowSelection.keyField',
|
||||
label: '数据源key'
|
||||
};
|
||||
},
|
||||
draggable: () =>
|
||||
draggable: context =>
|
||||
getSchemaTpl('switch', {
|
||||
name: 'draggable',
|
||||
label: '可拖拽'
|
||||
}),
|
||||
itemDraggableOn: () =>
|
||||
itemDraggableOn: context =>
|
||||
getSchemaTpl('formulaControl', {
|
||||
label: '可拖拽条件',
|
||||
name: 'itemDraggableOn'
|
||||
}),
|
||||
columnTogglable: () => false
|
||||
saveOrderApi: context => {
|
||||
return getSchemaTpl('apiControl', {
|
||||
name: 'saveOrderApi',
|
||||
renderLabel: false,
|
||||
label: {
|
||||
type: 'tpl',
|
||||
tpl: '保存排序',
|
||||
className: 'flex items-end'
|
||||
}
|
||||
});
|
||||
},
|
||||
columnTogglable: context => false
|
||||
};
|
||||
|
||||
/** 需要动态控制的控件 */
|
||||
@ -713,9 +736,12 @@ export class Table2Plugin extends BasePlugin {
|
||||
this._dynamicControls = {...this._dynamicControls, ...controls};
|
||||
}
|
||||
|
||||
isCRUDContext(context: BaseEventContext) {
|
||||
return context.schema.type === 'crud2' || context.schema.type === 'crud';
|
||||
}
|
||||
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
const isCRUDBody =
|
||||
context.schema.type === 'crud2' || context.schema.type === 'crud';
|
||||
const isCRUDBody = this.isCRUDContext(context);
|
||||
const dc = this.dynamicControls;
|
||||
|
||||
return getSchemaTpl('tabs', [
|
||||
@ -734,8 +760,8 @@ export class Table2Plugin extends BasePlugin {
|
||||
pipeIn: defaultValue('${items}')
|
||||
}),
|
||||
dc?.primaryField?.(context),
|
||||
dc?.quickSaveApi?.(context),
|
||||
dc?.quickSaveItemApi?.(context),
|
||||
isCRUDBody ? null : dc?.quickSaveApi?.(context),
|
||||
isCRUDBody ? null : dc?.quickSaveItemApi?.(context),
|
||||
getSchemaTpl('switch', {
|
||||
name: 'title',
|
||||
label: '显示标题',
|
||||
@ -799,13 +825,14 @@ export class Table2Plugin extends BasePlugin {
|
||||
}),
|
||||
getSchemaTpl('tablePlaceholder', {
|
||||
hidden: isCRUDBody
|
||||
}),
|
||||
{
|
||||
type: 'input-number',
|
||||
name: 'combineNum',
|
||||
label: '合并单元格'
|
||||
}
|
||||
]
|
||||
})
|
||||
// TODD: 组件功能没有支持,暂时隐藏
|
||||
// {
|
||||
// type: 'input-number',
|
||||
// name: 'combineNum',
|
||||
// label: '合并单元格'
|
||||
// }
|
||||
].filter(Boolean)
|
||||
},
|
||||
{
|
||||
title: '列设置',
|
||||
@ -1070,6 +1097,7 @@ export class Table2Plugin extends BasePlugin {
|
||||
},
|
||||
dc?.draggable?.(context),
|
||||
dc?.itemDraggableOn?.(context),
|
||||
dc?.saveOrderApi?.(context),
|
||||
{
|
||||
name: 'showBadge',
|
||||
label: '行角标',
|
||||
|
@ -32,7 +32,7 @@ interface FieldSettingProps extends FormControlProps {
|
||||
/** 脚手架渲染类型 */
|
||||
renderer?: string;
|
||||
feat: DSFeatureType;
|
||||
options: {
|
||||
config: {
|
||||
showInputType?: boolean;
|
||||
showDisplayType?: boolean;
|
||||
};
|
||||
@ -50,7 +50,7 @@ export class FieldSetting extends React.Component<
|
||||
{loading: boolean}
|
||||
> {
|
||||
static defaultProps = {
|
||||
options: {
|
||||
config: {
|
||||
showInputType: true,
|
||||
showDisplayType: true
|
||||
}
|
||||
@ -308,11 +308,11 @@ export class FieldSetting extends React.Component<
|
||||
defaultValue: formDefaultValue,
|
||||
env,
|
||||
renderer,
|
||||
options,
|
||||
config,
|
||||
data: ctx,
|
||||
feat
|
||||
} = this.props;
|
||||
const {showDisplayType, showInputType} = options || {};
|
||||
const {showDisplayType, showInputType} = config || {};
|
||||
const isForm = renderer === 'form';
|
||||
const defaultValue = Array.isArray(formDefaultValue)
|
||||
? {items: formDefaultValue}
|
||||
|
@ -168,11 +168,15 @@ export default class TableColumnWidthControl extends React.Component<
|
||||
control: {
|
||||
type: 'input-number',
|
||||
min: 0,
|
||||
value,
|
||||
onChange: (val: number) => this.handleChange('fixed', val)
|
||||
value
|
||||
// onChange: (val: number) => this.handleChange('fixed', val)
|
||||
},
|
||||
unit: 'px'
|
||||
})
|
||||
unit: 'px',
|
||||
className: 'mt-3'
|
||||
}),
|
||||
{
|
||||
onChange: (val: number) => this.handleChange('fixed', val)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import Sortable from 'sortablejs';
|
||||
import get from 'lodash/get';
|
||||
import {FormItem, Button, Icon, toast, Tag, Spinner, autobind} from 'amis';
|
||||
import {TooltipWrapper} from 'amis-ui';
|
||||
import {JSONPipeIn} from 'amis-editor-core';
|
||||
import AddColumnModal from './AddColumnModal';
|
||||
|
||||
@ -208,14 +208,17 @@ export class CRUDColumnControl extends React.Component<
|
||||
|
||||
@autobind
|
||||
handleEdit(item: Option) {
|
||||
const {manager} = this.props;
|
||||
const {manager, node} = this.props;
|
||||
const columns = node?.schema?.columns ?? [];
|
||||
const idx = columns.findIndex(c => c.id === item.pristine.id);
|
||||
|
||||
if (!item.nodeId) {
|
||||
if (!~idx) {
|
||||
toast.warning(`未找到对应列「${item.label}」`);
|
||||
return;
|
||||
}
|
||||
|
||||
manager.setActiveId(item.nodeId);
|
||||
// FIXME: 理论上用item.nodeId就可以,不知道为何会重新构建一次导致store中node.id更新
|
||||
manager.setActiveId(columns[idx]?.$$id);
|
||||
}
|
||||
|
||||
/** 添加列 */
|
||||
@ -322,16 +325,33 @@ export class CRUDColumnControl extends React.Component<
|
||||
|
||||
@autobind
|
||||
renderOption(item: Option, index: number) {
|
||||
const {classnames: cx, data: ctx, render} = this.props;
|
||||
const {
|
||||
classnames: cx,
|
||||
data: ctx,
|
||||
render,
|
||||
popOverContainer,
|
||||
env
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={cx('ae-CRUDConfigControl-list-item', 'is-draggable')}
|
||||
>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
<TooltipWrapper
|
||||
tooltip={{
|
||||
content: item.label,
|
||||
tooltipTheme: 'dark',
|
||||
style: {fontSize: '12px'}
|
||||
}}
|
||||
container={popOverContainer || env?.getModalContainer?.()}
|
||||
trigger={['hover']}
|
||||
delay={150}
|
||||
>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-actions')}>
|
||||
{item.hidden || !item?.context?.isCascadingField ? null : (
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
Tag,
|
||||
autobind
|
||||
} from 'amis';
|
||||
import {TooltipWrapper} from 'amis-ui';
|
||||
import {DSFeatureEnum} from '../../builder/constants';
|
||||
import {traverseSchemaDeep} from '../../builder/utils';
|
||||
import {deepRemove} from '../../plugin/CRUD2/utils';
|
||||
@ -103,7 +104,7 @@ export class CRUDFiltersControl extends React.Component<
|
||||
? (option.label as any).tpl /** 处理 SchemaObject 的场景 */
|
||||
: option.name,
|
||||
value: option.name ?? (option as any).key,
|
||||
/** 使用$$id用于定位 */
|
||||
/** 使用id用于定位 */
|
||||
nodeId: option.$$id,
|
||||
pristine: option
|
||||
};
|
||||
@ -455,7 +456,7 @@ export class CRUDFiltersControl extends React.Component<
|
||||
value.filter(
|
||||
(item: any) =>
|
||||
item?.behavior !== DSFeatureEnum.AdvancedQuery &&
|
||||
item.type === 'condition-builder'
|
||||
item.type !== 'condition-builder'
|
||||
)
|
||||
];
|
||||
}
|
||||
@ -674,13 +675,24 @@ export class CRUDFiltersControl extends React.Component<
|
||||
|
||||
@autobind
|
||||
renderOption(item: Option, index: number) {
|
||||
const {classnames: cx, feat} = this.props;
|
||||
const {classnames: cx, feat, popOverContainer, env} = this.props;
|
||||
|
||||
return (
|
||||
<li key={index} className={cx('ae-CRUDConfigControl-list-item')}>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
<TooltipWrapper
|
||||
tooltip={{
|
||||
content: item.label,
|
||||
tooltipTheme: 'dark',
|
||||
style: {fontSize: '12px'}
|
||||
}}
|
||||
container={popOverContainer || env?.getModalContainer?.()}
|
||||
trigger={['hover']}
|
||||
delay={150}
|
||||
>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-actions')}>
|
||||
{item?.context?.isCascadingField ? (
|
||||
@ -688,8 +700,8 @@ export class CRUDFiltersControl extends React.Component<
|
||||
label={item?.context?.modelLabel}
|
||||
displayMode="normal"
|
||||
className={cx(
|
||||
'CRUDConfigControl-list-item-tag',
|
||||
'CRUDConfigControl-list-item-tag--cascading'
|
||||
'ae-CRUDConfigControl-list-item-tag',
|
||||
'ae-CRUDConfigControl-list-item-tag--cascading'
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -7,6 +7,7 @@ import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {FormItem, Button, Icon, toast, Spinner, autobind} from 'amis';
|
||||
import {TooltipWrapper} from 'amis-ui';
|
||||
import {findTreeAll} from 'amis-core';
|
||||
import {JSONPipeIn} from 'amis-editor-core';
|
||||
import {DSFeature, DSFeatureType, DSFeatureEnum} from '../../builder';
|
||||
@ -17,7 +18,9 @@ import type {EditorNodeType} from 'amis-editor-core';
|
||||
import type {ColumnSchema} from 'amis/lib/renderers/Table2';
|
||||
import type {DSBuilderInterface} from '../../builder';
|
||||
|
||||
type ActionValue = Extract<DSFeatureType, 'Insert' | 'BulkEdit' | 'BulkDelete'>;
|
||||
type ActionValue =
|
||||
| Extract<DSFeatureType, 'Insert' | 'BulkEdit' | 'BulkDelete'>
|
||||
| 'custom';
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
@ -81,7 +84,12 @@ export class CRUDToolbarControl extends React.Component<
|
||||
const store = manager.store;
|
||||
const node: EditorNodeType = store.getNodeById(nodeId);
|
||||
const actions = findTreeAll(node.children, item =>
|
||||
['Insert', 'BulkEdit', 'BulkDelete'].includes(item.schema.behavior)
|
||||
[
|
||||
DSFeatureEnum.Insert,
|
||||
DSFeatureEnum.BulkEdit,
|
||||
DSFeatureEnum.BulkDelete,
|
||||
'custom'
|
||||
].includes(item.schema.behavior)
|
||||
) as unknown as EditorNodeType[];
|
||||
|
||||
return actions;
|
||||
@ -98,7 +106,7 @@ export class CRUDToolbarControl extends React.Component<
|
||||
const behavior = schema.behavior as ActionValue;
|
||||
|
||||
return {
|
||||
label: DSFeature[behavior].label,
|
||||
label: this.getOptionLabel(schema, behavior),
|
||||
value: behavior,
|
||||
nodeId: schema.$$id,
|
||||
node: node,
|
||||
@ -109,6 +117,10 @@ export class CRUDToolbarControl extends React.Component<
|
||||
this.setState({options});
|
||||
}
|
||||
|
||||
getOptionLabel(schema: any, behavior: ActionValue) {
|
||||
return behavior === 'custom' ? schema.label : DSFeature[behavior].label;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEdit(item: Option) {
|
||||
const {manager} = this.props;
|
||||
@ -188,6 +200,18 @@ export class CRUDToolbarControl extends React.Component<
|
||||
CRUDSchemaID
|
||||
);
|
||||
break;
|
||||
default:
|
||||
scaffold = {
|
||||
type: 'button',
|
||||
label: '按钮',
|
||||
behavior: 'custom',
|
||||
className: 'm-r-xs',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: []
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!scaffold) {
|
||||
@ -199,7 +223,7 @@ export class CRUDToolbarControl extends React.Component<
|
||||
const actionSchema = JSONPipeIn({...scaffold});
|
||||
|
||||
options.push({
|
||||
label: DSFeature[type].label,
|
||||
label: this.getOptionLabel(actionSchema, type),
|
||||
value: type,
|
||||
nodeId: actionSchema.$$id,
|
||||
pristine: actionSchema
|
||||
@ -245,13 +269,24 @@ export class CRUDToolbarControl extends React.Component<
|
||||
|
||||
@autobind
|
||||
renderOption(item: Option, index: number) {
|
||||
const {classnames: cx} = this.props;
|
||||
const {classnames: cx, popOverContainer, env} = this.props;
|
||||
|
||||
return (
|
||||
<li key={index} className={cx('ae-CRUDConfigControl-list-item')}>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
<TooltipWrapper
|
||||
tooltip={{
|
||||
content: item.label,
|
||||
tooltipTheme: 'dark',
|
||||
style: {fontSize: '12px'}
|
||||
}}
|
||||
container={popOverContainer || env?.getModalContainer?.()}
|
||||
trigger={['hover']}
|
||||
delay={150}
|
||||
>
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-info')}>
|
||||
<span>{item.label}</span>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
|
||||
<div className={cx('ae-CRUDConfigControl-list-item-actions')}>
|
||||
<Button
|
||||
@ -283,44 +318,49 @@ export class CRUDToolbarControl extends React.Component<
|
||||
const options = this.state.options;
|
||||
const actions = this.collection.concat();
|
||||
|
||||
options.forEach(item => {
|
||||
if (actions.includes(item.value)) {
|
||||
const idx = actions.indexOf(item.value);
|
||||
if (~idx) {
|
||||
actions.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
// options.forEach(item => {
|
||||
// if (actions.includes(item.value)) {
|
||||
// const idx = actions.indexOf(item.value);
|
||||
// if (~idx) {
|
||||
// actions.splice(idx, 1);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
const optionValues = options.map(item => item.value);
|
||||
|
||||
return (
|
||||
<header className={cx('ae-CRUDConfigControl-header')}>
|
||||
<span className={cx('Form-label')}>工具栏</span>
|
||||
{render(
|
||||
'crud-toolbar-control-dropdown',
|
||||
{
|
||||
type: 'dropdown-button',
|
||||
closeOnClick: true,
|
||||
hideCaret: true,
|
||||
level: 'link',
|
||||
align: 'right',
|
||||
trigger: ['click'],
|
||||
popOverContainer:
|
||||
env.getModalContainer ?? this.dom ?? document.body,
|
||||
icon: 'column-add',
|
||||
label: '添加操作',
|
||||
className: cx('ae-CRUDConfigControl-dropdown'),
|
||||
disabledTip: {
|
||||
content: '暂无可添加操作',
|
||||
tooltipTheme: 'dark'
|
||||
},
|
||||
buttons: actions.map(item => ({
|
||||
{render('crud-toolbar-control-dropdown', {
|
||||
type: 'dropdown-button',
|
||||
closeOnClick: true,
|
||||
hideCaret: true,
|
||||
level: 'link',
|
||||
align: 'right',
|
||||
trigger: ['click'],
|
||||
popOverContainer: env.getModalContainer ?? this.dom ?? document.body,
|
||||
icon: 'column-add',
|
||||
label: '添加操作',
|
||||
className: cx('ae-CRUDConfigControl-dropdown'),
|
||||
disabledTip: {
|
||||
content: '暂无可添加操作',
|
||||
tooltipTheme: 'dark'
|
||||
},
|
||||
buttons: actions
|
||||
.map((item: Exclude<ActionValue, 'custom'>) => ({
|
||||
type: 'button',
|
||||
label: DSFeature[item].label,
|
||||
disabled: !!~optionValues.findIndex(op => op === item),
|
||||
onClick: () => this.handleAddAction(item)
|
||||
}))
|
||||
},
|
||||
{disabled: actions.length === 0}
|
||||
)}
|
||||
.concat({
|
||||
type: 'button',
|
||||
label: '自定义按钮',
|
||||
disabled: false,
|
||||
onClick: () => this.handleAddAction('custom')
|
||||
})
|
||||
})}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
@ -2702,7 +2702,7 @@ export const getEventControlConfig = (
|
||||
: ACTION_TYPE_TREE(manager);
|
||||
const allComponents = manager?.store?.getComponentTreeSource();
|
||||
const checkComponent = (node: any, action: RendererPluginAction) => {
|
||||
const actionType = action.actionType!;
|
||||
const actionType = action?.actionType;
|
||||
const actions = manager?.pluginActions[node.type];
|
||||
const haveChild = !!node.children?.length;
|
||||
let isSupport = false;
|
||||
@ -2756,6 +2756,10 @@ export const getEventControlConfig = (
|
||||
return manager.dataSchema;
|
||||
},
|
||||
getComponents: (action: RendererPluginAction) => {
|
||||
if (!action) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let components = manager?.store?.getComponentTreeSource();
|
||||
let finalCmpts: any[] = [];
|
||||
if (isSubEditor) {
|
||||
|
@ -748,7 +748,7 @@ export class EventControl extends React.Component<
|
||||
getContextSchemas,
|
||||
...actionConfig,
|
||||
groupType: actionConfig?.__actionType || action.actionType,
|
||||
__actionDesc: actionNode!.description!, // 树节点描述
|
||||
__actionDesc: actionNode?.description ?? '', // 树节点描述
|
||||
__actionSchema: actionNode!.schema, // 树节点schema
|
||||
__subActions: hasSubActionNode?.actions, // 树节点子动作
|
||||
__cmptTreeSource: supportComponents ?? [],
|
||||
|
@ -1702,6 +1702,5 @@ setSchemaTpl('primaryField', {
|
||||
'主键',
|
||||
'每行记录的唯一标识符,通常用于行选择、批量操作等场景。'
|
||||
),
|
||||
pipeIn: defaultValue('id'),
|
||||
required: true
|
||||
pipeIn: defaultValue('id')
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis-formula",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"description": "负责 amis 里面的表达式实现,内置公式,编辑器等",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "npm run clean-dist && NODE_ENV=production rollup -c ",
|
||||
@ -36,8 +36,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/mini-decimal": "^1.0.1",
|
||||
"amis-core": "^3.4.0",
|
||||
"amis-formula": "^3.4.0",
|
||||
"amis-core": "^3.4.0-alpha.4",
|
||||
"amis-formula": "^3.4.0-alpha.4",
|
||||
"classnames": "2.3.2",
|
||||
"codemirror": "^5.63.0",
|
||||
"downshift": "6.1.12",
|
||||
|
@ -149,7 +149,10 @@ export class Tag extends React.Component<TagProps> {
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<span className={cx('Tag-text')}>
|
||||
<span
|
||||
className={cx('Tag-text')}
|
||||
title={typeof label === 'string' ? label : undefined}
|
||||
>
|
||||
{prevIcon}
|
||||
{label || children}
|
||||
</span>
|
||||
@ -187,6 +190,7 @@ class CheckableTagComp extends React.Component<CheckableTagProps> {
|
||||
})}
|
||||
onClick={disabled ? noop : this.handleClick}
|
||||
style={style}
|
||||
title={typeof label === 'string' ? label : undefined}
|
||||
>
|
||||
{label || children}
|
||||
</span>
|
||||
|
@ -132,7 +132,16 @@ const FormulaInput: React.FC<FormulaInputProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const cmptValue = pipInValue(value ?? inputSettings.defaultValue);
|
||||
let cmptValue = pipInValue(value ?? inputSettings.defaultValue);
|
||||
|
||||
/** 数据来源可能是从 query中下发的(CRUD查询表头),导致数字或者布尔值被转为 string 格式,这里预处理一下 */
|
||||
if (schemaType === 'number') {
|
||||
cmptValue = isNaN(+cmptValue) ? cmptValue : +cmptValue;
|
||||
} else if (schemaType === 'boolean') {
|
||||
cmptValue =
|
||||
cmptValue === 'true' ? true : cmptValue === 'false' ? false : cmptValue;
|
||||
}
|
||||
|
||||
const targetVariable =
|
||||
variables && cmptValue != null && typeof cmptValue === 'string'
|
||||
? findTree(variables, item => {
|
||||
|
@ -468,7 +468,7 @@ export class Table extends React.PureComponent<TableProps, TableState> {
|
||||
}
|
||||
current && this.updateTableDom(current);
|
||||
|
||||
if (this.props.draggable) {
|
||||
if (this.props.draggable && this.tbodyDom?.current) {
|
||||
this.initDragging();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amis",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"description": "一种MIS页面生成工具",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
@ -37,8 +37,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"amis-core": "^3.4.0",
|
||||
"amis-ui": "^3.4.0",
|
||||
"amis-core": "^3.4.0-alpha.4",
|
||||
"amis-ui": "^3.4.0-alpha.4",
|
||||
"attr-accept": "2.2.2",
|
||||
"blueimp-canvastoblob": "2.1.0",
|
||||
"classnames": "2.3.2",
|
||||
|
@ -598,7 +598,8 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
)
|
||||
)) &&
|
||||
// 弹窗期间不进行刷新
|
||||
(!stopAutoRefreshWhenModalIsOpen || !store.dialogOpen) &&
|
||||
(!stopAutoRefreshWhenModalIsOpen ||
|
||||
(!store.dialogOpen && !store?.parentStore?.dialogOpen)) &&
|
||||
(this.timer = setTimeout(
|
||||
this.getData.bind(this, silentPolling, undefined, true),
|
||||
Math.max(interval, 1000)
|
||||
@ -1084,6 +1085,9 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
// onAction: onAction
|
||||
};
|
||||
|
||||
if (schema.type === 'pagination') {
|
||||
}
|
||||
|
||||
return render(region, schema, {
|
||||
data,
|
||||
...props,
|
||||
|
@ -212,6 +212,11 @@ export interface TableControlSchema
|
||||
* 底部新增按钮配置
|
||||
*/
|
||||
footerAddBtn?: SchemaCollection;
|
||||
|
||||
/**
|
||||
* 新增内容时是否自动聚焦
|
||||
*/
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
export interface TableProps
|
||||
@ -603,7 +608,7 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
||||
}
|
||||
|
||||
async addItem(index: number, isDispatch: boolean = true) {
|
||||
const {needConfirm, scaffold, columns, data} = this.props;
|
||||
const {needConfirm, scaffold, columns, data, autoFocus} = this.props;
|
||||
const items = this.state.items.concat();
|
||||
let value: any = {
|
||||
__isPlaceholder: true
|
||||
@ -667,6 +672,14 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
||||
} else {
|
||||
this.startEdit(index, true);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.tableRef?.current?.scrollIntoView?.({
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
inline: 'nearest'
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1632,7 +1632,9 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
label: false,
|
||||
className: cx('Table-searchableForm-checkbox'),
|
||||
inputClassName: cx('Table-searchableForm-checkbox-inner'),
|
||||
name: `__search_${column.searchable?.name ?? column.name}`,
|
||||
name: `${
|
||||
column.searchable.strategy === 'jsonql' ? '' : '__search_'
|
||||
}${column.searchable?.name ?? column.name}`,
|
||||
option: column.searchable?.label ?? column.label,
|
||||
value: column.enableSearch,
|
||||
badge: {
|
||||
|
Loading…
Reference in New Issue
Block a user