mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 10:59:42 +08:00
commit
a390db8634
@ -114,6 +114,7 @@
|
||||
"amis-core": "*",
|
||||
"amis-formula": "*",
|
||||
"amis-ui": "*",
|
||||
"amis-theme-editor-helper": "*",
|
||||
"i18n-runtime": "*",
|
||||
"react": ">=16.8.6",
|
||||
"react-dom": ">=16.8.6"
|
||||
|
@ -48,7 +48,7 @@
|
||||
@import './style-control/background';
|
||||
@import './style-control/size';
|
||||
@import './style-control/style-common';
|
||||
@import './style-control/theme_classname';
|
||||
@import './style-control/theme-css-code';
|
||||
|
||||
@keyframes growing {
|
||||
0% {
|
||||
|
@ -0,0 +1,85 @@
|
||||
.ThemeCssCode {
|
||||
position: relative;
|
||||
&-button {
|
||||
width: 100%;
|
||||
}
|
||||
.is-group {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.icon-theme-css {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.ThemeCssCode-editor {
|
||||
height: auto;
|
||||
width: px2rem(460px);
|
||||
padding: px2rem(16px);
|
||||
position: relative;
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
margin-bottom: px2rem(16px);
|
||||
}
|
||||
&-close {
|
||||
position: absolute;
|
||||
top: px2rem(14px);
|
||||
right: px2rem(16px);
|
||||
button {
|
||||
height: px2rem(16px);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
&-content {
|
||||
height: px2rem(500px);
|
||||
width: 100%;
|
||||
&-header {
|
||||
display: flex;
|
||||
margin-bottom: px2rem(10px);
|
||||
border-bottom: 1px solid #e8e9eb;
|
||||
|
||||
&-title {
|
||||
margin-right: px2rem(30px);
|
||||
cursor: pointer;
|
||||
padding-bottom: px2rem(10px);
|
||||
}
|
||||
&-title--active {
|
||||
color: #2468f2;
|
||||
border-bottom: 2px solid #2468f2;
|
||||
}
|
||||
}
|
||||
&-main {
|
||||
overflow: auto;
|
||||
height: px2rem(460px);
|
||||
}
|
||||
&-body--hidden {
|
||||
display: none;
|
||||
}
|
||||
&-body {
|
||||
margin-bottom: px2rem(10px);
|
||||
&-title {
|
||||
margin-bottom: px2rem(10px);
|
||||
margin-left: px2rem(16px);
|
||||
font-size: 12px;
|
||||
}
|
||||
&-editor {
|
||||
height: 200px;
|
||||
margin-bottom: px2rem(10px);
|
||||
border-bottom: 1px solid #e8e9eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.editorPanel-inner {
|
||||
.Theme-FontEditor {
|
||||
.Theme-Wrapper-header-left {
|
||||
font-weight: 400;
|
||||
color: #5c5f66;
|
||||
}
|
||||
}
|
||||
.Theme-ShadowEditor {
|
||||
.Theme-Wrapper-header-left {
|
||||
font-weight: 400;
|
||||
color: #5c5f66;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
.ThemeClassName {
|
||||
position: relative;
|
||||
&-button {
|
||||
position: absolute;
|
||||
left: px2rem(-38px);
|
||||
}
|
||||
.is-group {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.ThemeClassName-editor {
|
||||
height: auto;
|
||||
width: px2rem(400px);
|
||||
padding: px2rem(16px);
|
||||
position: relative;
|
||||
&-title {
|
||||
font-size: 14px;
|
||||
margin-bottom: px2rem(16px);
|
||||
}
|
||||
&-close {
|
||||
position: absolute;
|
||||
top: px2rem(14px);
|
||||
right: px2rem(16px);
|
||||
button {
|
||||
height: px2rem(16px);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
&-content {
|
||||
height: px2rem(500px);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.editorPanel-inner {
|
||||
.Theme-FontEditor {
|
||||
.Theme-Wrapper-header-left {
|
||||
font-weight: 400;
|
||||
color: #5c5f66;
|
||||
}
|
||||
}
|
||||
.Theme-ShadowEditor {
|
||||
.Theme-Wrapper-header-left {
|
||||
font-weight: 400;
|
||||
color: #5c5f66;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import {
|
||||
needDefaultWidth,
|
||||
guid,
|
||||
addStyleClassName,
|
||||
setThemeDefaultData,
|
||||
appTranslate
|
||||
} from '../../src/util';
|
||||
import {
|
||||
@ -528,7 +529,8 @@ export const MainStore = types
|
||||
|
||||
getValueOf(id: string) {
|
||||
const schema = JSONGetById(self.schema, id);
|
||||
const res = JSONPipeOut(schema, false);
|
||||
const data = JSONPipeOut(schema, false);
|
||||
const res = setThemeDefaultData(data);
|
||||
return res;
|
||||
},
|
||||
|
||||
@ -546,7 +548,8 @@ export const MainStore = types
|
||||
key !== '$$commonSchema') ||
|
||||
typeof props === 'function' || // pipeIn 和 pipeOut
|
||||
key.substring(0, 2) === '__' ||
|
||||
key === 'css' ||
|
||||
key === 'themeCss' ||
|
||||
key === 'editorPath' ||
|
||||
key === 'editorState') // 样式不需要出现做json中,
|
||||
);
|
||||
},
|
||||
|
@ -9,6 +9,8 @@ import isPlainObject from 'lodash/isPlainObject';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import type {Schema} from 'amis';
|
||||
import {SchemaObject} from 'amis/lib/Schema';
|
||||
import {assign, cloneDeep} from 'lodash';
|
||||
import {getGlobalData} from 'amis-theme-editor-helper';
|
||||
|
||||
const {
|
||||
guid,
|
||||
@ -39,6 +41,8 @@ export {
|
||||
createObject
|
||||
};
|
||||
|
||||
export let themeConfig: any = {};
|
||||
|
||||
export function __uri(id: string) {
|
||||
return id;
|
||||
}
|
||||
@ -198,15 +202,16 @@ export function JSONPipeOut(
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果存在css属性,则给对应的className加上name
|
||||
* 如果存在themeCss属性,则给对应的className加上name
|
||||
*/
|
||||
export function addStyleClassName(obj: Schema) {
|
||||
const css = obj.css;
|
||||
if (!obj.css) {
|
||||
const themeCss = obj.themeCss || obj.css;
|
||||
// page暂时不做处理
|
||||
if (!themeCss || obj.type === 'page') {
|
||||
return obj;
|
||||
}
|
||||
let toUpdate: any = {};
|
||||
Object.keys(css).forEach(key => {
|
||||
Object.keys(themeCss).forEach(key => {
|
||||
if (key !== '$$id') {
|
||||
let classname = `${key}-${obj.id.replace('u:', '')}`;
|
||||
if (!obj[key]) {
|
||||
@ -695,18 +700,23 @@ export function filterSchemaForEditor(schema: any): any {
|
||||
mapped[key] = filtered;
|
||||
|
||||
// 组件切换状态修改classname
|
||||
if (/[C|c]lassName/.test(key) && schema.editorState) {
|
||||
mapped[key] = mapped[key]
|
||||
? mapped[key] + ' ' + schema.editorState
|
||||
: schema.editorState;
|
||||
modified = true;
|
||||
}
|
||||
// TODO:切换状态暂时先不改变组件的样式
|
||||
// if (/[C|c]lassName/.test(key) && schema.editorState) {
|
||||
// mapped[key] = mapped[key]
|
||||
// ? mapped[key] + ' ' + schema.editorState
|
||||
// : schema.editorState;
|
||||
// modified = true;
|
||||
// }
|
||||
|
||||
if (filtered !== value) {
|
||||
modified = true;
|
||||
}
|
||||
});
|
||||
return modified ? mapped : schema;
|
||||
const finalSchema = modified ? mapped : schema;
|
||||
if (finalSchema?.type) {
|
||||
return setThemeDefaultData(finalSchema);
|
||||
}
|
||||
return finalSchema;
|
||||
}
|
||||
|
||||
return schema;
|
||||
@ -1083,3 +1093,15 @@ export function needFillPlaceholder(curProps: any) {
|
||||
}
|
||||
return needFillPlaceholder;
|
||||
}
|
||||
// 设置主题数据
|
||||
export function setThemeConfig(config: any) {
|
||||
themeConfig = config;
|
||||
}
|
||||
|
||||
// 将主题数据传入组件的schema
|
||||
export function setThemeDefaultData(data: any) {
|
||||
const schemaData = cloneDeep(data);
|
||||
schemaData.themeConfig = themeConfig;
|
||||
assign(schemaData, getGlobalData(themeConfig));
|
||||
return schemaData;
|
||||
}
|
||||
|
@ -175,6 +175,9 @@ import jFlexEnd from './display/jFlexEnd.svg';
|
||||
import jSpaceBetween from './display/jSpaceBetween.svg';
|
||||
import jSpaceAround from './display/jSpaceAround.svg';
|
||||
|
||||
// 主题
|
||||
import themeCss from './theme/css.svg';
|
||||
|
||||
// 功能类组件 icon x 11
|
||||
registerIcon('audio-plugin', audio);
|
||||
registerIcon('custom-plugin', custom);
|
||||
@ -336,4 +339,7 @@ registerIcon('jFlexEnd', jFlexEnd);
|
||||
registerIcon('jSpaceBetween', jSpaceBetween);
|
||||
registerIcon('jSpaceAround', jSpaceAround);
|
||||
|
||||
// 主题
|
||||
registerIcon('theme-css', themeCss);
|
||||
|
||||
export {Icon};
|
||||
|
1
packages/amis-editor/src/icons/theme/css.svg
Normal file
1
packages/amis-editor/src/icons/theme/css.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2778" ><path d="M173.320441 64.959477l0 17.833157 0 855.984379 0 17.833157 17.833157 0 641.98854 0 17.833157 0 0-17.833157L850.975295 332.454788l0-7.245008-5.015224-5.572926L596.296895 69.975724l-5.572926-5.015224-7.245008 0L191.153598 64.960501 173.320441 64.960501zM208.986755 100.625792l356.660072 0L565.646827 332.454788l0 17.833157 17.833157 0 231.828996 0 0 570.655911L208.986755 920.943856 208.986755 100.625792zM601.312118 126.260635l188.360995 188.360995L601.312118 314.621631 601.312118 126.260635zM367.287107 467.88527c23.945365 0 42.979884 11.665691 54.952566 24.866341l-14.429641 16.270569c-10.744715-11.358699-23.638373-18.726503-40.215934-18.726503-38.375006 0-63.85533 31.314193-63.85533 81.353866 0 50.653657 24.252357 82.581834 62.627363 82.581834 19.033495 0 33.155121-7.982812 46.048779-22.410406l14.429641 15.656585c-15.656585 18.419512-35.612079 29.164227-61.092403 29.164227-50.653657 0-88.413656-38.680974-88.413656-104.378256C277.337469 507.48722 315.712475 467.88527 367.287107 467.88527zM456.316793 629.058043c14.429641 15.042601 34.384112 25.174356 55.56655 25.174356 26.402323 0 42.058908-13.20065 42.058908-32.849152 0-20.569478-14.735609-27.015284-33.769105-35.612079l-28.858258-12.586666c-18.726503-7.982812-40.829917-22.410406-40.829917-51.882648 0-30.699186 27.015284-53.416584 63.548338-53.416584 23.945365 0 45.128827 10.130731 59.250452 24.866341l-13.507642 16.57756c-12.279674-11.665691-27.322275-19.033495-45.74281-19.033495-22.717398 0-37.759999 11.358699-37.759999 29.471219 0 19.340487 17.805528 26.708292 33.463136 33.463136l28.857235 12.279674c23.331381 10.129708 41.443901 23.945365 41.443901 53.723575 0 31.928177-26.4013 57.408501-68.766177 57.408501-28.244274 0-52.803623-11.664667-70.302159-29.77821L456.316793 629.058043zM614.416578 629.058043c14.429641 15.042601 34.384112 25.174356 55.56655 25.174356 26.402323 0 42.058908-13.20065 42.058908-32.849152 0-20.569478-14.735609-27.015284-33.769105-35.612079l-28.858258-12.586666c-18.726503-7.982812-40.829917-22.410406-40.829917-51.882648 0-30.699186 27.015284-53.416584 63.548338-53.416584 23.945365 0 45.128827 10.130731 59.250452 24.866341l-13.507642 16.57756c-12.279674-11.665691-27.322275-19.033495-45.74281-19.033495-22.717398 0-37.759999 11.358699-37.759999 29.471219 0 19.340487 17.805528 26.708292 33.463136 33.463136l28.857235 12.279674c23.331381 10.129708 41.443901 23.945365 41.443901 53.723575 0 31.928177-26.4013 57.408501-68.766177 57.408501-28.244274 0-52.803623-11.664667-70.302159-29.77821L614.416578 629.058043z" fill="currentColor"></path></svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -178,7 +178,7 @@ import './renderer/FeatureControl';
|
||||
import './renderer/event-control/index';
|
||||
import './renderer/TreeOptionControl';
|
||||
import './renderer/TransferTableControl';
|
||||
import './renderer/style-control/ThemeClassName';
|
||||
import './renderer/style-control/ThemeCssCode';
|
||||
import './renderer/ButtonGroupControl';
|
||||
import './renderer/FlexSettingControl';
|
||||
import 'amis-theme-editor/lib/renderers/Border';
|
||||
|
@ -116,28 +116,35 @@ export class ButtonPlugin extends BasePlugin {
|
||||
return [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: `css.className.font:${state}`,
|
||||
visibleOn: visibleOn
|
||||
name: `themeCss.className.font:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: [
|
||||
`button1.type.\${level}.${state}.body.font-color`,
|
||||
`button1.size.\${size}.body.font`
|
||||
]
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
label: '背景',
|
||||
name: `css.className.background:${state}`,
|
||||
name: `themeCss.className.background:${state}`,
|
||||
labelMode: 'input',
|
||||
needGradient: true,
|
||||
visibleOn: visibleOn
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `button1.type.\${level}.${state}.body.bg-color`
|
||||
}),
|
||||
getSchemaTpl('theme:border', {
|
||||
name: `css.className.border:${state}`,
|
||||
visibleOn: visibleOn
|
||||
name: `themeCss.className.border:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `button1.type.\${level}.${state}.body.border`
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: `css.className.padding-and-margin:${state}`,
|
||||
|
||||
visibleOn: visibleOn
|
||||
name: `themeCss.className.padding-and-margin:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `button1.size.\${size}.body.padding-and-margin`
|
||||
}),
|
||||
getSchemaTpl('theme:radius', {
|
||||
name: `css.className.radius:${state}`,
|
||||
visibleOn: visibleOn
|
||||
name: `themeCss.className.radius:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `button1.size.\${size}.body.border`
|
||||
})
|
||||
];
|
||||
};
|
||||
@ -358,7 +365,14 @@ export class ButtonPlugin extends BasePlugin {
|
||||
...buttonStateFunc("${editorState == 'active'}", 'active')
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:classNames', {isFormItem: false})
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: [
|
||||
{
|
||||
value: '',
|
||||
state: ['default', 'hover', 'active']
|
||||
}
|
||||
]
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
|
@ -235,16 +235,7 @@ export class ContainerPlugin extends LayoutBasePlugin {
|
||||
title: '外观',
|
||||
className: 'p-none',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('style:common', ['layout']),
|
||||
getSchemaTpl('style:classNames', {
|
||||
isFormItem: false,
|
||||
schema: [
|
||||
getSchemaTpl('className', {
|
||||
name: 'bodyClassName',
|
||||
label: '内容区'
|
||||
})
|
||||
]
|
||||
})
|
||||
...getSchemaTpl('theme:common', ['layout'])
|
||||
])
|
||||
}
|
||||
]);
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import {defaultValue, getSchemaTpl, tipedLabel} from 'amis-editor-core';
|
||||
import {ValidatorTag} from '../../validator';
|
||||
import {getEventControlConfig} from '../../renderer/event-control/helper';
|
||||
import {inputStateTpl} from '../../renderer/style-control/helper';
|
||||
|
||||
export class NumberControlPlugin extends BasePlugin {
|
||||
// 关联渲染器名字
|
||||
@ -272,7 +273,28 @@ export class NumberControlPlugin extends BasePlugin {
|
||||
}
|
||||
]
|
||||
}),
|
||||
getSchemaTpl('style:classNames')
|
||||
getSchemaTpl('theme:form-label'),
|
||||
getSchemaTpl('theme:form-description'),
|
||||
{
|
||||
title: '数字输入框样式',
|
||||
body: [
|
||||
...inputStateTpl(
|
||||
'themeCss.inputControlClassName',
|
||||
'inputNumber.base.base'
|
||||
)
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: [
|
||||
{
|
||||
name: '数字输入框',
|
||||
value: '',
|
||||
className: 'inputControlClassName',
|
||||
state: ['default', 'hover', 'active']
|
||||
}
|
||||
],
|
||||
isFormItem: true
|
||||
})
|
||||
],
|
||||
{...context?.schema, configTitle: 'style'}
|
||||
)
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
import {defaultValue, getSchemaTpl, tipedLabel} from 'amis-editor-core';
|
||||
import {ValidatorTag} from '../../validator';
|
||||
import {getEventControlConfig} from '../../renderer/event-control/helper';
|
||||
import {inputStateTpl} from '../../renderer/style-control/helper';
|
||||
|
||||
const isText = 'data.type === "input-text"';
|
||||
const isPassword = 'data.type === "input-password"';
|
||||
@ -154,36 +155,6 @@ export class TextControlPlugin extends BasePlugin {
|
||||
panelBodyCreator = (context: BaseEventContext) => {
|
||||
const renderer: any = context.info.renderer;
|
||||
|
||||
const inputStateFunc = (visibleOn: string, state: string) => {
|
||||
return [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: `css.inputControlClassName.font:${state}`,
|
||||
visibleOn: visibleOn
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
label: '背景',
|
||||
name: `css.inputControlClassName.background:${state}`,
|
||||
labelMode: 'input',
|
||||
needGradient: true,
|
||||
visibleOn: visibleOn
|
||||
}),
|
||||
getSchemaTpl('theme:border', {
|
||||
name: `css.inputControlClassName.border:${state}`,
|
||||
visibleOn: visibleOn
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: `css.inputControlClassName.padding-and-margin:${state}`,
|
||||
|
||||
visibleOn: visibleOn
|
||||
}),
|
||||
getSchemaTpl('theme:radius', {
|
||||
name: `css.inputControlClassName.radius:${state}`,
|
||||
visibleOn: visibleOn
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
return getSchemaTpl('tabs', [
|
||||
{
|
||||
title: '属性',
|
||||
@ -389,32 +360,10 @@ export class TextControlPlugin extends BasePlugin {
|
||||
{
|
||||
title: '输入框样式',
|
||||
body: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'editorState',
|
||||
label: '状态',
|
||||
selectFirst: true,
|
||||
options: [
|
||||
{
|
||||
label: '常规',
|
||||
value: 'default'
|
||||
},
|
||||
{
|
||||
label: '悬浮',
|
||||
value: 'hover'
|
||||
},
|
||||
{
|
||||
label: '点击',
|
||||
value: 'active'
|
||||
}
|
||||
]
|
||||
},
|
||||
...inputStateFunc(
|
||||
"${editorState == 'default' || !editorState}",
|
||||
'default'
|
||||
),
|
||||
...inputStateFunc("${editorState == 'hover'}", 'hover'),
|
||||
...inputStateFunc("${editorState == 'active'}", 'active')
|
||||
...inputStateTpl(
|
||||
'themeCss.inputControlClassName',
|
||||
'input.base.default'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -423,28 +372,28 @@ export class TextControlPlugin extends BasePlugin {
|
||||
body: [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: 'css.addOnClassName.font'
|
||||
name: 'themeCss.addOnClassName.font:default'
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: 'css.addOnClassName.padding-and-margin'
|
||||
name: 'themeCss.addOnClassName.padding-and-margin:default'
|
||||
})
|
||||
]
|
||||
},
|
||||
getSchemaTpl('theme:classNames', {
|
||||
schema: [
|
||||
getSchemaTpl('theme:cssCode', {
|
||||
themeClass: [
|
||||
{
|
||||
type: 'theme-classname',
|
||||
label: '输入框',
|
||||
name: 'inputControlClassName'
|
||||
name: '输入框',
|
||||
value: '',
|
||||
className: 'inputControlClassName',
|
||||
state: ['default', 'hover', 'active']
|
||||
},
|
||||
{
|
||||
type: 'theme-classname',
|
||||
name: 'addOnClassName',
|
||||
suffix: 'addOn',
|
||||
label: 'AddOn',
|
||||
visibleOn: 'this.addOn && this.addOn.type === "text"'
|
||||
name: 'addOn',
|
||||
value: 'addOn',
|
||||
className: 'addOnClassName'
|
||||
}
|
||||
]
|
||||
],
|
||||
isFormItem: true
|
||||
})
|
||||
],
|
||||
{...context?.schema, configTitle: 'style'}
|
||||
|
@ -76,10 +76,7 @@ export class IFramePlugin extends BasePlugin {
|
||||
})
|
||||
]
|
||||
},
|
||||
getSchemaTpl('style:classNames', {
|
||||
isFormItem: false
|
||||
}),
|
||||
...getSchemaTpl('style:common', [], 'border')
|
||||
...getSchemaTpl('theme:common', ['layout'])
|
||||
])
|
||||
]
|
||||
}
|
||||
|
@ -210,13 +210,7 @@ export class FlexPluginBase extends LayoutBasePlugin {
|
||||
{
|
||||
title: '外观',
|
||||
className: 'p-none',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('style:common', []),
|
||||
{
|
||||
title: 'CSS 类名',
|
||||
body: [getSchemaTpl('className', {label: '外层CSS类名'})]
|
||||
}
|
||||
])
|
||||
body: getSchemaTpl('collapseGroup', [...getSchemaTpl('theme:common')])
|
||||
}
|
||||
])
|
||||
];
|
||||
|
@ -275,30 +275,7 @@ export class PagePlugin extends BasePlugin {
|
||||
className: 'p-none',
|
||||
body: [
|
||||
getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('theme:common', ['layout']),
|
||||
getSchemaTpl('style:classNames', {
|
||||
isFormItem: false,
|
||||
schema: [
|
||||
getSchemaTpl('className', {
|
||||
name: 'headerClassName',
|
||||
label: '顶部'
|
||||
}),
|
||||
getSchemaTpl('className', {
|
||||
name: 'bodyClassName',
|
||||
label: '内容区'
|
||||
}),
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'asideClassName',
|
||||
label: '边栏'
|
||||
}),
|
||||
|
||||
getSchemaTpl('className', {
|
||||
name: 'toolbarClassName',
|
||||
label: '工具栏'
|
||||
})
|
||||
]
|
||||
})
|
||||
...getSchemaTpl('theme:common', ['layout'])
|
||||
])
|
||||
]
|
||||
},
|
||||
|
@ -105,7 +105,29 @@ export class StepsPlugin extends BasePlugin {
|
||||
},
|
||||
{
|
||||
title: '外观',
|
||||
body: [getSchemaTpl('className')]
|
||||
body: [
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'select',
|
||||
label: '模式',
|
||||
value: 'horizontal',
|
||||
options: [
|
||||
{
|
||||
label: '水平',
|
||||
value: 'horizontal'
|
||||
},
|
||||
{
|
||||
label: '竖直',
|
||||
value: 'vertical'
|
||||
},
|
||||
{
|
||||
label: '简单',
|
||||
value: 'simple'
|
||||
}
|
||||
]
|
||||
},
|
||||
getSchemaTpl('className')
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '显隐',
|
||||
|
@ -245,13 +245,7 @@ export class TableViewPlugin extends BasePlugin {
|
||||
{
|
||||
title: '外观',
|
||||
className: 'p-none',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('style:common'),
|
||||
{
|
||||
title: 'CSS 类名',
|
||||
body: [getSchemaTpl('className')]
|
||||
}
|
||||
])
|
||||
body: getSchemaTpl('collapseGroup', [...getSchemaTpl('theme:common')])
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
@ -446,7 +440,7 @@ export class TableViewPlugin extends BasePlugin {
|
||||
{
|
||||
title: '外观',
|
||||
className: 'p-none',
|
||||
body: getSchemaTpl('collapseGroup', getSchemaTpl('style:common'))
|
||||
body: getSchemaTpl('collapseGroup', getSchemaTpl('theme:common'))
|
||||
}
|
||||
])
|
||||
];
|
||||
@ -476,7 +470,7 @@ export class TableViewPlugin extends BasePlugin {
|
||||
{
|
||||
title: '外观',
|
||||
className: 'p-none',
|
||||
body: getSchemaTpl('collapseGroup', getSchemaTpl('style:common'))
|
||||
body: getSchemaTpl('collapseGroup', getSchemaTpl('theme:common'))
|
||||
}
|
||||
])
|
||||
];
|
||||
|
@ -219,10 +219,7 @@ export class TplPlugin extends BasePlugin {
|
||||
{
|
||||
title: '外观',
|
||||
body: getSchemaTpl('collapseGroup', [
|
||||
...getSchemaTpl('style:common', ['layout']),
|
||||
getSchemaTpl('style:classNames', {
|
||||
isFormItem: false
|
||||
})
|
||||
...getSchemaTpl('theme:common', ['layout'], ['font'])
|
||||
])
|
||||
},
|
||||
{
|
||||
|
@ -409,7 +409,7 @@ export default class FormulaControl extends React.Component<
|
||||
'kilobitSeparator',
|
||||
'value',
|
||||
'inputControlClassName',
|
||||
'css'
|
||||
'themeCss'
|
||||
];
|
||||
|
||||
// 当前组件要剔除的字段
|
||||
|
@ -1,237 +0,0 @@
|
||||
/**
|
||||
* 类名输入框 + 自定义样式源码编辑器
|
||||
*/
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Button, Editor, Icon, Overlay, PopOver} from 'amis-ui';
|
||||
import {FormControlProps, FormItem, render} from 'amis-core';
|
||||
import {parse as cssParse} from 'amis-postcss';
|
||||
import {PlainObject} from './types';
|
||||
import {debounce} from 'lodash';
|
||||
|
||||
const valueMap: PlainObject = {
|
||||
'margin-top': 'marginTop',
|
||||
'margin-right': 'marginRight',
|
||||
'margin-bottom': 'marginBottom',
|
||||
'margin-left': 'marginLeft',
|
||||
'padding-top': 'paddingTop',
|
||||
'padding-right': 'paddingRight',
|
||||
'padding-bottom': 'paddingBottom',
|
||||
'padding-left': 'paddingLeft',
|
||||
'border-top-width': 'top-border-width',
|
||||
'border-right-width': 'right-border-width',
|
||||
'border-bottom-width': 'bottom-border-width',
|
||||
'border-left-width': 'left-border-width',
|
||||
'border-top-style': 'top-border-style',
|
||||
'border-right-style': 'right-border-style',
|
||||
'border-bottom-style': 'bottom-border-style',
|
||||
'border-left-style': 'left-border-style',
|
||||
'border-top-color': 'top-border-color',
|
||||
'border-right-color': 'right-border-color',
|
||||
'border-bottom-color': 'bottom-border-color',
|
||||
'border-left-color': 'left-border-color',
|
||||
'font-size': 'fontSize',
|
||||
'font-weight': 'fontWeight',
|
||||
'line-height': 'lineHeight'
|
||||
};
|
||||
|
||||
const fontStyle = [
|
||||
'color',
|
||||
'font-weight',
|
||||
'font-size',
|
||||
'font-style',
|
||||
'text-decoration',
|
||||
'text-align',
|
||||
'vertical-align',
|
||||
'font-family',
|
||||
'line-height'
|
||||
];
|
||||
|
||||
function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
const {name, value: classname, suffix} = props;
|
||||
const [value, setValue] = useState('');
|
||||
function getCssAndSetValue(
|
||||
classname?: string,
|
||||
name?: string,
|
||||
suffix?: string
|
||||
) {
|
||||
try {
|
||||
const id =
|
||||
classname?.replace(name + '-', '') + (suffix ? '-' + suffix : '');
|
||||
const dom = document.getElementById(id || '') || null;
|
||||
const content = dom?.innerHTML || '';
|
||||
const ast = cssParse(content);
|
||||
const nodes: any[] = [];
|
||||
ast.nodes.forEach((node: any) => {
|
||||
const selector = node.selector;
|
||||
if (!selector.endsWith('.hover') && !selector.endsWith('.active')) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
ast.nodes = nodes;
|
||||
|
||||
const css = nodes
|
||||
.map(node => {
|
||||
const style = node.nodes.map((n: any) => `${n.prop}: ${n.value};`);
|
||||
return `${node.selector} {\n ${style.join('\n ')}\n}`;
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
setValue(css);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCssAndSetValue(classname, name, suffix);
|
||||
}, []);
|
||||
|
||||
function handleChange(value: string) {
|
||||
setValue(value);
|
||||
editorChange(value);
|
||||
}
|
||||
|
||||
const editorChange = debounce((value: string) => {
|
||||
try {
|
||||
const ast = cssParse(value);
|
||||
const {data, onBulkChange, name} = props;
|
||||
const sourceCss = data.css || {};
|
||||
const className: PlainObject = {};
|
||||
ast.nodes.forEach((node: any) => {
|
||||
const nodes = node.nodes;
|
||||
const selector = node.selector;
|
||||
let state = 'default';
|
||||
if (!!~selector.indexOf(':hover:active')) {
|
||||
state = 'active';
|
||||
} else if (!!~selector.indexOf(':hover')) {
|
||||
state = 'hover';
|
||||
}
|
||||
nodes.forEach((item: any) => {
|
||||
const prop = item.prop;
|
||||
const cssValue = item.value;
|
||||
if (!!~prop.indexOf('radius')) {
|
||||
const type = 'radius:' + state;
|
||||
!className[type] && (className[type] = {});
|
||||
const radius = cssValue.split(' ');
|
||||
|
||||
className[type]['top-left-border-radius'] = radius[0];
|
||||
className[type]['top-right-border-radius'] = radius[1];
|
||||
className[type]['bottom-right-border-radius'] = radius[2];
|
||||
className[type]['bottom-left-border-radius'] = radius[3];
|
||||
} else if (!!~prop.indexOf('border')) {
|
||||
!className['border:' + state] &&
|
||||
(className['border:' + state] = {});
|
||||
className['border:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else if (!!~prop.indexOf('padding') || !!~prop.indexOf('margin')) {
|
||||
!className['padding-and-margin:' + state] &&
|
||||
(className['padding-and-margin:' + state] = {});
|
||||
className['padding-and-margin:' + state][valueMap[prop] || prop] =
|
||||
cssValue;
|
||||
} else if (fontStyle.includes(prop)) {
|
||||
!className['font:' + state] && (className['font:' + state] = {});
|
||||
className['font:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else {
|
||||
className[(valueMap[prop] || prop) + ':' + state] = cssValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
const newCss = {
|
||||
...sourceCss,
|
||||
[name!]: className
|
||||
};
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
css: newCss
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="ThemeClassName-editor">
|
||||
<div className="ThemeClassName-editor-title">自定义样式源码</div>
|
||||
<div className="ThemeClassName-editor-close">
|
||||
<Button onClick={props.onHide} level="link">
|
||||
<Icon icon="close" className="icon" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="ThemeClassName-editor-content">
|
||||
<Editor
|
||||
value={value}
|
||||
language="css"
|
||||
onChange={handleChange}
|
||||
options={{
|
||||
automaticLayout: true,
|
||||
lineNumbers: 'off',
|
||||
glyphMargin: false,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
lineDecorationsWidth: 0,
|
||||
lineNumbersMinChars: 0,
|
||||
selectOnLineNumbers: true,
|
||||
scrollBeyondLastLine: false,
|
||||
folding: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemeClassName(props: FormControlProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const {value} = props;
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
function handleShowEditor() {
|
||||
setShowEditor(true);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div ref={ref} className="ThemeClassName">
|
||||
<Button
|
||||
onClick={handleShowEditor}
|
||||
level="link"
|
||||
className=":ThemeClassName-button"
|
||||
>
|
||||
<Icon icon="file" className="icon" />
|
||||
</Button>
|
||||
{render({
|
||||
type: 'input-tag',
|
||||
name: 'class',
|
||||
placeholder: '请输入类名',
|
||||
delimiter: ' ',
|
||||
value: value,
|
||||
onChange: (value: string) => {
|
||||
props.onChange && props.onChange(value);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<Overlay
|
||||
container={document.body}
|
||||
placement="left"
|
||||
target={ref.current}
|
||||
show={showEditor}
|
||||
rootClose={false}
|
||||
>
|
||||
<PopOver overlay onHide={() => setShowEditor(false)}>
|
||||
<AmisStyleCodeEditor {...props} onHide={() => setShowEditor(false)} />
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@FormItem({
|
||||
type: 'theme-classname',
|
||||
strictMode: false
|
||||
})
|
||||
export class BorderRenderer extends React.Component<FormControlProps> {
|
||||
render() {
|
||||
return <ThemeClassName {...this.props} />;
|
||||
}
|
||||
}
|
396
packages/amis-editor/src/renderer/style-control/ThemeCssCode.tsx
Normal file
396
packages/amis-editor/src/renderer/style-control/ThemeCssCode.tsx
Normal file
@ -0,0 +1,396 @@
|
||||
/**
|
||||
* 类名输入框 + 自定义样式源码编辑器
|
||||
*/
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Button, Editor, Overlay, PopOver} from 'amis-ui';
|
||||
import {FormControlProps, FormItem, styleMap} from 'amis-core';
|
||||
import {parse as cssParse} from 'amis-postcss';
|
||||
import {PlainObject} from './types';
|
||||
import {debounce, isEmpty} from 'lodash';
|
||||
import {Icon} from '../../icons/index';
|
||||
import editorFactory from './themeLanguage';
|
||||
import cx from 'classnames';
|
||||
|
||||
const valueMap: PlainObject = {};
|
||||
|
||||
for (let key in styleMap) {
|
||||
valueMap[styleMap[key]] = key;
|
||||
}
|
||||
const fontStyle = [
|
||||
'color',
|
||||
'font-weight',
|
||||
'font-size',
|
||||
'font-style',
|
||||
'text-decoration',
|
||||
'text-align',
|
||||
'vertical-align',
|
||||
'font-family',
|
||||
'line-height'
|
||||
];
|
||||
|
||||
interface CssNode {
|
||||
value: string;
|
||||
selector: string;
|
||||
}
|
||||
|
||||
interface CssNodeTab {
|
||||
name: string;
|
||||
children: CssNode[];
|
||||
}
|
||||
|
||||
function AmisThemeCssCodeEditor(props: FormControlProps) {
|
||||
const {themeClass, data} = props;
|
||||
const id = data.id.replace('u:', '');
|
||||
const [cssNodes, setCssNodes] = useState<CssNodeTab[]>([]);
|
||||
const [tabId, setTabId] = useState(0);
|
||||
function getCssAndSetValue(themeClass: any[]) {
|
||||
try {
|
||||
const newCssNodes: CssNodeTab[] = [];
|
||||
themeClass?.forEach(n => {
|
||||
const classId = n.value ? id + '-' + n.value : id;
|
||||
const state = n.state || ['default'];
|
||||
const className = n.className || 'className';
|
||||
const dom = document.getElementById(classId || '') || null;
|
||||
const content = dom?.innerHTML || '';
|
||||
const ast = cssParse(content);
|
||||
const nodes: any[] = [];
|
||||
ast.nodes.forEach((node: any) => {
|
||||
const selector = node.selector;
|
||||
if (!selector.endsWith('.hover') && !selector.endsWith('.active')) {
|
||||
nodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
const css: {selector: string; value: string; state: string}[] = [];
|
||||
state.forEach((s: string) => {
|
||||
css.push({
|
||||
selector: `.${className}-${id}${s === 'default' ? '' : ':' + s}`,
|
||||
state: s,
|
||||
value: ''
|
||||
});
|
||||
});
|
||||
nodes.forEach(node => {
|
||||
const style = node.nodes.map((n: any) => `${n.prop}: ${n.value};`);
|
||||
const item = css.find(c => {
|
||||
if (
|
||||
c.selector === node.selector ||
|
||||
node.selector.endsWith(`:${c.state}`)
|
||||
) {
|
||||
return c;
|
||||
}
|
||||
return false;
|
||||
})!;
|
||||
item.value = style.join('\n');
|
||||
});
|
||||
|
||||
newCssNodes.push({
|
||||
name: n.name || '自定义样式',
|
||||
children: css
|
||||
});
|
||||
});
|
||||
setCssNodes(newCssNodes);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCssAndSetValue(themeClass);
|
||||
}, []);
|
||||
|
||||
const editorChange = debounce((nodeTabs: CssNodeTab[]) => {
|
||||
try {
|
||||
const {data, onBulkChange} = props;
|
||||
const sourceCss = data.themeCss || data.css || {};
|
||||
const newCss: any = {};
|
||||
nodeTabs.forEach(tab => {
|
||||
tab.children.forEach(node => {
|
||||
const nodes = cssParse(node.value)
|
||||
.nodes.map(node => {
|
||||
const {prop, value} = node;
|
||||
return {
|
||||
prop,
|
||||
value
|
||||
};
|
||||
})
|
||||
.filter(n => n.value);
|
||||
const selector = node.selector;
|
||||
const nameEtr = /\.(.*)\-/.exec(selector);
|
||||
const cssCode: PlainObject = {};
|
||||
let name = nameEtr ? nameEtr[1] : '';
|
||||
let state = 'default';
|
||||
if (!!~selector.indexOf(':active')) {
|
||||
state = 'active';
|
||||
} else if (!!~selector.indexOf(':hover')) {
|
||||
state = 'hover';
|
||||
}
|
||||
nodes.forEach(item => {
|
||||
const prop = item.prop;
|
||||
const cssValue = item.value;
|
||||
if (!!~prop.indexOf('radius')) {
|
||||
const type = 'radius:' + state;
|
||||
!cssCode[type] && (cssCode[type] = {});
|
||||
const radius = cssValue.split(' ');
|
||||
|
||||
cssCode[type]['top-left-border-radius'] = radius[0];
|
||||
cssCode[type]['top-right-border-radius'] = radius[1];
|
||||
cssCode[type]['bottom-right-border-radius'] = radius[2];
|
||||
cssCode[type]['bottom-left-border-radius'] = radius[3];
|
||||
} else if (!!~prop.indexOf('border')) {
|
||||
!cssCode['border:' + state] && (cssCode['border:' + state] = {});
|
||||
cssCode['border:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else if (
|
||||
!!~prop.indexOf('padding') ||
|
||||
!!~prop.indexOf('margin')
|
||||
) {
|
||||
!cssCode['padding-and-margin:' + state] &&
|
||||
(cssCode['padding-and-margin:' + state] = {});
|
||||
cssCode['padding-and-margin:' + state][valueMap[prop] || prop] =
|
||||
cssValue;
|
||||
} else if (fontStyle.includes(prop)) {
|
||||
!cssCode['font:' + state] && (cssCode['font:' + state] = {});
|
||||
cssCode['font:' + state][valueMap[prop] || prop] = cssValue;
|
||||
} else {
|
||||
cssCode[(valueMap[prop] || prop) + ':' + state] = cssValue;
|
||||
}
|
||||
});
|
||||
if (newCss[name]) {
|
||||
newCss[name] = Object.assign(newCss[name], cssCode);
|
||||
} else {
|
||||
newCss[name] = cssCode;
|
||||
}
|
||||
});
|
||||
});
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
themeCss: {
|
||||
...sourceCss,
|
||||
...newCss
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
function handleChange(value: string, i: number, j: number) {
|
||||
const newCssNodes = cssNodes;
|
||||
newCssNodes[i].children[j].value = value;
|
||||
setCssNodes(newCssNodes); // 好像不需要这个?
|
||||
editorChange(newCssNodes);
|
||||
}
|
||||
function formateTitle(title: string) {
|
||||
if (title.endsWith('hover')) {
|
||||
return '悬浮态样式';
|
||||
} else if (title.endsWith('active')) {
|
||||
return '点击态样式';
|
||||
} else if (title.endsWith('disabled')) {
|
||||
return '禁用态样式';
|
||||
}
|
||||
return '常规态样式';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ThemeCssCode-editor">
|
||||
<div className="ThemeCssCode-editor-title">编辑样式源码</div>
|
||||
<div className="ThemeCssCode-editor-close">
|
||||
<Button onClick={props.onHide} level="link">
|
||||
<Icon icon="close" className="icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content">
|
||||
<div className="ThemeCssCode-editor-content-header">
|
||||
{cssNodes.map((node, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => setTabId(index)}
|
||||
className={cx(
|
||||
'ThemeCssCode-editor-content-header-title',
|
||||
index === tabId &&
|
||||
'ThemeCssCode-editor-content-header-title--active'
|
||||
)}
|
||||
>
|
||||
{node.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content-main">
|
||||
{cssNodes.map((node, i) => {
|
||||
const children = node.children;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={cx(
|
||||
i !== tabId && 'ThemeCssCode-editor-content-body--hidden'
|
||||
)}
|
||||
>
|
||||
{children.map((css, j) => {
|
||||
return (
|
||||
<div
|
||||
className="ThemeCssCode-editor-content-body"
|
||||
key={`${i}-${j}-${css.selector}`}
|
||||
id={`${i}-${j}-${css.selector}`}
|
||||
>
|
||||
{children.length > 1 ? (
|
||||
<div className="ThemeCssCode-editor-content-body-title">
|
||||
{formateTitle(css.selector)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="ThemeCssCode-editor-content-body-editor">
|
||||
<Editor
|
||||
value={css.value}
|
||||
editorFactory={editorFactory}
|
||||
options={{
|
||||
onChange: (value: string) =>
|
||||
handleChange(value, i, j)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AmisStyleCodeEditor(props: FormControlProps) {
|
||||
const {data, onBulkChange} = props;
|
||||
const {style} = data;
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
function getCssAndSetValue(data: any) {
|
||||
if (isEmpty(data)) {
|
||||
return '';
|
||||
}
|
||||
let str = '';
|
||||
for (let key in data) {
|
||||
if (key === 'radius') {
|
||||
str += `border-radius: ${
|
||||
data.radius['top-left-border-radius'] +
|
||||
' ' +
|
||||
data.radius['top-right-border-radius'] +
|
||||
' ' +
|
||||
data.radius['bottom-right-border-radius'] +
|
||||
' ' +
|
||||
data.radius['bottom-left-border-radius']
|
||||
};\n`;
|
||||
} else {
|
||||
str += `${styleMap[key] || key}: ${data[key]};\n`;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const res = getCssAndSetValue(style);
|
||||
setValue(res);
|
||||
}, []);
|
||||
|
||||
const editorChange = debounce((value: string) => {
|
||||
const newStyle: PlainObject = {};
|
||||
try {
|
||||
const style = cssParse(value);
|
||||
style.nodes.forEach(node => {
|
||||
const {prop, value} = node;
|
||||
if (value) {
|
||||
if (prop === 'border-radius') {
|
||||
const radius = value.split(' ');
|
||||
newStyle['radius'] = {
|
||||
'top-left-border-radius': radius[0] || '',
|
||||
'top-right-border-radius': radius[1] || '',
|
||||
'bottom-right-border-radius': radius[2] || '',
|
||||
'bottom-left-border-radius': radius[3] || ''
|
||||
};
|
||||
} else {
|
||||
newStyle[valueMap[prop] || prop] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
style: newStyle
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
function handleChange(value: string) {
|
||||
editorChange(value);
|
||||
setValue(value);
|
||||
}
|
||||
return (
|
||||
<div className="ThemeCssCode-editor">
|
||||
<div className="ThemeCssCode-editor-title">编辑样式源码</div>
|
||||
<div className="ThemeCssCode-editor-close">
|
||||
<Button onClick={props.onHide} level="link">
|
||||
<Icon icon="close" className="icon" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ThemeCssCode-editor-content">
|
||||
<Editor
|
||||
value={value}
|
||||
editorFactory={editorFactory}
|
||||
options={{
|
||||
onChange: handleChange
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemeCssCode(props: FormControlProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
function handleShowEditor() {
|
||||
setShowEditor(true);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div ref={ref} className="ThemeCssCode">
|
||||
<Button onClick={handleShowEditor} className=":ThemeCssCode-button">
|
||||
<Icon icon="theme-css" className="icon" /> 编辑样式源码
|
||||
</Button>
|
||||
</div>
|
||||
<Overlay
|
||||
container={document.body}
|
||||
placement="left"
|
||||
target={ref.current}
|
||||
show={showEditor}
|
||||
rootClose={false}
|
||||
>
|
||||
<PopOver overlay onHide={() => setShowEditor(false)}>
|
||||
{props.isLayout ? (
|
||||
<AmisStyleCodeEditor
|
||||
{...props}
|
||||
onHide={() => setShowEditor(false)}
|
||||
/>
|
||||
) : (
|
||||
<AmisThemeCssCodeEditor
|
||||
{...props}
|
||||
onHide={() => setShowEditor(false)}
|
||||
/>
|
||||
)}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@FormItem({
|
||||
type: 'theme-cssCode',
|
||||
strictMode: false
|
||||
})
|
||||
export class ThemeCssCodeRenderer extends React.Component<FormControlProps> {
|
||||
render() {
|
||||
return <ThemeCssCode {...this.props} />;
|
||||
}
|
||||
}
|
85
packages/amis-editor/src/renderer/style-control/helper.tsx
Normal file
85
packages/amis-editor/src/renderer/style-control/helper.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import {getSchemaTpl} from 'amis-editor-core';
|
||||
|
||||
export const inputStateTpl = (className: string, path: string = '') => {
|
||||
return [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'editorState',
|
||||
label: '状态',
|
||||
selectFirst: true,
|
||||
options: [
|
||||
{
|
||||
label: '常规',
|
||||
value: 'default'
|
||||
},
|
||||
{
|
||||
label: '悬浮',
|
||||
value: 'hover'
|
||||
},
|
||||
{
|
||||
label: '点击',
|
||||
value: 'active'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'editorPath',
|
||||
value: path
|
||||
},
|
||||
...inputStateFunc(
|
||||
"${editorState == 'default' || !editorState}",
|
||||
'default',
|
||||
className,
|
||||
path
|
||||
),
|
||||
...inputStateFunc("${editorState == 'hover'}", 'hover', className, path),
|
||||
...inputStateFunc("${editorState == 'active'}", 'active', className, path)
|
||||
];
|
||||
};
|
||||
|
||||
export const inputStateFunc = (
|
||||
visibleOn: string,
|
||||
state: string,
|
||||
className: string,
|
||||
path: string,
|
||||
options: any = []
|
||||
) => {
|
||||
return [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: `${className}.font:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `${path}.${state}.body.font`,
|
||||
state
|
||||
}),
|
||||
getSchemaTpl('theme:colorPicker', {
|
||||
label: '背景',
|
||||
name: `${className}.background:${state}`,
|
||||
labelMode: 'input',
|
||||
needGradient: true,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `${path}.${state}.body.bg-color`,
|
||||
state
|
||||
}),
|
||||
getSchemaTpl('theme:border', {
|
||||
name: `${className}.border:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `${path}.${state}.body.border`,
|
||||
state
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: `${className}.padding-and-margin:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `${path}.${state}.body.padding-and-margin`,
|
||||
state
|
||||
}),
|
||||
getSchemaTpl('theme:radius', {
|
||||
name: `${className}.radius:${state}`,
|
||||
visibleOn: visibleOn,
|
||||
editorThemePath: `${path}.${state}.body.border`,
|
||||
state
|
||||
}),
|
||||
...options
|
||||
];
|
||||
};
|
3150
packages/amis-editor/src/renderer/style-control/themeLanguage.ts
Normal file
3150
packages/amis-editor/src/renderer/style-control/themeLanguage.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -427,39 +427,36 @@ export const styleTpl = {
|
||||
|
||||
// css类名
|
||||
setSchemaTpl(
|
||||
'theme:classNames',
|
||||
(config: {schema: any; isFormItem: boolean; unsupportStatic?: boolean}) => {
|
||||
const {
|
||||
isFormItem = true,
|
||||
unsupportStatic = false,
|
||||
schema = []
|
||||
} = config || {};
|
||||
return {
|
||||
title: 'CSS 类名',
|
||||
body: (isFormItem
|
||||
? [
|
||||
'theme:cssCode',
|
||||
({
|
||||
themeClass = [],
|
||||
isFormItem,
|
||||
isLayout
|
||||
}: {
|
||||
themeClass?: any[];
|
||||
isFormItem?: boolean;
|
||||
} = {}) => {
|
||||
if (isFormItem) {
|
||||
themeClass.push(
|
||||
...[
|
||||
{
|
||||
type: 'theme-classname',
|
||||
label: 'Label',
|
||||
suffix: 'label',
|
||||
name: 'labelClassName'
|
||||
name: 'description',
|
||||
value: 'description',
|
||||
className: 'descriptionClassName'
|
||||
},
|
||||
{name: 'label', value: 'label', className: 'labelClassName'}
|
||||
]
|
||||
);
|
||||
}
|
||||
return {
|
||||
title: '样式源码',
|
||||
body: [
|
||||
{
|
||||
type: 'theme-classname',
|
||||
label: '描述',
|
||||
suffix: 'description',
|
||||
name: 'descriptionClassName',
|
||||
visibleOn: 'this.description'
|
||||
type: 'theme-cssCode',
|
||||
label: false,
|
||||
themeClass
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: 'theme-classname',
|
||||
label: '外层',
|
||||
name: 'className'
|
||||
}
|
||||
]
|
||||
).concat(schema)
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -471,10 +468,11 @@ setSchemaTpl('theme:form-label', () => {
|
||||
body: [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: 'css.labelClassName.font'
|
||||
name: 'themeCss.labelClassName.font:default',
|
||||
editorThemePath: 'form.item.default.label.body.font'
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: 'css.labelClassName.padding-and-margin'
|
||||
name: 'themeCss.labelClassName.padding-and-margin:default'
|
||||
})
|
||||
]
|
||||
};
|
||||
@ -488,10 +486,11 @@ setSchemaTpl('theme:form-description', () => {
|
||||
body: [
|
||||
getSchemaTpl('theme:font', {
|
||||
label: '文字',
|
||||
name: 'css.descriptionClassName.font'
|
||||
name: 'themeCss.descriptionClassName.font:default',
|
||||
editorThemePath: 'form.item.default.description.body.font'
|
||||
}),
|
||||
getSchemaTpl('theme:paddingAndMargin', {
|
||||
name: 'css.descriptionClassName.padding-and-margin'
|
||||
name: 'themeCss.descriptionClassName.padding-and-margin:default'
|
||||
})
|
||||
]
|
||||
};
|
||||
@ -503,7 +502,7 @@ setSchemaTpl('theme:font', (option: any = {}) => {
|
||||
mode: 'default',
|
||||
type: 'amis-theme-font-editor',
|
||||
label: '文字',
|
||||
name: `css.className.font`,
|
||||
name: `themeCss.className.font:default`,
|
||||
needColorCustom: true,
|
||||
...option
|
||||
};
|
||||
@ -515,7 +514,7 @@ setSchemaTpl('theme:colorPicker', (option: any = {}) => {
|
||||
mode: 'default',
|
||||
type: 'amis-theme-color-picker',
|
||||
label: '颜色',
|
||||
name: `css.className.color`,
|
||||
name: `themeCss.className.color:default`,
|
||||
needCustom: true,
|
||||
...option
|
||||
};
|
||||
@ -527,7 +526,7 @@ setSchemaTpl('theme:border', (option: any = {}) => {
|
||||
mode: 'default',
|
||||
type: 'amis-theme-border',
|
||||
label: '边框',
|
||||
name: `css.className.border`,
|
||||
name: `themeCss.className.border:default`,
|
||||
needColorCustom: true,
|
||||
...option
|
||||
};
|
||||
@ -539,7 +538,7 @@ setSchemaTpl('theme:paddingAndMargin', (option: any = {}) => {
|
||||
mode: 'default',
|
||||
type: 'amis-theme-padding-and-margin',
|
||||
label: '边距',
|
||||
name: `css.className.padding-and-margin`,
|
||||
name: `themeCss.className.padding-and-margin:default`,
|
||||
...option
|
||||
};
|
||||
});
|
||||
@ -550,7 +549,7 @@ setSchemaTpl('theme:radius', (option: any = {}) => {
|
||||
mode: 'default',
|
||||
type: 'amis-theme-radius',
|
||||
label: '圆角',
|
||||
name: `css.className.radius`,
|
||||
name: `themeCss.className.radius:default`,
|
||||
...option
|
||||
};
|
||||
});
|
||||
@ -560,7 +559,7 @@ setSchemaTpl('theme:shadow', (option: any = {}) => {
|
||||
return {
|
||||
type: 'amis-theme-shadow-editor',
|
||||
label: false,
|
||||
name: `css.className.boxShadow`,
|
||||
name: `themeCss.className.boxShadow:default`,
|
||||
hasSenior: true,
|
||||
...option
|
||||
};
|
||||
@ -568,31 +567,19 @@ setSchemaTpl('theme:shadow', (option: any = {}) => {
|
||||
|
||||
setSchemaTpl(
|
||||
'theme:common',
|
||||
(exclude: string[] | string, include: string[] | string) => {
|
||||
(exclude: string[] | string, include: string[]) => {
|
||||
// key统一转换成Kebab case,eg: boxShadow => bos-shadow
|
||||
exclude = (
|
||||
exclude ? (Array.isArray(exclude) ? exclude : [exclude]) : []
|
||||
).map((key: string) => kebabCase(key));
|
||||
|
||||
include = (
|
||||
include ? (Array.isArray(include) ? include : [include]) : []
|
||||
).map((key: string) => kebabCase(key));
|
||||
return [
|
||||
{
|
||||
header: '布局',
|
||||
key: 'layout',
|
||||
body: [
|
||||
{
|
||||
type: 'style-display',
|
||||
label: false,
|
||||
const moreStyle =
|
||||
include?.map(key =>
|
||||
getSchemaTpl(`theme:${key}`, {
|
||||
name: 'style'
|
||||
}
|
||||
].filter(comp => !~exclude.indexOf(comp.type.replace(/^style-/i, '')))
|
||||
},
|
||||
{
|
||||
header: '样式',
|
||||
key: 'style',
|
||||
body: [
|
||||
})
|
||||
) || [];
|
||||
const styles = moreStyle.concat([
|
||||
getSchemaTpl('theme:border', {
|
||||
name: 'style'
|
||||
}),
|
||||
@ -612,30 +599,33 @@ setSchemaTpl(
|
||||
getSchemaTpl('theme:shadow', {
|
||||
name: 'style.boxShadow'
|
||||
})
|
||||
]);
|
||||
return [
|
||||
{
|
||||
header: '布局',
|
||||
key: 'layout',
|
||||
body: [
|
||||
{
|
||||
type: 'style-display',
|
||||
label: false,
|
||||
name: 'style'
|
||||
}
|
||||
].filter(comp => !~exclude.indexOf(comp.type.replace(/^style-/i, '')))
|
||||
},
|
||||
{
|
||||
title: '自定义样式',
|
||||
body: styles
|
||||
},
|
||||
{
|
||||
title: '样式源码',
|
||||
body: [
|
||||
{
|
||||
type: 'theme-cssCode',
|
||||
label: false,
|
||||
isLayout: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
header: '圆角',
|
||||
key: 'radius',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '间距',
|
||||
key: 'box-model',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '背景',
|
||||
key: 'background',
|
||||
body: []
|
||||
},
|
||||
{
|
||||
header: '阴影',
|
||||
key: 'box-shadow',
|
||||
body: []
|
||||
}
|
||||
].filter(item =>
|
||||
include.length ? ~include.indexOf(item.key) : !~exclude.indexOf(item.key)
|
||||
);
|
||||
].filter(item => !~exclude.indexOf(item.key || ''));
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user