merge: 合并pre-release分支

Change-Id: I91249304a5c80b68857846cdd2b88744753d7084
This commit is contained in:
lurunze1226 2022-11-14 11:03:39 +08:00
commit d448835f43
14 changed files with 450 additions and 282 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "amis-editor", "name": "amis-editor",
"version": "5.2.1", "version": "5.2.1-beta.2",
"description": "amis 可视化编辑器", "description": "amis 可视化编辑器",
"main": "lib/index.js", "main": "lib/index.js",
"module": "esm/index.js", "module": "esm/index.js",
@ -61,24 +61,14 @@
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@types/tinycolor2": "^1.4.3", "@types/tinycolor2": "^1.4.3",
"ajv": "^8.8.2", "ajv": "^8.8.2",
"amis": "2.3.2-beta.1", "amis": "2.4.0",
"amis-core": "2.3.2-beta.1", "amis-core": "2.4.0",
"amis-formula": "2.3.2-beta.1", "amis-formula": "2.4.0",
"amis-ui": "2.3.2-beta.1", "amis-ui": "2.4.0",
"axios": "0.21.1", "axios": "0.21.1",
"concurrently": "^6.2.0", "concurrently": "^6.2.0",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"faker": "^5.5.3", "faker": "^5.5.3",
"fis-parser-sass": "^1.1.0",
"fis-parser-svgr": "^1.0.1",
"fis3": "^3.5.0-beta.0",
"fis3-hook-commonjs": "^0.1.32",
"fis3-hook-node_modules": "^2.3.1",
"fis3-parser-typescript": "^1.4.0",
"fis3-postpackager-loader": "^2.1.12",
"fis3-prepackager-stand-alone-pack": "^1.0.1",
"fis3-preprocessor-js-require-css": "^0.1.3",
"fis3-preprocessor-js-require-file": "^0.1.3",
"husky": "^7.0.0", "husky": "^7.0.0",
"lint-staged": "^12.1.2", "lint-staged": "^12.1.2",
"mini-css-extract-plugin": "^2.3.0", "mini-css-extract-plugin": "^2.3.0",

View File

@ -8,8 +8,10 @@ import {
diff, diff,
defaultValue, defaultValue,
getSchemaTpl, getSchemaTpl,
CodeEditor as AmisCodeEditor CodeEditor as AmisCodeEditor,
RendererPluginEvent
} from 'amis-editor-core'; } from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
const ChartConfigEditor = ({value, onChange}: any) => { const ChartConfigEditor = ({value, onChange}: any) => {
return ( return (
@ -19,6 +21,54 @@ const ChartConfigEditor = ({value, onChange}: any) => {
); );
}; };
const DEFAULT_EVENT_PARAMS = [
{
type: 'object',
properties: {
'event.data.componentType': {
type: 'string',
title: 'componentType'
},
'event.data.seriesType': {
type: 'string',
title: 'seriesType'
},
'event.data.seriesIndex': {
type: 'number',
title: 'seriesIndex'
},
'event.data.seriesName': {
type: 'string',
title: 'seriesName'
},
'event.data.name': {
type: 'string',
title: 'name'
},
'event.data.dataIndex': {
type: 'number',
title: 'dataIndex'
},
'event.data.data': {
type: 'object',
title: 'data'
},
'event.data.dataType': {
type: 'string',
title: 'dataType'
},
'event.data.value': {
type: 'number',
title: 'data'
},
'event.data.color': {
type: 'string',
title: 'color'
}
}
}
];
export class ChartPlugin extends BasePlugin { export class ChartPlugin extends BasePlugin {
// 关联渲染器名字 // 关联渲染器名字
rendererName = 'chart'; rendererName = 'chart';
@ -56,6 +106,42 @@ export class ChartPlugin extends BasePlugin {
...this.scaffold ...this.scaffold
}; };
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: 'click',
eventLabel: '鼠标点击',
description: '鼠标点击时触发',
dataSchema: DEFAULT_EVENT_PARAMS
},
{
eventName: 'mouseover',
eventLabel: '鼠标悬停',
description: '鼠标悬停时触发',
dataSchema: DEFAULT_EVENT_PARAMS
},
{
eventName: 'legendselectchanged',
eventLabel: '切换图例选中状态',
description: '切换图例选中状态时触发',
dataSchema: [
{
type: 'object',
properties: {
'event.data.name': {
type: 'string',
title: 'name'
},
'event.data.selected': {
type: 'object',
title: 'selected'
}
}
}
]
}
];
// 动作定义 // 动作定义
actions: RendererPluginAction[] = [ actions: RendererPluginAction[] = [
{ {
@ -68,6 +154,7 @@ export class ChartPlugin extends BasePlugin {
actionLabel: '更新数据', actionLabel: '更新数据',
description: '触发组件数据更新' description: '触发组件数据更新'
} }
// 特性动作太多了,这里先不加了,可以通过写代码配置
]; ];
panelTitle = '图表'; panelTitle = '图表';
@ -184,6 +271,16 @@ export class ChartPlugin extends BasePlugin {
{ {
title: '其他', title: '其他',
body: [getSchemaTpl('name')] body: [getSchemaTpl('name')]
},
{
title: '事件',
className: 'p-none',
body: [
getSchemaTpl('eventControl', {
name: 'onEvent',
...getEventControlConfig(this.manager, context)
})
]
} }
]) ])
]; ];

