mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 11:07:52 +08:00
1291 lines
30 KiB
TypeScript
1291 lines
30 KiB
TypeScript
/**
|
||
* @file 生成一些通用的配置项
|
||
*/
|
||
|
||
import {debug} from 'console';
|
||
|
||
/**
|
||
* 创建一个层级,比如 name 是 title,这里面的子组件 name 就相当于 title.name
|
||
* @param name
|
||
* @param label
|
||
*/
|
||
|
||
export const createHierarchy = (name: string, controls: any[]) => {
|
||
return {
|
||
name: name,
|
||
type: 'combo',
|
||
label: '',
|
||
noBorder: true,
|
||
multiLine: true,
|
||
controls: controls
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 方便生成颜色类的控件
|
||
* @param name
|
||
* @param label
|
||
*/
|
||
export const color = (
|
||
name: string,
|
||
label: string,
|
||
defaultColor?: string,
|
||
labelRemark?: string
|
||
) => {
|
||
return {
|
||
type: 'color',
|
||
format: 'rgba',
|
||
pipeIn: (value: any) => {
|
||
if (
|
||
typeof value === 'undefined' &&
|
||
defaultColor &&
|
||
defaultColor !== 'null'
|
||
) {
|
||
return defaultColor;
|
||
}
|
||
return value;
|
||
},
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined,
|
||
name: name,
|
||
label: label
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 默认值是 true 的 switch,并且 inline
|
||
* @param name
|
||
* @param label
|
||
*/
|
||
export const trueSwitch = (
|
||
name: string,
|
||
label: string,
|
||
labelRemark?: string
|
||
) => {
|
||
return {
|
||
type: 'switch',
|
||
name: name,
|
||
mode: 'inline',
|
||
className: 'w-full',
|
||
label: label,
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return true;
|
||
}
|
||
return value;
|
||
}
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 默认值是 false 的 switch,并且 inline
|
||
* @param name
|
||
* @param label
|
||
*/
|
||
export const falseSwitch = (
|
||
name: string,
|
||
label: string,
|
||
labelRemark?: string
|
||
) => {
|
||
return {
|
||
type: 'switch',
|
||
name: name,
|
||
mode: 'inline',
|
||
className: 'w-full',
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined,
|
||
label: label
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 如果没数据就默认用第一个的 select
|
||
* @param name
|
||
* @param label
|
||
* @param options
|
||
*/
|
||
export const select = (
|
||
name: string,
|
||
label: string,
|
||
options: any[],
|
||
labelRemark?: string
|
||
) => {
|
||
return {
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return options[0];
|
||
}
|
||
return value;
|
||
},
|
||
options: options
|
||
};
|
||
};
|
||
|
||
/**
|
||
* fieldSet 辅助
|
||
* @param label
|
||
* @param controls
|
||
* @param collapsed
|
||
*/
|
||
export const fieldSet = (
|
||
label: string,
|
||
controls: any[],
|
||
collapsed: boolean = false
|
||
) => {
|
||
return {
|
||
type: 'fieldSet',
|
||
title: label,
|
||
collapsable: true,
|
||
collapsed: collapsed,
|
||
controls: controls
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 控制一组控件的显隐
|
||
* @param visibleOn
|
||
* @param controls
|
||
*/
|
||
export const visibleOn = (visibleOn: string, controls: any[]) => {
|
||
return {
|
||
type: 'container',
|
||
visibleOn: visibleOn,
|
||
controls: controls
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 方便生成数字类的控件
|
||
* @param name
|
||
* @param label
|
||
* @param isInteger 是否是整数
|
||
*/
|
||
export const number = (
|
||
name: string,
|
||
label: string,
|
||
labelRemark?: string,
|
||
defaultNumber?: number,
|
||
min: number = -1e4,
|
||
max: number = 1e4,
|
||
step: number = 1
|
||
) => {
|
||
const control: any = {
|
||
type: 'number',
|
||
name: name,
|
||
label: label
|
||
};
|
||
if (labelRemark) {
|
||
control.labelRemark = {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
};
|
||
}
|
||
if (typeof defaultNumber !== 'undefined') {
|
||
control.pipeIn = (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return defaultNumber;
|
||
}
|
||
return value;
|
||
};
|
||
}
|
||
control.min = min;
|
||
control.max = max;
|
||
control.step = step;
|
||
return control;
|
||
};
|
||
|
||
/**
|
||
* 生成文本控件
|
||
* @param name
|
||
* @param label
|
||
* @param labelRemark
|
||
* @param defaultText
|
||
*/
|
||
export const text = (
|
||
name: string,
|
||
label: string,
|
||
labelRemark?: string,
|
||
defaultText?: string
|
||
) => {
|
||
const control: any = {
|
||
type: 'text',
|
||
name: name,
|
||
label: label
|
||
};
|
||
if (labelRemark) {
|
||
if (labelRemark) {
|
||
control.labelRemark = {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
};
|
||
}
|
||
}
|
||
if (typeof defaultText !== 'undefined') {
|
||
control.pipeIn = (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return defaultText;
|
||
}
|
||
return value;
|
||
};
|
||
}
|
||
return control;
|
||
};
|
||
|
||
/**
|
||
* 用于 left/top 等,支持三种类型,一种是关键字,比如 auto,一种是数字,另一种是百分比
|
||
* @param name
|
||
* @param label
|
||
* @param labelForRadio 切换按钮的文字
|
||
* @param keywordList 关键字列表
|
||
*/
|
||
export const viewportControl = (
|
||
name: string,
|
||
label: string,
|
||
labelForRadio: string,
|
||
keywordList: string[]
|
||
) => {
|
||
return [
|
||
{
|
||
type: 'button-group',
|
||
name: name,
|
||
label: labelForRadio,
|
||
options: [
|
||
{
|
||
label: '关键字',
|
||
value: keywordList[0]
|
||
},
|
||
{
|
||
label: '数字',
|
||
value: 2
|
||
},
|
||
{
|
||
label: '百分比',
|
||
value: 3
|
||
}
|
||
],
|
||
pipeIn: value => {
|
||
if (typeof value === 'undefined') {
|
||
return keywordList[0];
|
||
}
|
||
if (typeof value === 'string') {
|
||
if (value.indexOf('%') !== -1) {
|
||
return 3;
|
||
} else {
|
||
return keywordList[0];
|
||
}
|
||
} else if (typeof value === 'number') {
|
||
return 2;
|
||
} else {
|
||
return keywordList[0];
|
||
}
|
||
},
|
||
pipeOut: value => {
|
||
if (value === 1) {
|
||
return keywordList[0];
|
||
} else if (value === 2) {
|
||
return 0;
|
||
} else if (value === 3) {
|
||
return '0%';
|
||
} else {
|
||
return keywordList[0];
|
||
}
|
||
}
|
||
},
|
||
{
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
options: keywordList,
|
||
visibleOn: `(typeof this.${name} === "undefined") || ((typeof this.${name} === "string") && (this.${name}.indexOf("%") === -1))`
|
||
},
|
||
{
|
||
type: 'number',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `(typeof this.${name} === "number")`
|
||
},
|
||
{
|
||
type: 'text',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `(typeof this.${name} === "string") && (this.${name}.indexOf("%") !== -1)`
|
||
}
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 设置某个组件在容器中的距离
|
||
* @param label 前缀
|
||
* @param label 组件名
|
||
*/
|
||
export const viewport = (scope: string, label: string) => {
|
||
return {
|
||
type: 'fieldSet',
|
||
title: '离容器边距',
|
||
collapsable: true,
|
||
collapsed: true,
|
||
controls: [
|
||
...viewportControl(
|
||
`${scope}left`,
|
||
`${label}离容器左侧的距离`,
|
||
'左侧距离值类型',
|
||
['auto', 'left', 'center', 'right']
|
||
),
|
||
...viewportControl(
|
||
`${scope}top`,
|
||
`${label}离容器上侧的距离`,
|
||
'上侧距离值类型',
|
||
['auto', 'top', 'middle', 'bottom']
|
||
),
|
||
...viewportControl(
|
||
`${scope}right`,
|
||
`${label}离容器右侧的距离`,
|
||
'右侧距离值类型',
|
||
['auto']
|
||
),
|
||
...viewportControl(
|
||
`${scope}bottom`,
|
||
`${label}离容器下侧的距离`,
|
||
'下侧距离值类型',
|
||
['auto']
|
||
)
|
||
]
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 设置宽度的便捷方法
|
||
* @param label
|
||
*/
|
||
export const width = (label: string) => {
|
||
return keywordOrNumber('width', `${label}的宽度`, '宽度的单位', ['auto']);
|
||
};
|
||
|
||
/**
|
||
* 设置高度的便捷方法
|
||
* @param label
|
||
*/
|
||
export const height = (label: string) => {
|
||
return keywordOrNumber('height', `${label}的高度`, '高度的单位', ['auto']);
|
||
};
|
||
|
||
/**
|
||
* 设置 padding 的便捷方法
|
||
* @param label
|
||
*/
|
||
export const padding = (label: string) => {
|
||
return numberOrArray('padding', label + '内边距', '单独设置每个内边距');
|
||
};
|
||
|
||
/**
|
||
* 设置 orient 的便捷方法
|
||
* @param label
|
||
*/
|
||
export const origin = (label: string) => {
|
||
return select('origin', `${label}列表的布局朝向`, ['horizontal', 'vertical']);
|
||
};
|
||
|
||
/**
|
||
* formatter 的简便方法,不过这里不支持函数
|
||
* @param label
|
||
*/
|
||
export const formatter = (label: string) => {
|
||
return {
|
||
type: 'text',
|
||
name: 'formatter',
|
||
label: `格式化${label}文本`
|
||
};
|
||
};
|
||
|
||
/**
|
||
* selectedMode 的简便写法
|
||
* @param label
|
||
*/
|
||
export const selectedMode = (label: string) => {
|
||
return booleanOrKeyword('selectedMode', `${label}选择的模式`, [
|
||
{label: '单选', value: 'single'},
|
||
{label: '多选', value: 'multiple'}
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* 生成动画相关的控件
|
||
* @param scope
|
||
*/
|
||
export const animation = (scope?: string, collapsed: boolean = true) => {
|
||
let prefix = '';
|
||
if (scope) {
|
||
prefix = `${scope}.`;
|
||
}
|
||
const easing = [
|
||
'linear',
|
||
'quadraticIn',
|
||
'quadraticOut',
|
||
'quadraticInOut',
|
||
'cubicIn',
|
||
'cubicOut',
|
||
'cubicInOut',
|
||
'quarticIn',
|
||
'quarticOut',
|
||
'quarticInOut',
|
||
'quinticIn',
|
||
'quinticOut',
|
||
'quinticInOut',
|
||
'sinusoidalIn',
|
||
'sinusoidalOut',
|
||
'sinusoidalInOut',
|
||
'exponentialIn',
|
||
'exponentialOut',
|
||
'exponentialInOut',
|
||
'circularIn',
|
||
'circularOut',
|
||
'circularInOut',
|
||
'elasticIn',
|
||
'elasticOut',
|
||
'elasticInOut',
|
||
'backIn',
|
||
'backOut',
|
||
'backInOut',
|
||
'bounceIn',
|
||
'bounceOut',
|
||
'bounceInOut'
|
||
];
|
||
return fieldSet('动画', [
|
||
trueSwitch(`${prefix}animation`, '是否开启动画'),
|
||
visibleOn(`this.${prefix}animation`, [
|
||
number(
|
||
`${prefix}animationThreshold`,
|
||
'是否开启动画的阈值',
|
||
'当单个系列显示的图形数量大于这个阈值时会关闭动画',
|
||
2000
|
||
),
|
||
number(
|
||
`${prefix}animationDuration`,
|
||
'初始动画的时长',
|
||
'支持回调函数,可以通过每个数据返回不同的时长实现更戏剧的初始动画效果,不过只能写代码',
|
||
1000
|
||
),
|
||
select(`${prefix}animationEasing`, '初始动画的缓动效果', easing),
|
||
number(
|
||
`${prefix}animationDelay`,
|
||
'初始动画的延迟',
|
||
'初始动画的延迟,支持回调函数,可以通过每个数据返回不同的 delay 时间实现更戏剧的初始动画效果',
|
||
0
|
||
)
|
||
]),
|
||
number(
|
||
`${prefix}animationDurationUpdate`,
|
||
'数据更新动画的时长',
|
||
'支持回调函数,可以通过每个数据返回不同的时长实现更戏剧的更新动画效果',
|
||
200
|
||
),
|
||
select(`${prefix}animationEasingUpdate`, '数据更新动画的缓动效果', easing),
|
||
number(
|
||
`${prefix}animationDelayUpdate`,
|
||
'数据更新动画的延迟',
|
||
'支持回调函数,可以通过每个数据返回不同的 delay 时间实现更戏剧的更新动画效果'
|
||
)
|
||
]);
|
||
};
|
||
|
||
/**
|
||
* icon 的简版写法
|
||
*/
|
||
export const icon = (label: string) => {
|
||
return keywordOrString(
|
||
'icon',
|
||
`${label}的 icon`,
|
||
'切换类型为 url',
|
||
[
|
||
'circle',
|
||
'rect',
|
||
'roundRect',
|
||
'triangle',
|
||
'diamond',
|
||
'pin',
|
||
'arrow',
|
||
'none'
|
||
],
|
||
'image://http://',
|
||
`可以通过 'image://url' 设置为图片,其中 URL 为图片的链接,或者 dataURI。
|
||
|
||
URL 为图片链接例如:
|
||
|
||
'image://http://xxx.xxx.xxx/a/b.png'
|
||
URL 为 dataURI 例如:
|
||
|
||
'image://data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'
|
||
可以通过 'path://' 将图标设置为任意的矢量路径。这种方式相比于使用图片的方式,不用担心因为缩放而产生锯齿或模糊,而且可以设置为任意颜色。路径图形会自适应调整为合适的大小。路径的格式参见 SVG PathData。可以从 Adobe Illustrator 等工具编辑导出。`
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 用于生成类似 padding 那种可以是数字或数组的控件
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch 切换按钮的文字
|
||
* @param defaultNumber 默认数字
|
||
* @param defaultArray 默认数组
|
||
*/
|
||
export const numberOrArray = (
|
||
name: string,
|
||
label: string,
|
||
labelForSwitch: string,
|
||
defaultNumber: number = 5,
|
||
defaultArray = [0, 0, 0, 0]
|
||
) => {
|
||
return [
|
||
{
|
||
type: 'group',
|
||
controls: [
|
||
{
|
||
type: 'number',
|
||
name: name,
|
||
hiddenOn: `Array.isArray(this.${name})`,
|
||
label: label
|
||
},
|
||
{
|
||
type: 'switch',
|
||
name: name,
|
||
label: labelForSwitch,
|
||
pipeIn: (value: any) => {
|
||
return Array.isArray(value);
|
||
},
|
||
pipeOut: (value: any, oldValue: any) => {
|
||
return value ? defaultArray : defaultNumber;
|
||
}
|
||
}
|
||
]
|
||
},
|
||
|
||
{
|
||
type: 'array',
|
||
name: name,
|
||
label: label,
|
||
labelRemark:
|
||
'设置两个值将分别是上下、左右;设置四个值则分别是上、右、下、左',
|
||
visibleOn: `Array.isArray(this.${name})`,
|
||
minLength: 2,
|
||
maxLength: 4,
|
||
items: {
|
||
type: 'number'
|
||
}
|
||
}
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 用于生成类似 padding 那种可以是数字或数组的控件
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch 切换按钮的文字
|
||
* @param labelRemark remark
|
||
* @param defaultNumber 默认数字
|
||
* @param defaultArray 默认数组
|
||
*/
|
||
export const vector = (
|
||
name: string,
|
||
label: string,
|
||
labelForSwitch: string,
|
||
labelRemark?: string,
|
||
defaultNumber: number = 0,
|
||
defaultArray = [0, 0, 0, 0]
|
||
) => {
|
||
return [
|
||
{
|
||
type: 'array',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `Array.isArray(this.${name})`,
|
||
minLength: 2,
|
||
maxLength: 4,
|
||
items: {
|
||
type: 'number'
|
||
}
|
||
},
|
||
{
|
||
type: 'group',
|
||
controls: [
|
||
{
|
||
type: 'number',
|
||
name: name,
|
||
hiddenOn: `Array.isArray(this.${name})`,
|
||
label: label
|
||
},
|
||
{
|
||
type: 'switch',
|
||
name: name,
|
||
label: labelForSwitch,
|
||
pipeIn: (value: any) => {
|
||
return Array.isArray(value);
|
||
},
|
||
pipeOut: (value: any) => {
|
||
return value ? defaultArray : defaultNumber;
|
||
},
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined
|
||
}
|
||
]
|
||
}
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 阴影相关的控件
|
||
*/
|
||
export const shadowControls = (scope: string) => {
|
||
return {
|
||
type: 'fieldSet',
|
||
title: '阴影',
|
||
collapsable: true,
|
||
collapsed: true,
|
||
controls: [
|
||
color(`${scope}shadowColor`, '阴影颜色'),
|
||
number(`${scope}shadowBlur`, '阴影模糊大小'),
|
||
number(`${scope}shadowOffsetX`, '阴影水平方向上的偏移距离'),
|
||
number(`${scope}shadowOffsetY`, '阴影垂直方向上的偏移距离')
|
||
]
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 关键字或数字,比如 fontWeight,可以是 normal,或者 100
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch
|
||
* @param keywordList
|
||
*/
|
||
export const keywordOrNumber = (
|
||
name: string,
|
||
label: string,
|
||
labelForSwitch: string,
|
||
keywordList: string[],
|
||
defaultNumber: number = 100
|
||
) => {
|
||
return {
|
||
type: 'group',
|
||
controls: [
|
||
{
|
||
type: 'switch',
|
||
label: labelForSwitch,
|
||
name: name,
|
||
pipeIn: (value: any, data) => {
|
||
if (typeof data[name] === 'undefined') {
|
||
return false;
|
||
}
|
||
return typeof data[name] !== 'string';
|
||
},
|
||
pipeOut: (value: any, oldValue: any, data: any) => {
|
||
if (value) {
|
||
return defaultNumber;
|
||
} else {
|
||
return keywordList[0];
|
||
}
|
||
}
|
||
},
|
||
{
|
||
type: 'number',
|
||
name: name,
|
||
visibleOn: `typeof(this.${name}) === 'number'`,
|
||
label: label
|
||
},
|
||
{
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `typeof(this.${name}) === 'undefined' || typeof(this.${name}) === 'string'`,
|
||
options: keywordList
|
||
}
|
||
]
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 可以是关键字或者是字符串
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch
|
||
* @param keywordList
|
||
* @param remark
|
||
*/
|
||
export const keywordOrString = (
|
||
name: string,
|
||
label: string,
|
||
labelForSwitch: string,
|
||
keywordList: string[],
|
||
defaultString: string = '',
|
||
remark?: string
|
||
) => {
|
||
return {
|
||
type: 'group',
|
||
controls: [
|
||
{
|
||
type: 'switch',
|
||
label: labelForSwitch,
|
||
name: name,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return false;
|
||
}
|
||
return keywordList.indexOf(value) === -1;
|
||
},
|
||
pipeOut: (value: any, oldValue: any, data: any) => {
|
||
if (value) {
|
||
return defaultString;
|
||
} else {
|
||
return keywordList[0];
|
||
}
|
||
},
|
||
labelRemark: remark
|
||
},
|
||
{
|
||
type: 'text',
|
||
name: name,
|
||
visibleOn: `this.${name} && ${JSON.stringify(
|
||
keywordList
|
||
)}.indexOf(this.${name}) === -1`,
|
||
label: label
|
||
},
|
||
{
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `typeof(this.${name}) === 'undefined' || ${JSON.stringify(
|
||
keywordList
|
||
)}.indexOf(this.${name}) !== -1`,
|
||
options: keywordList
|
||
}
|
||
]
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 对象或者对象数组
|
||
* TODO: 这样会造成配置项 double,是否有更好的方法?
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch
|
||
* @param keywordList
|
||
* @param controls
|
||
*/
|
||
export const objectOrArray = (
|
||
name: string,
|
||
labelForSwitch: string,
|
||
controls: any[]
|
||
) => {
|
||
return [
|
||
{
|
||
type: 'switch',
|
||
label: labelForSwitch,
|
||
mode: 'inline',
|
||
name: name,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return false;
|
||
}
|
||
return Array.isArray(value);
|
||
},
|
||
pipeOut: (value: any, oldValue: any, data: any) => {
|
||
if (value) {
|
||
if (data[name]) {
|
||
return [data[name]];
|
||
}
|
||
return [{}];
|
||
} else {
|
||
if (data[name] && Array.isArray(data[name])) {
|
||
return data[name][0];
|
||
}
|
||
return {};
|
||
}
|
||
}
|
||
},
|
||
{
|
||
type: 'combo',
|
||
name: name,
|
||
label: '',
|
||
noBorder: true,
|
||
multiLine: true,
|
||
visibleOn: `typeof(this.${name}) === 'undefined' || !Array.isArray(this.${name})`,
|
||
multiple: false,
|
||
controls: controls
|
||
},
|
||
{
|
||
type: 'combo',
|
||
name: name,
|
||
label: '',
|
||
noBorder: true,
|
||
multiLine: true,
|
||
visibleOn: `Array.isArray(this.${name})`,
|
||
multiple: true,
|
||
controls: controls
|
||
}
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 常见样式设置
|
||
* @param label
|
||
*/
|
||
export const commonStyle = (scope: string, label: string) => {
|
||
return [
|
||
color(`${scope}backgroundColor`, `${label}背景色,默认透明`),
|
||
color(`${scope}borderColor`, `${label}的边框颜色`),
|
||
number(`${scope}borderWidth`, `${label}的边框线宽`),
|
||
...numberOrArray(
|
||
`${scope}borderRadius`,
|
||
'圆角半径',
|
||
'单独设置每个圆角半径'
|
||
),
|
||
shadowControls(scope)
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 布尔类型或关键字,优先布尔类型
|
||
* @param name
|
||
* @param label
|
||
* @param keywordList
|
||
*/
|
||
export const booleanOrKeyword = (
|
||
name: string,
|
||
label: string,
|
||
keywordList: any[],
|
||
defaultBoolean: boolean = true
|
||
) => {
|
||
return {
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return defaultBoolean ? 'true' : 'false';
|
||
}
|
||
return value;
|
||
},
|
||
pipeOut: (value: any) => {
|
||
if (value === 'true') {
|
||
return true;
|
||
} else if (value === 'false') {
|
||
return false;
|
||
} else {
|
||
return value;
|
||
}
|
||
},
|
||
options: [
|
||
{label: '开启', value: 'true'},
|
||
{label: '关闭', value: 'false'},
|
||
...keywordList
|
||
]
|
||
};
|
||
};
|
||
|
||
/**
|
||
* enum 辅助方法
|
||
* @param name
|
||
* @param label
|
||
* @param keywordList
|
||
*/
|
||
export const enumControl = (
|
||
name: string,
|
||
label: string,
|
||
keywordList: any[],
|
||
defaultValue: string,
|
||
labelRemark?: string
|
||
) => {
|
||
return {
|
||
type: 'select',
|
||
name: name,
|
||
label: label,
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
defaultValue;
|
||
}
|
||
if (value === true) {
|
||
return 'true';
|
||
}
|
||
if (value === false) {
|
||
return 'false';
|
||
}
|
||
return value;
|
||
},
|
||
pipeOut: (value: any) => {
|
||
if (value === 'true') {
|
||
return true;
|
||
} else if (value === 'false') {
|
||
return false;
|
||
} else {
|
||
return value;
|
||
}
|
||
},
|
||
options: keywordList
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 数字或百分比
|
||
* @param name
|
||
* @param label
|
||
* @param labelForSwitch
|
||
* @param keywordList
|
||
*/
|
||
export const numberOrPercentage = (
|
||
name: string,
|
||
label: string,
|
||
labelForSwitch: string,
|
||
labelRemark?: string,
|
||
defaultPercent: string = '1%',
|
||
defaultNumber: number = 100
|
||
) => {
|
||
return [
|
||
{
|
||
type: 'group',
|
||
controls: [
|
||
{
|
||
type: 'switch',
|
||
label: labelForSwitch,
|
||
name: name,
|
||
pipeIn: (value: any) => {
|
||
if (typeof value === 'undefined') {
|
||
return false;
|
||
}
|
||
return typeof value !== 'string';
|
||
},
|
||
pipeOut: (value: any) => {
|
||
if (value) {
|
||
return defaultNumber;
|
||
} else {
|
||
return defaultPercent;
|
||
}
|
||
},
|
||
labelRemark: labelRemark
|
||
? {
|
||
type: 'remark',
|
||
content: labelRemark
|
||
}
|
||
: undefined
|
||
},
|
||
{
|
||
type: 'number',
|
||
name: name,
|
||
visibleOn: `typeof(this.${name}) === 'number'`,
|
||
label: label
|
||
},
|
||
{
|
||
type: 'text',
|
||
name: name,
|
||
label: label,
|
||
visibleOn: `typeof(this.${name}) === 'undefined' || typeof(this.${name}) === 'string'`
|
||
}
|
||
]
|
||
}
|
||
];
|
||
};
|
||
|
||
/**
|
||
* 生成文本样式的控件
|
||
* @param scope
|
||
*/
|
||
export const textStyleControls = (scope: string, label: string) => {
|
||
return createHierarchy(scope, [
|
||
{
|
||
type: 'fieldSet',
|
||
title: label,
|
||
collapsable: true,
|
||
controls: [
|
||
color('color', `${label}文字的颜色`),
|
||
select('fontStyle', `${label}文字字体的风格`, [
|
||
'normal',
|
||
'italic',
|
||
'oblique'
|
||
]),
|
||
keywordOrNumber(
|
||
'fontWeight',
|
||
`${label}文字字体的粗细`,
|
||
'字体粗细格式使用数字',
|
||
['normal', 'bold', 'bolder', 'lighter']
|
||
),
|
||
{
|
||
type: 'text',
|
||
name: 'fontFamily',
|
||
label: `${label}文字的字体`,
|
||
options: [
|
||
'Arial',
|
||
'Bookman',
|
||
'Candara',
|
||
'Courier New',
|
||
'Courier',
|
||
'Garamond',
|
||
'Georgia',
|
||
'Helvetica',
|
||
'Impact',
|
||
'Microsoft YaHei',
|
||
'Monaco',
|
||
'monospace',
|
||
'Palatino',
|
||
'Roboto',
|
||
'sans-serif',
|
||
'serif',
|
||
'Times New Roman',
|
||
'Verdana'
|
||
]
|
||
},
|
||
number('fontSize', `${label}文字的字体大小`),
|
||
number('lineHeight', `${label}行高`),
|
||
// TODO:用处不大,要不别支持了
|
||
numberOrPercentage('width', '文字块宽度', '宽度使用数字'),
|
||
numberOrPercentage('height', '文字块高度', '高度使用数字'),
|
||
color('textBorderColor', '文字本身的描边颜色'),
|
||
number('textBorderWidth', '文字本身的描边宽度'),
|
||
color('textShadowColor', '文字本身的阴影颜色'),
|
||
number('textShadowBlur', '文字本身的阴影长度'),
|
||
number('textShadowOffsetX', '文字本身的阴影 X 偏移'),
|
||
number('textShadowOffsetY', '文字本身的阴影 Y 偏移')
|
||
// TODO: rich 暂时不支持
|
||
]
|
||
}
|
||
]);
|
||
};
|
||
|
||
// 对于一些没正确识别的 label 手动处理
|
||
const FIX_LABEL = {
|
||
symbolKeepAspect: '是否在缩放时保持该图形的长宽比',
|
||
pageIcons: '图例控制块的图标',
|
||
pageIconColor: '翻页按钮的颜色',
|
||
pageIconInactiveColor: '翻页按钮不激活时(即翻页到头时)的颜色',
|
||
pageIconSize: '翻页按钮的大小',
|
||
scrollDataIndex: '图例当前最左上显示项的 dataIndex',
|
||
pageButtonItemGap: '图例控制块中,按钮和页信息之间的间隔',
|
||
pageButtonGap: '图例控制块和图例项之间的间隔',
|
||
pageButtonPosition: '图例控制块的位置',
|
||
pageFormatter: '图例控制块中,页信息的显示格式',
|
||
appendToBody: '是否添加为 HTML 的 <body> 的子节点'
|
||
};
|
||
|
||
/**
|
||
* 构建某个基于文档的控件
|
||
* @param name
|
||
* @param option
|
||
*/
|
||
const buildOneOption = (scope: string, name: string, option: any) => {
|
||
if (name.indexOf('<') !== -1) {
|
||
return;
|
||
}
|
||
if (name.indexOf('.0') != -1) {
|
||
return;
|
||
}
|
||
if (name.indexOf('.1') != -1) {
|
||
return;
|
||
}
|
||
const desc = option.desc.trim();
|
||
const uiControl = option.uiControl;
|
||
if (!desc) {
|
||
console.warn('must have desc', name);
|
||
return false;
|
||
}
|
||
const descSplit = desc.split('。');
|
||
let label = descSplit[0]
|
||
.trim()
|
||
.replace('<p>', '')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/'/g, '')
|
||
.replace(/<\/?[^>]+(>|$)/g, '');
|
||
|
||
let remark =
|
||
descSplit.length > 1 ? descSplit[1].trim().replace('</p>', '') : '';
|
||
// 有些描述太长了,再通过逗号拆分一下
|
||
const labelSplitComma = label.split(',');
|
||
if (labelSplitComma.length > 1) {
|
||
label = labelSplitComma[0];
|
||
remark = labelSplitComma[1] + remark;
|
||
}
|
||
|
||
if (name in FIX_LABEL) {
|
||
label = FIX_LABEL[name];
|
||
}
|
||
|
||
remark = `「${name}」${remark}`;
|
||
|
||
name = scope + name;
|
||
if (!uiControl || !uiControl.type) {
|
||
// 这种可能只有 desc
|
||
return text(name, label, remark);
|
||
}
|
||
const uiControlType = uiControl.type;
|
||
if (uiControlType === 'boolean') {
|
||
if (uiControl.default) {
|
||
return trueSwitch(name, label, remark);
|
||
} else {
|
||
return falseSwitch(name, label, remark);
|
||
}
|
||
} else if (uiControlType === 'color') {
|
||
return color(name, label, uiControl.default, remark);
|
||
} else if (uiControlType === 'number' || uiControlType === 'angle') {
|
||
const defaultValue =
|
||
typeof uiControl.default === 'undefined' ? 0 : +uiControl.default;
|
||
const min = typeof uiControl.min === 'undefined' ? -1e4 : +uiControl.min;
|
||
const max = typeof uiControl.max === 'undefined' ? 1e4 : +uiControl.max;
|
||
const step = typeof uiControl.step === 'undefined' ? 1 : +uiControl.step;
|
||
return number(name, label, remark, defaultValue, min, max, step);
|
||
} else if (uiControlType === 'percent') {
|
||
return numberOrPercentage(name, label, '使用绝对值', remark);
|
||
} else if (uiControlType === 'enum') {
|
||
if (uiControl.options) {
|
||
return enumControl(
|
||
name,
|
||
label,
|
||
uiControl.options.split(',').map((item: string) => item.trim()),
|
||
uiControl.default,
|
||
remark
|
||
);
|
||
} else {
|
||
console.warn('enum do not have options, fallback to text', name);
|
||
return text(name, label, remark);
|
||
}
|
||
} else if (uiControlType === 'vector') {
|
||
return vector(name, label, '单独设置', remark);
|
||
} else if (uiControlType === 'percentvector') {
|
||
// TODO: 可能需要特殊处理
|
||
// return vector(name, label, '单独设置', remark);
|
||
} else if (uiControlType === 'text') {
|
||
// return text(name, label, remark, uiControl.default);
|
||
} else {
|
||
console.warn('unknow type', name, uiControlType);
|
||
}
|
||
};
|
||
|
||
export const buildGroupOptions = (
|
||
scope: string,
|
||
parentName: string,
|
||
options: any
|
||
) => {
|
||
let controls = [];
|
||
for (const name in options) {
|
||
if (name.startsWith(parentName + '.')) {
|
||
const control = buildOneOption(scope, name, options[name]);
|
||
if (control) {
|
||
if (Array.isArray(control)) {
|
||
controls = controls.concat(control);
|
||
} else {
|
||
controls.push(control);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return controls;
|
||
};
|
||
|
||
/**
|
||
* 基于 ECharts 文档的数据构建控件,对常见元素做分组
|
||
* @param scope 前缀,虽然可以用 combo,但加前缀可以方便 debug,空字符串就是没前缀
|
||
* @param options
|
||
*/
|
||
export const buildOptions = (scope: string, options: any) => {
|
||
const commonStyleKeys = new Set([
|
||
'backgroundColor',
|
||
'borderColor',
|
||
'borderWidth',
|
||
'borderRadius'
|
||
]);
|
||
|
||
const viewportKeys = new Set(['left', 'top', 'right', 'bottom']);
|
||
|
||
// 没啥用的
|
||
const uselessKeys = new Set(['id', 'z', 'zlevel']);
|
||
|
||
let controls = [];
|
||
|
||
// 有些属性有深层结构,对它们进行特殊处理,使用 fieldSet 来自动折叠
|
||
const groupKeys = new Set();
|
||
|
||
for (const name in options) {
|
||
if (name.indexOf('.') !== -1) {
|
||
groupKeys.add(name.split('.')[0]);
|
||
}
|
||
}
|
||
|
||
for (const name in options) {
|
||
// if (!name.startsWith('label.padding')) {
|
||
// continue; // 用于开发时单独测试某个属性
|
||
// }
|
||
// 这些样式单独处理或忽略
|
||
if (
|
||
commonStyleKeys.has(name) ||
|
||
viewportKeys.has(name) ||
|
||
uselessKeys.has(name)
|
||
) {
|
||
continue;
|
||
}
|
||
if (
|
||
name.startsWith('textStyle') ||
|
||
name.startsWith('subtextStyle') ||
|
||
name.startsWith('pageTextStyle') ||
|
||
name.startsWith('animation')
|
||
) {
|
||
continue;
|
||
}
|
||
// TODO: 后续单独处理
|
||
if (name === 'data' || name === 'tooltip') {
|
||
continue;
|
||
}
|
||
|
||
if (groupKeys.has(name)) {
|
||
controls.push(
|
||
fieldSet(name, buildGroupOptions(scope, name, options), true)
|
||
);
|
||
}
|
||
|
||
if (name.indexOf('.') !== -1) {
|
||
// 前面已经处理了
|
||
continue;
|
||
}
|
||
|
||
const control = buildOneOption(scope, name, options[name]);
|
||
|
||
if (control) {
|
||
if (Array.isArray(control)) {
|
||
controls = controls.concat(control);
|
||
} else {
|
||
controls.push(control);
|
||
}
|
||
} else {
|
||
console.warn('build control error', name);
|
||
}
|
||
}
|
||
|
||
return controls;
|
||
};
|