View File

@ -5,15 +5,19 @@ import {BasePlugin, BaseEventContext} from 'amis-editor-core';
import {ValidatorTag} from '../../validator'; import {ValidatorTag} from '../../validator';
import {getEventControlConfig} from '../../renderer/event-control/helper'; import {getEventControlConfig} from '../../renderer/event-control/helper';
import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core'; import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core';
import {getRendererByName} from 'amis-core';
const DateType: { const DateType: {
[key: string]: { [key: string]: {
format: string; format: string;
placeholder: string; placeholder: string;
ranges: string[]; ranges: string[];
sizeMutable?: boolean;
type?: string;
}; };
} = { } = {
date: { date: {
...getRendererByName('input-date-range'),
format: 'YYYY-MM-DD', format: 'YYYY-MM-DD',
placeholder: '请选择日期范围', placeholder: '请选择日期范围',
ranges: [ ranges: [
@ -26,6 +30,7 @@ const DateType: {
] ]
}, },
datetime: { datetime: {
...getRendererByName('input-datetime-range'),
format: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss',
placeholder: '请选择日期时间范围', placeholder: '请选择日期时间范围',
ranges: [ ranges: [
@ -38,27 +43,35 @@ const DateType: {
] ]
}, },
time: { time: {
...getRendererByName('input-time-range'),
format: 'HH:mm', format: 'HH:mm',
placeholder: '请选择时间范围', placeholder: '请选择时间范围',
ranges: [] ranges: []
}, },
month: { month: {
...getRendererByName('input-month-range'),
format: 'YYYY-MM', format: 'YYYY-MM',
placeholder: '请选择月份范围', placeholder: '请选择月份范围',
ranges: [] ranges: []
}, },
quarter: { quarter: {
...getRendererByName('input-quarter-range'),
format: 'YYYY [Q]Q', format: 'YYYY [Q]Q',
placeholder: '请选择季度范围', placeholder: '请选择季度范围',
ranges: ['thisquarter', 'prevquarter'] ranges: ['thisquarter', 'prevquarter']
}, },
year: { year: {
...getRendererByName('input-year-range'),
format: 'YYYY', format: 'YYYY',
placeholder: '请选择年范围', placeholder: '请选择年范围',
ranges: ['thisyear', 'lastYear'] ranges: ['thisyear', 'lastYear']
} }
}; };
const sizeImmutableComponents = Object.values(DateType)
.map(item => (item?.sizeMutable === false ? item.type : null))
.filter(a => a);
const tipedLabelText = const tipedLabelText =
'支持 <code>now、+1day、-2weeks、+1hours、+2years</code>这种相对值用法,同时支持变量如<code>\\${start_date}</code>'; '支持 <code>now、+1day、-2weeks、+1hours、+2years</code>这种相对值用法,同时支持变量如<code>\\${start_date}</code>';
@ -206,7 +219,11 @@ export class DateRangeControlPlugin extends BasePlugin {
minDate: '', minDate: '',
maxDate: '', maxDate: '',
value: '', value: '',
ranges: DateType[type]?.ranges ranges: DateType[type]?.ranges,
// size immutable 组件去除 size 字段
size: sizeImmutableComponents.includes(value)
? undefined
: form.data?.size
}); });
} }
}), }),
@ -307,16 +324,27 @@ export class DateRangeControlPlugin extends BasePlugin {
}), }),
getSchemaTpl('dateShortCutControl', { getSchemaTpl('dateShortCutControl', {
mode: 'normal', mode: 'normal',
dropDownOption: { normalDropDownOption: {
'yesterday': '昨天', yesterday: '昨天',
'thisweek': '这个周', thisweek: '这个周',
'prevweek': '上周', prevweek: '上周',
'7daysago': '最近7天', thismonth: '这个月',
'thismonth': '这个月', prevmonth: '上个月',
'prevmonth': '上个月', thisquarter: '这个季度',
'thisquarter': '这个季度', prevquarter: '上个季度',
'prevquarter': '上个季度', thisyear: '今年'
'thisyear': '今年' },
customDropDownOption: {
daysago: '最近n天',
dayslater: 'n天以内',
weeksago: '最近n周',
weekslater: 'n周以内',
monthsago: '最近n月',
monthslater: 'n月以内',
quartersago: '最近n季度',
quarterslater: 'n季度以内',
yearsago: '最近n年',
yearslater: 'n年以内'
} }
}), }),
// getSchemaTpl('remark'), // getSchemaTpl('remark'),
@ -349,7 +377,17 @@ export class DateRangeControlPlugin extends BasePlugin {
body: getSchemaTpl( body: getSchemaTpl(
'collapseGroup', 'collapseGroup',
[ [
getSchemaTpl('style:formItem', renderer), getSchemaTpl('style:formItem', {
renderer: {...renderer, sizeMutable: false},
schema: [
// 需要作为一个字符串表达式传入,因为切换 type 后 panelBodyCreator 不会重新执行
getSchemaTpl('formItemSize', {
hiddenOn: `["${sizeImmutableComponents.join(
'","'
)}"].includes(this.type)`
})
]
}),
getSchemaTpl('style:classNames', [ getSchemaTpl('style:classNames', [
getSchemaTpl('className', { getSchemaTpl('className', {
label: '描述', label: '描述',

View File

@ -32,7 +32,8 @@ export class NumberControlPlugin extends BasePlugin {
scaffold = { scaffold = {
type: 'input-number', type: 'input-number',
label: '数字', label: '数字',
name: 'number' name: 'number',
keyboard: true
}; };
previewSchema: any = { previewSchema: any = {
type: 'form', type: 'form',
@ -137,7 +138,16 @@ export class NumberControlPlugin extends BasePlugin {
required: true required: true
}), }),
getSchemaTpl('label'), getSchemaTpl('label'),
getSchemaTpl('numberSwitchKeyboard'), {
type: 'switch',
label: tipedLabel(
'键盘事件',
'通过键盘上下方向键来加减数据值'
),
name: 'keyboard',
value: true,
inputClassName: 'is-inline'
},
getSchemaTpl('kilobitSeparator'), getSchemaTpl('kilobitSeparator'),
getSchemaTpl('valueFormula', { getSchemaTpl('valueFormula', {
@ -152,10 +162,7 @@ export class NumberControlPlugin extends BasePlugin {
value: context?.schema.min value: context?.schema.min
}, },
needDeleteProps: ['min'], // 避免自我限制 needDeleteProps: ['min'], // 避免自我限制
label: tipedLabel( label: '最小值',
'最小值',
'请输入数字或使用 <code>\\${xxx}</code> 来获取变量,否则该配置不生效'
),
valueType: 'number' valueType: 'number'
}), }),
@ -166,10 +173,7 @@ export class NumberControlPlugin extends BasePlugin {
value: context?.schema.max value: context?.schema.max
}, },
needDeleteProps: ['max'], // 避免自我限制 needDeleteProps: ['max'], // 避免自我限制
label: tipedLabel( label: '最大值',
'最大值',
'请输入数字或使用 <code>\\${xxx}</code> 来获取变量,否则该配置不生效'
),
valueType: 'number' valueType: 'number'
}), }),
@ -194,18 +198,12 @@ export class NumberControlPlugin extends BasePlugin {
{ {
type: 'input-text', type: 'input-text',
name: 'prefix', name: 'prefix',
label: tipedLabel( label: tipedLabel('前缀', '输入内容前展示,不包含在数据值中')
'前缀',
'仅在输入内容前展示,不包含在数据值中'
)
}, },
{ {
type: 'input-text', type: 'input-text',
name: 'suffix', name: 'suffix',
label: tipedLabel( label: tipedLabel('后缀', '输入内容后展示,不包含在数据值中')
'后缀',
'仅在输入内容前展示,不包含在数据值中'
)
}, },
getSchemaTpl('combo-container', { getSchemaTpl('combo-container', {
@ -263,7 +261,7 @@ export class NumberControlPlugin extends BasePlugin {
label: '快捷编辑', label: '快捷编辑',
name: 'displayMode', name: 'displayMode',
type: 'select', type: 'select',
value: 'base', pipeIn: defaultValue('base'),
options: [ options: [
{ {
label: '单侧按钮', label: '单侧按钮',

View File

@ -112,28 +112,18 @@ export class RateControlPlugin extends BasePlugin {
valueType: 'number', // 期望数值类型 valueType: 'number', // 期望数值类型
visibleOn: '!data.multiple' visibleOn: '!data.multiple'
}), }),
// 评分组件没有 min、max 属性,有 count 属性
getSchemaTpl('valueFormula', { getSchemaTpl('valueFormula', {
name: 'min', name: 'count',
rendererSchema: { rendererSchema: {
...context?.schema, ...context?.schema,
type: 'input-number' type: 'input-number',
max: 10,
min: 1,
step: 1,
precision: 0
}, },
needDeleteProps: ['min'], // 避免自我限制 needDeleteProps: ['count'], // 避免自我限制
label: tipedLabel(
'最小值',
'请输入数字或使用 <code>\\${xxx}</code> 来获取变量,否则该配置不生效'
),
valueType: 'number'
}),
getSchemaTpl('valueFormula', {
name: 'max',
rendererSchema: {
...context?.schema,
type: 'input-number'
},
needDeleteProps: ['max'], // 避免自我限制
label: tipedLabel( label: tipedLabel(
'最大值', '最大值',
'请输入数字或使用 <code>\\${xxx}</code> 来获取变量,否则该配置不生效' '请输入数字或使用 <code>\\${xxx}</code> 来获取变量,否则该配置不生效'

View File

@ -190,13 +190,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.addable', visibleOn: 'data.addable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'addBtnIcon', name: 'addBtnIcon',
label: '增加按钮图标', label: '增加按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.addable' visibleOn: 'data.addable'
}, }),
getSchemaTpl('api', { getSchemaTpl('api', {
name: 'addApi', name: 'addApi',
label: '新增时提交的 API', label: '新增时提交的 API',
@ -213,13 +212,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.removable', visibleOn: 'data.removable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'deleteBtnIcon', name: 'deleteBtnIcon',
label: '删除按钮图标', label: '删除按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.removable' visibleOn: 'data.removable'
}, }),
getSchemaTpl('api', { getSchemaTpl('api', {
name: 'deleteApi', name: 'deleteApi',
label: '删除时提交的 API', label: '删除时提交的 API',
@ -236,13 +234,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.editable', visibleOn: 'data.editable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'editBtnIcon', name: 'editBtnIcon',
label: '编辑按钮图标', label: '编辑按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.editable' visibleOn: 'data.editable'
}, }),
getSchemaTpl('switch', { getSchemaTpl('switch', {
label: '是否可复制', label: '是否可复制',
name: 'copyable' name: 'copyable'
@ -254,13 +251,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.copyable', visibleOn: 'data.copyable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'copyBtnIcon', name: 'copyBtnIcon',
label: '复制按钮图标', label: '复制按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.copyable' visibleOn: 'data.copyable'
}, }),
getSchemaTpl('api', { getSchemaTpl('api', {
name: 'updateApi', name: 'updateApi',
label: '修改时提交的 API', label: '修改时提交的 API',
@ -273,13 +269,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.editable', visibleOn: 'data.editable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'confirmBtnIcon', name: 'confirmBtnIcon',
label: '确认编辑按钮图标', label: '确认编辑按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.editable' visibleOn: 'data.editable'
}, }),
{ {
type: 'input-text', type: 'input-text',
name: 'cancelBtnLabel', name: 'cancelBtnLabel',
@ -287,13 +282,12 @@ export class TableControlPlugin extends BasePlugin {
visibleOn: 'data.editable', visibleOn: 'data.editable',
pipeIn: defaultValue('') pipeIn: defaultValue('')
}, },
{ getSchemaTpl('icon', {
name: 'cancelBtnIcon', name: 'cancelBtnIcon',
label: '取消编辑按钮图标', label: '取消编辑按钮图标',
type: 'icon-picker',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
visibleOn: 'data.editable' visibleOn: 'data.editable'
}, }),
getSchemaTpl('switch', { getSchemaTpl('switch', {
label: '是否可拖拽排序', label: '是否可拖拽排序',
name: 'draggable' name: 'draggable'

View File

@ -51,35 +51,16 @@ export class LinkPlugin extends BasePlugin {
name: 'blank', name: 'blank',
label: '在新窗口打开' label: '在新窗口打开'
}), }),
{
label: '图标位置',
type: 'button-group-select',
name: 'position',
pipeIn: defaultValue('rightIcon'),
size: 'sm',
value: 'rightIcon',
options: [
{
label: '左侧',
value: 'icon'
},
{
label: '右侧',
value: 'rightIcon'
}
]
},
getSchemaTpl('iconLink', { getSchemaTpl('iconLink', {
name: 'icon', name: 'icon',
visibleOn: 'this.position == "icon"' label: '左侧图标'
}), }),
getSchemaTpl('iconLink', { getSchemaTpl('iconLink', {
name: 'rightIcon', name: 'rightIcon',
visibleOn: 'this.position == "rightIcon"' label: '右侧图标'
}), })
] ]
}, },
getSchemaTpl('status', { getSchemaTpl('status', {

View File

@ -210,6 +210,7 @@ export class PagePlugin extends BasePlugin {
}), }),
getSchemaTpl('apiControl', { getSchemaTpl('apiControl', {
name: 'initApi', name: 'initApi',
mode: 'row',
labelClassName: 'none', labelClassName: 'none',
label: tipedLabel( label: tipedLabel(
'初始化接口', '初始化接口',

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import merge from 'lodash/merge'; import mergeWith from 'lodash/mergeWith';
import isEqual from 'lodash/isEqual';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import cx from 'classnames'; import cx from 'classnames';
import {FormItem, Icon} from 'amis'; import {FormItem, Icon} from 'amis';
@ -324,7 +323,20 @@ export default class APIControl extends React.Component<
} }
if (typeof value !== 'string' || typeof values !== 'string') { if (typeof value !== 'string' || typeof values !== 'string') {
api = merge({}, normalizeApi(values)); api = mergeWith({}, normalizeApi(value), normalizeApi(values),
(value, srcValue, key) => {
// 这三个支持删除单个key的属性需用新值完全替换
// 否则删除无效
if (['data', 'responseData', 'headers'].includes(key)) {
return srcValue;
}
}
);
['data', 'responseData', 'headers'].forEach((item: keyof(Api)) => {
if (api[item] == null) {
delete api[item];
}
});
} }
onChange?.(api); onChange?.(api);
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import merge from 'lodash/merge'; import mergeWith from 'lodash/mergeWith';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import cx from 'classnames'; import cx from 'classnames';
import {FormItem, InputBox} from 'amis'; import {FormItem, InputBox} from 'amis';
@ -195,7 +195,20 @@ export default class APIControl extends React.Component<
} }
if (typeof value !== 'string' || typeof values !== 'string') { if (typeof value !== 'string' || typeof values !== 'string') {
api = merge({}, normalizeApi(value), normalizeApi(values)); api = mergeWith({}, normalizeApi(value), normalizeApi(values),
(value, srcValue, key) => {
// 这三个支持删除单个key的属性需用新值完全替换
// 否则删除无效
if (['data', 'responseData', 'headers'].includes(key)) {
return srcValue;
}
}
);
['data', 'responseData', 'headers'].forEach((item: keyof(Api)) => {
if (api[item] == null) {
delete api[item];
}
});
} }
onChange?.(api); onChange?.(api);

View File

@ -5,41 +5,71 @@ import React from 'react';
import cx from 'classnames'; import cx from 'classnames';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {FormItem, Button, Icon, InputBox} from 'amis'; import {FormItem, Icon} from 'amis';
import type {FormControlProps} from 'amis-core'; import type {FormControlProps} from 'amis-core';
import type {BaseEventContext} from 'amis-editor-core'; import type {BaseEventContext} from 'amis-editor-core';
import type {Option} from 'amis';
import {autobind} from 'amis-editor-core'; import {autobind} from 'amis-editor-core';
import FormulaControl from './FormulaControl';
const CustomType = 'custom'; type $Object = {
type RangesType = Array<string | {label: string; range: any}>;
type DropDownOption = {
[key: string]: string; [key: string]: string;
}; };
enum RangeType {
Normal = 'Normal',
Custom = 'Custom'
}
export interface DateShortCutControlProps extends FormControlProps { export interface DateShortCutControlProps extends FormControlProps {
className?: string; className?: string;
/** /**
* Form的其他字段 * Form的其他字段
*/ */
context: BaseEventContext; context: BaseEventContext;
dropDownOption: DropDownOption; normalDropDownOption: $Object;
customDropDownOption: $Object;
} }
interface OptionsType { interface OptionsType {
label: string; label?: string;
type: string; value: string;
inputValue: string; type: RangeType;
inputType?: string;
} }
interface DateShortCutControlState { interface DateShortCutControlState {
options: Array<OptionsType>; options: Array<OptionsType>;
} }
interface InputOption {
type: 'middle' | 'suffix',
prefix?: string,
suffix: string
}
const ShortCutItemWrap = (
props: {
index: number,
children: React.ReactNode,
handleDelete: (index: number, e: React.SyntheticEvent<any>) => void
}) => {
return (
<>
<a className={klass + 'Item-dragBar'}><Icon icon='drag-bar' className='icon' /></a>
<span className={klass + 'Item-content'}>
{props.children}
</span>
<span
className={klass + 'Item-close'}
onClick={(e) => props.handleDelete(props.index, e)}>
<Icon icon='status-close' className='icon' />
</span>
</>
);
}
const klass = 'ae-DateShortCutControl'; const klass = 'ae-DateShortCutControl';
export class DateShortCutControl extends React.PureComponent< export class DateShortCutControl extends React.PureComponent<
@ -49,10 +79,8 @@ export class DateShortCutControl extends React.PureComponent<
sortable?: Sortable; sortable?: Sortable;
drag?: HTMLElement | null; drag?: HTMLElement | null;
target: HTMLElement | null; target: HTMLElement | null;
dropDownOptionArr: Array<{ normalDropDownOptionArr: Array<Option>;
label: string; customDropDownOptionArr: Array<Option>
value: string;
}>;
static defaultProps: Partial<DateShortCutControlProps> = { static defaultProps: Partial<DateShortCutControlProps> = {
label: '快捷键' label: '快捷键'
@ -60,18 +88,16 @@ export class DateShortCutControl extends React.PureComponent<
constructor(props: DateShortCutControlProps) { constructor(props: DateShortCutControlProps) {
super(props); super(props);
const {dropDownOption, data} = props; const {normalDropDownOption, customDropDownOption, data} = props;
this.dropDownOptionArr = Object.keys(dropDownOption).map(key => ({ this.normalDropDownOptionArr = Object.keys(normalDropDownOption).map(key => ({
label: dropDownOption[key], label: normalDropDownOption[key],
value: key value: key
})); }));
this.initOptions(data.ranges); this.customDropDownOptionArr = Object.keys(customDropDownOption).map(key => ({
} label: customDropDownOption[key],
value: key
initOptions(ranges: RangesType) { }));
if (!ranges) { const defaultRanges = [
// 这里先写固定如果amis的dateTimeRange组件暴露对应属性从中获取更合适到时需要让其暴露下
ranges = [
'yesterday', 'yesterday',
'7daysago', '7daysago',
'prevweek', 'prevweek',
@ -79,51 +105,23 @@ export class DateShortCutControl extends React.PureComponent<
'prevmonth', 'prevmonth',
'prevquarter' 'prevquarter'
]; ];
this.state = {
options: (data?.ranges ?? defaultRanges).map((item: string, index: number) => {
const arr = item.match(/^(\d+)[a-zA-Z]+/);
if (arr) {
return {
value: arr[1],
type: RangeType.Custom,
inputType: item.match(/[a-zA-Z]+/)?.[0]
} }
const {dropDownOption} = this.props;
const options: Array<OptionsType> = [];
if (Array.isArray(ranges)) {
ranges.map(item => {
if (typeof item === 'string' && dropDownOption.hasOwnProperty(item)) {
options.push({
label: dropDownOption[item as keyof typeof dropDownOption],
type: item,
inputValue: item
});
} }
if (typeof item === 'object') { return {
options.push({ label: normalDropDownOption[item],
label: item?.label, value: item,
type: CustomType, type: RangeType.Normal,
inputValue: item.range
});
} }
}); })
} };
this.state = {options};
}
/**
*
*/
addItem(item: {label: string; value: string}) {
const {options} = this.state;
this.setState(
{
options: [
...options,
{
...item,
inputValue: item.value === CustomType ? '' : item.value,
type: item.value
}
]
},
() => {
this.onChangeOptions();
this.scrollToBottom();
}
);
} }
@autobind @autobind
@ -207,72 +205,121 @@ export class DateShortCutControl extends React.PureComponent<
<div className={klass + '-wrapper'}> <div className={klass + '-wrapper'}>
{options && options.length ? ( {options && options.length ? (
<ul className={klass + '-content'} ref={this.dragRef}> <ul className={klass + '-content'} ref={this.dragRef}>
{options.map((option, index) => this.renderOption(option, index))} {options.map(
(option, index) =>
<li className={klass + 'Item'} key={index}>
{option.type === RangeType.Normal
? this.renderNormalOption(option, index)
: this.renderCustomOption(option, index)}
</li>
)
}
</ul> </ul>
) : ( ) : (
<div className="ae-OptionControl-placeholder"></div> <div className={klass + '-content ' + klass + '-empty'}></div>
)} )}
</div> </div>
); );
} }
/** /**
* *
*/ */
renderOption(option: OptionsType, index: number) { renderNormalOption(option: OptionsType, index: number) {
return ( return (
<li className={klass + 'Item'} key={index}> <ShortCutItemWrap index={index} handleDelete={this.handleDelete}>
<a className={klass + 'Item-dragBar'}> <span>{option.label}</span>
<Icon icon="drag-bar" className="icon" /> </ShortCutItemWrap>);
</a>
<InputBox
className={klass + 'Item-input'}
clearable={false}
placeholder="名称"
value={option.label}
onInput={(e: React.FocusEvent<HTMLInputElement>) =>
this.onInputChange(index, e.target.value, 'label')
} }
/>
<FormulaControl /**
{...this.props} *
simple */
variables={[]} renderCustomOption(option: OptionsType, index: number) {
functions={[]} const {render} = this.props;
header={''}
onChange={(value: string) => const renderInput = (option: InputOption & {value: string}) => {
this.onInputChange(index, value, 'inputValue') if (option.type === 'middle') {
return render('inner', {
type: 'input-text',
prefix: option?.prefix,
suffix: option.suffix,
mode: 'normal',
placeholder: 'n',
value: option?.value,
onChange: (value: string) => this.handleCustomItemChange(value, index)
})
} }
value={option.inputValue} return render('inner', {
/> type: 'input-text',
<Button placeholder: 'n',
className={klass + 'Item-action'} mode: 'normal',
level="link" suffix: option.suffix,
size="md" value: option?.value,
onClick={(e: React.UIEvent<any>) => this.handleDelete(index, e)} onChange: (value: string) => this.handleCustomItemChange(value, index)
> })
<Icon icon="delete-btn" className="icon" /> }
</Button>
</li> const dateMap: {[key: string]: InputOption} = {
daysago: {prefix: '最近', suffix: '天', type: 'middle'},
dayslater: {suffix: '天以内', type: 'suffix'},
weeksago: {prefix: '最近', suffix: '周', type: 'middle'},
weekslater: {suffix: '周以内', type: 'suffix'},
monthsago: {prefix: '最近', suffix: '月', type: 'middle'},
monthslater: {suffix: '月以内', type: 'suffix'},
quartersago: {prefix: '最近', suffix: '季度', type: 'middle'},
quarterslater: {suffix: '季度以内', type: 'suffix'},
yearsago: {prefix: '最近', suffix: '年', type: 'middle'},
yearslater: {suffix: '年以内', type: 'suffix'}
}
return (
<ShortCutItemWrap index={index} handleDelete={this.handleDelete}>
{option.inputType
? renderInput({...dateMap[option.inputType], value: option.value})
: null}
</ShortCutItemWrap>
); );
} }
/** /**
* *
*/ */
onInputChange(index: number, value: string, key: 'inputValue' | 'label') { handleCustomItemChange(value: string, index: number) {
const options = this.state.options.concat(); const options = [...this.state.options];
options[index][key] = value; options[index].value = value;
options[index].type = CustomType;
this.setState({options}, () => this.onChangeOptions()); this.setState({options}, () => this.onChangeOptions());
} }
/**
* option添加
*/
addItem(item: Option, type: RangeType) {
this.setState(
{
options: [
...this.state.options,
{
label: item?.label ?? '',
type,
value: type === RangeType.Normal ? item.value : '',
...(type === RangeType.Normal ? {} : {inputType: item.value})
}
]
},
() => {
this.onChangeOptions();
this.scrollToBottom();
}
);
}
/** /**
* *
*/ */
@autobind
handleDelete(index: number, e: React.UIEvent<any>) { handleDelete(index: number, e: React.UIEvent<any>) {
const options = this.state.options.concat(); const options = this.state.options.concat();
options.splice(index, 1); options.splice(index, 1);
this.setState({options}, () => this.onChangeOptions()); this.setState({options}, () => this.onChangeOptions());
} }
@ -283,28 +330,20 @@ export class DateShortCutControl extends React.PureComponent<
onChangeOptions() { onChangeOptions() {
const {options} = this.state; const {options} = this.state;
const {onBulkChange} = this.props; const {onBulkChange} = this.props;
const newOptions: RangesType = []; const newOptions: Array<string> = [];
options.forEach(item => { options.forEach((item, index) => {
if (item.type !== CustomType) { if (item.type === RangeType.Normal) {
newOptions.push(item.inputValue); newOptions[index] = item.value;
} else { }
newOptions.push({ if (item.type === RangeType.Custom && item.value) {
label: item.label, newOptions[index] = `${item.value}${item.inputType}`;
range: item.inputValue
});
} }
}); });
onBulkChange && onBulkChange({ranges: newOptions}); onBulkChange && onBulkChange({ranges: newOptions});
} }
render() { render() {
const {className, label, render} = this.props; const {className, label, render} = this.props;
const optionList = this.dropDownOptionArr.map((item: any) => ({
...item,
type: 'button',
onAction: (e: React.MouseEvent, action: any) => this.addItem(item)
}));
return ( return (
<div className={cx(klass, className)}> <div className={cx(klass, className)}>
<header className={klass + '-header'}> <header className={klass + '-header'}>
@ -312,20 +351,46 @@ export class DateShortCutControl extends React.PureComponent<
</header> </header>
{this.renderContent()} {this.renderContent()}
<div className={klass + '-footer'}> <div className={klass + '-footer'}>
<div className={klass + '-footer-btn'}>
{render( {render(
'inner', 'inner',
{ {
type: 'dropdown-button', type: 'dropdown-button',
label: '添加选项', label: '常用跨度',
closeOnClick: true, closeOnClick: true,
closeOnOutside: true, closeOnOutside: true,
buttons: optionList level: 'enhance',
buttons: this.normalDropDownOptionArr.map((item: any) => ({
...item,
type: 'button',
onAction: (e: React.MouseEvent, action: any) => this.addItem(item, RangeType.Normal)
}))
}, },
{ {
popOverContainer: null // amis 渲染挂载节点会使用 this.target popOverContainer: null
} }
)} )}
</div> </div>
<div className={klass + '-footer-btn'}>
{render(
'inner',
{
type: 'dropdown-button',
label: '自定义跨度',
closeOnClick: true,
closeOnOutside: true,
buttons: this.customDropDownOptionArr.map((item: any) => ({
...item,
type: 'button',
onAction: (e: React.MouseEvent, action: any) => this.addItem(item, RangeType.Custom)
}))
},
{
popOverContainer: null
}
)}
</div>
</div>
</div> </div>
); );
} }

View File

@ -356,7 +356,7 @@ export default class SwitchMore extends React.Component<
return { return {
type: 'input-sub-form', type: 'input-sub-form',
btnLabel: '', btnLabel: '',
className: 'inline-block m-0 h-6', className: 'inline-block m-0 h-6 bg-white ',
itemClassName: 'bg-white hover:bg-white m-0 p-0', itemClassName: 'bg-white hover:bg-white m-0 p-0',
icon: 'fa fa-cog', icon: 'fa fa-cog',
form: { form: {

View File

@ -163,14 +163,11 @@ export default class TimelineItemControl extends React.Component<
placeholder: '请输入', placeholder: '请输入',
label: '颜色' label: '颜色'
}, },
{ getSchemaTpl('icon', {
type: 'icon-picker',
name: 'icon',
value: props?.['icon'], value: props?.['icon'],
placeholder: '请输入', placeholder: '请输入',
clearable: true, clearable: true,
description: '', description: '',
label: '图标',
className: 'fix-icon-picker-overflow', className: 'fix-icon-picker-overflow',
pipeIn: (value: any) => value?.icon, pipeIn: (value: any) => value?.icon,
pipeOut: (value: any) => { pipeOut: (value: any) => {
@ -183,7 +180,7 @@ export default class TimelineItemControl extends React.Component<
} }
return undefined; return undefined;
} }
} })
]; ];
} }

View File

@ -120,7 +120,7 @@ setSchemaTpl('formItemSize', {
name: 'size', name: 'size',
label: '控件宽度', label: '控件宽度',
type: 'select', type: 'select',
pipeIn: defaultValue(''), pipeIn: defaultValue('full'),
// mode: 'inline', // mode: 'inline',
// className: 'w-full', // className: 'w-full',
options: [ options: [
@ -144,12 +144,8 @@ setSchemaTpl('formItemSize', {
value: 'lg' value: 'lg'
}, },
{ {
label: '占满', label: '默认(占满',
value: 'full' value: 'full'
},
{
label: '默认',
value: ''
} }
] ]
}); });
@ -692,7 +688,6 @@ setSchemaTpl('visible', {
expressionName: 'visibleOn' expressionName: 'visibleOn'
}); });
setSchemaTpl('static', { setSchemaTpl('static', {
type: 'ae-StatusControl', type: 'ae-StatusControl',
label: '静态展示', label: '静态展示',
@ -752,13 +747,6 @@ setSchemaTpl('numberSwitchDefaultValue', {
pipeOut: (value: any, origin: any, data: any) => (value ? '' : undefined) pipeOut: (value: any, origin: any, data: any) => (value ? '' : undefined)
}); });
setSchemaTpl('numberSwitchKeyboard', {
type: 'switch',
label: tipedLabel('键盘事件', '默认是不启用'),
name: 'keyboard',
inputClassName: 'is-inline'
});
setSchemaTpl('kilobitSeparator', { setSchemaTpl('kilobitSeparator', {
type: 'switch', type: 'switch',
label: '千分符', label: '千分符',
@ -1086,12 +1074,16 @@ setSchemaTpl('app-page-args', {
setSchemaTpl( setSchemaTpl(
'iconLink', 'iconLink',
(schema: {name: 'icon' | 'rightIcon'; visibleOn: boolean}) => { (schema: {
const {name, visibleOn} = schema; name: 'icon' | 'rightIcon';
visibleOn: boolean;
label?: string;
}) => {
const {name, visibleOn, label} = schema;
return getSchemaTpl('icon', { return getSchemaTpl('icon', {
name: name, name: name,
visibleOn, visibleOn,
label: '图标', label: label ?? '图标',
placeholder: '点击选择图标', placeholder: '点击选择图标',
clearable: true, clearable: true,
description: '' description: ''