Commit 6905ba0b authored by 田翔's avatar 田翔

统一表单配置信息

parent 3b2245b9
...@@ -3518,7 +3518,7 @@ const Test = (props) => { ...@@ -3518,7 +3518,7 @@ const Test = (props) => {
<div style={{ width: '100%' }}> <div style={{ width: '100%' }}>
<Tabs> <Tabs>
<Tabs.TabPane tab="编辑" key="item-1"> <Tabs.TabPane tab="编辑" key="item-1">
<FormDesigner setSchema={changeSchema} /> <FormDesigner setSchema={changeSchema} tableName='' />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab="渲染" key="item-2"> <Tabs.TabPane tab="渲染" key="item-2">
<FormRender schema={schema} /> <FormRender schema={schema} />
......
...@@ -73,23 +73,17 @@ module.exports = { ...@@ -73,23 +73,17 @@ module.exports = {
options: { options: {
modules: { modules: {
getLocalIdent: (context, _, localName) => { getLocalIdent: (context, _, localName) => {
if (
context.resourcePath.includes('node_modules') ||
context.resourcePath.includes('ant.design.pro.less') ||
context.resourcePath.includes('main.less')
) {
return localName;
}
const match = context.resourcePath.match(/src(.*)/); const match = context.resourcePath.match(/src(.*)/);
const matchAntd = /^ant/.test(localName); const matchAntd = /^ant/.test(localName);
if (match && match[1] && !matchAntd) { if (context.resourcePath.includes('wisdom-map')) {
const antdProPath = match[1].replace('.less', ''); if (match && match[1] && !matchAntd) {
const arr = slash(antdProPath) const antdProPath = match[1].replace('.less', '');
.split('/') const arr = slash(antdProPath)
.map(a => a.replace(/([A-Z])/g, '-$1')) .split('/')
.map(a => a.toLowerCase()); .map(a => a.replace(/([A-Z])/g, '-$1'))
return `parse-form-${arr.join('-')}-${localName}`.replace(/--/g, '-'); .map(a => a.toLowerCase());
return `pandaXform-${arr.join('-')}-${localName}`.replace(/--/g, '-');
}
} }
return localName; return localName;
}, },
......
{ {
"name": "panda-xform", "name": "panda-xform",
"version": "1.3.0", "version": "1.4.0",
"description": "1.3.0: 支持附件各种形态", "description": "1.4.0: 统一表单配置信息",
"keywords": [ "keywords": [
"panda-xform" "panda-xform"
], ],
......
const baseSettings = { const baseSettings = {
title: { // title: {
title: '标题', // title: '标题',
type: 'string', // type: 'string',
// widget: 'htmlInput', // // widget: 'htmlInput',
displayType: 'row', // displayType: 'row',
labelWidth: 80, // labelWidth: 80,
}, // },
} }
const commonSettings = { const commonSettings = {
...@@ -29,8 +29,10 @@ const commonSettings = { ...@@ -29,8 +29,10 @@ const commonSettings = {
placeholder: { placeholder: {
title: '提示语', title: '提示语',
type: 'string', type: 'string',
widget: 'Placeholder',
displayType: 'row', displayType: 'row',
labelWidth: 80, labelWidth: 80,
dependencies: ['title']
}, },
// description: { // description: {
// title: '说明', // title: '说明',
...@@ -41,7 +43,6 @@ const commonSettings = { ...@@ -41,7 +43,6 @@ const commonSettings = {
type: 'string', type: 'string',
displayType: 'row', displayType: 'row',
labelWidth: 80, labelWidth: 80,
// default: ''
}, },
} }
...@@ -49,36 +50,39 @@ const switchSettings = { ...@@ -49,36 +50,39 @@ const switchSettings = {
disabled: { disabled: {
title: '只读', title: '只读',
type: 'boolean', type: 'boolean',
widget: 'BooleanSwitch', widget: 'checkbox',
displayType: 'row', // displayType: 'row',
labelWidth: 80, // labelWidth: 80,
default: false default: false,
width: '33%',
}, },
required: { required: {
title: '必填', title: '必填',
type: 'boolean', type: 'boolean',
widget: 'BooleanSwitch', widget: 'checkbox',
displayType: 'row', // displayType: 'row',
labelWidth: 80, // labelWidth: 80,
default: false default: false,
width: '33%',
}, },
hidden: { hidden: {
title: '隐藏', title: '隐藏',
type: 'boolean', type: 'boolean',
widget: 'BooleanSwitch', widget: 'checkbox',
default: false, default: false,
displayType: 'row', width: '33%',
labelWidth: 80, // displayType: 'row',
// labelWidth: 80,
}, },
} }
const elementSettings = { const elementSettings = {
width: { // width: {
title: '元素宽度', // title: '元素宽度',
type: 'string', // type: 'string',
widget: 'percentSlider', // widget: 'percentSlider',
// default: '33%', // // default: '33%',
}, // },
labelWidth: { labelWidth: {
title: '标签宽度', title: '标签宽度',
description: '默认值110', description: '默认值110',
...@@ -95,12 +99,46 @@ const elementSettings = { ...@@ -95,12 +99,46 @@ const elementSettings = {
const globalSettings = { const globalSettings = {
type: 'object', type: 'object',
properties: { properties: {
groupBase: {
title: '基础信息',
type: 'object',
properties: {},
},
tableName: {
title: '表名',
type: 'string',
widget: 'select',
required: true,
displayType: 'row',
labelWidth: 70,
default: '我是表名',
},
tableAlias: {
title: '别名',
type: 'string',
widget: 'htmlInput',
displayType: 'row',
labelWidth: 70,
},
interfaceText: {
title: '接口',
type: 'string',
widget: 'htmlInput',
displayType: 'row',
labelWidth: 70,
default: '我是接口'
},
groupStyle: {
title: '表单样式',
type: 'object',
properties: {},
},
column: { column: {
title: '整体布局', title: '整体布局',
type: 'number', type: 'number',
enum: [1], enum: [1, 2, 3],
default: 1, default: 1,
enumNames: ['一行一列'], enumNames: ['一行一列', '一行两列', '一行三列'],
widget: 'select', widget: 'select',
props: { props: {
placeholder: '默认一行一列', placeholder: '默认一行一列',
...@@ -129,6 +167,8 @@ const globalSettings = { ...@@ -129,6 +167,8 @@ const globalSettings = {
}, },
}, },
}, },
} }
export { export {
......
...@@ -6,282 +6,634 @@ const allSetting = { ...@@ -6,282 +6,634 @@ const allSetting = {
...elementSettings ...elementSettings
} }
const settings = [ const layoutWidgets = [
{ {
title: '布局控件', text: '分组名称',
widgets: [ name: '分组名称',
{ schema: {
text: '分组名称', title: '分组名称',
name: '分组名称', type: 'object',
schema: { properties: {}
title: '分组名称', },
type: 'object', setting: {
properties: {} groupBase: {
}, title: '基础信息',
setting: {}, type: 'object',
properties: {}
}, },
], title: {
title: '标题',
type: 'string',
widget: 'FieldNames',
required: true,
displayType: 'row',
labelWidth: 80,
},
// groupStyle: {
// title: '控件样式',
// type: 'object',
// properties: {
// color: {
// title: '颜色',
// type: 'string',
// }
// }
// }
},
}, },
]
const textWidgets = [
{ {
title: '文本控件', text: '文本',
widgets: [ name: '文本',
{ schema: {
text: '文本', title: '文本',
name: '文本', type: 'string',
schema: { widget: 'TextInput',
title: '文本', props: {
type: 'string', style: {
}, color: 'pink'
setting: { }
...allSetting }
}, },
setting: {
groupBase: {
title: '基础信息',
type: 'object',
collapsed: true,
properties: {}
}, },
{ fieldName: {
text: '唯一值文本', title: '字段名称',
name: '唯一值文本', type: 'string',
schema: { widget: 'FieldNames',
title: '唯一值文本', required: true,
type: 'string', displayType: 'row',
widget: 'UniqueValue' labelWidth: 80,
},
setting: {
...allSetting
},
}, },
{ title: {
text: '多行文本', title: '标题',
name: '多行文本', required: true,
schema: { type: 'string',
title: '多行文本', widget: 'htmlInput',
type: 'string', displayType: 'row',
widget: 'TextArea' labelWidth: 80,
},
setting: {
...allSetting
},
}, },
{ placeholder: {
text: '数值', title: '提示语',
name: '数值', type: 'string',
schema: { widget: 'Placeholder',
title: '数值', displayType: 'row',
type: 'number', labelWidth: 80,
widget: 'number' dependencies: ['title']
},
setting: {
...allSetting
},
}, },
{ presetValue: {
text: '本人部门', title: '默认值',
name: '本人部门', type: 'string',
schema: { widget: 'InputDefault',
title: '本人部门', displayType: 'row',
type: 'string', labelWidth: 80,
name: '本人部门',
widget: 'SelfInfo'
},
setting: {
...allSetting
},
}, },
{ // groupSource: {
text: '本人姓名', // title: '数据来源',
name: '本人姓名', // type: 'object',
schema: { // properties: {}
title: '本人姓名', // },
name: '本人姓名', // source: {
type: 'string', // title: '数据来源',
widget: 'SelfInfo' // name: '数据来源',
}, // type: 'string',
setting: { // widget: 'select',
...allSetting // displayType: 'row',
// labelWidth: 80,
// enum: [1, 2, 3],
// enumNames: ['一行一列', '一行两列', '一行三列'],
// },
groupVerify: {
title: '数据校验',
type: 'object',
properties: {}
},
required: {
title: '必填',
type: 'boolean',
widget: 'BooleanSwitch',
default: false,
displayType: 'row',
labelWidth: 100,
},
maxLength: {
title: '字节最大长度',
type: 'number',
widget: 'number',
default: 200,
displayType: 'row',
labelWidth: 100,
},
rules: {
title: '正则校验',
type: 'array',
default: [],
widget: 'VerifyTextInput',
displayType: 'row',
labelWidth: 100,
},
groupSetting: {
title: '控件设置',
type: 'object',
properties: {}
},
disabled: {
title: '只读',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '33%',
},
hidden: {
title: '隐藏',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '33%',
},
emphasis: {
title: '强调',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '33%',
},
groupStyle: {
title: '控件样式',
type: 'object',
properties: {}
},
prefix: {
title: '前缀',
type: 'string',
widget: 'InputAddon',
displayType: 'row',
labelWidth: 80,
},
suffix: {
title: '后缀',
type: 'string',
widget: 'InputAddon',
displayType: 'row',
labelWidth: 80,
},
labelWidth: {
title: '标签宽度',
description: '默认值110',
default: 110,
type: 'number',
widget: 'slider',
max: 400,
props: {
hideNumber: true,
}, },
}, },
{ },
text: '本人ID', },
name: '本人ID', {
schema: { text: '唯一值文本',
title: '本人ID', name: '唯一值文本',
name: '本人ID', schema: {
type: 'string', title: '唯一值文本',
widget: 'SelfInfo' type: 'string',
widget: 'UniqueValue'
},
setting: {
// ...allSetting
},
},
{
text: '多行文本',
name: '多行文本',
schema: {
title: '多行文本',
type: 'string',
widget: 'TextArea'
},
setting: {
...allSetting
},
},
{
text: '数值',
name: '数值',
schema: {
title: '数值',
type: 'number',
widget: 'number'
},
setting: {
fieldName: {
title: '字段名称',
type: 'string',
widget: 'FieldNames',
required: true,
displayType: 'row',
labelWidth: 80,
},
title: {
title: '标题',
required: true,
type: 'string',
widget: 'htmlInput',
displayType: 'row',
labelWidth: 80,
},
placeholder: {
title: '提示语',
type: 'string',
widget: 'Placeholder',
displayType: 'row',
labelWidth: 80,
dependencies: ['title']
},
},
},
{
text: '本人部门',
name: '本人部门',
schema: {
title: '本人部门',
type: 'string',
name: '本人部门',
widget: 'SelfInfo'
},
setting: {
...allSetting
},
},
{
text: '本人姓名',
name: '本人姓名',
schema: {
title: '本人姓名',
name: '本人姓名',
type: 'string',
widget: 'SelfInfo'
},
setting: {
...allSetting
},
},
{
text: '本人ID',
name: '本人ID',
schema: {
title: '本人ID',
name: '本人ID',
type: 'string',
widget: 'SelfInfo'
},
setting: {
...allSetting
},
},
{
text: '富文本',
name: '富文本',
schema: {
title: '富文本',
name: '富文本',
type: 'string',
widget: 'RichText'
},
setting: {
...allSetting,
width: {
title: '元素宽度',
type: 'string',
hidden: true,
widget: 'percentSlider',
default: '100%',
},
},
},
]
const selectWidgets = [
{
text: '值选择器',
name: '值选择器',
schema: {
title: '值选择器',
name: '值选择器',
type: 'string',
widget: 'ValueSelect',
require: true,
},
setting: {
groupBase: {
title: '基础信息',
type: 'object',
collapsed: true,
properties: {}
},
fieldName: {
title: '字段名称',
type: 'string',
widget: 'FieldNames',
required: true,
displayType: 'row',
labelWidth: 80,
},
title: {
title: '标题',
required: true,
type: 'string',
widget: 'htmlInput',
displayType: 'row',
labelWidth: 80,
},
placeholder: {
title: '提示语',
type: 'string',
displayType: 'row',
labelWidth: 80,
default: '123'
},
presetValue: {
title: '默认值',
type: 'string',
displayType: 'row',
labelWidth: 80,
},
'数据来源': {
title: '数据来源',
type: 'object',
properties: {}
},
options: {
title: '选项',
name: '选项',
type: 'array',
default: [],
widget: 'simpleList',
default: [
{ value: '选项一', },
],
items: {
type: 'object',
properties: {
value: {
title: '选项值',
type: 'string',
displayType: 'row',
labelWidth: 75,
required: true,
default: '',
props: {
style: {
width: '120px',
color: 'blue'
}
}
},
},
}, },
setting: { props: {
...allSetting // hideMove: true,
hideCopy: true,
}, },
displayType: 'row',
labelWidth: 80,
}, },
{ '数据校验': {
text: '富文本', title: '数据校验',
name: '富文本', type: 'object',
schema: { properties: {}
title: '富文本', },
name: '富文本', required: {
type: 'string', title: '必填',
widget: 'RichText' type: 'boolean',
}, widget: 'checkbox',
setting: { default: false,
...allSetting width: '33%',
},
'控件设置': {
title: '控件设置',
type: 'object',
properties: {}
},
disabled: {
title: '只读',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '25%',
},
hidden: {
title: '隐藏',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '25%',
},
emphasis: {
title: '强调',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '25%',
},
isMultiple: {
title: '多选',
type: 'boolean',
widget: 'checkbox',
default: false,
width: '25%',
},
isEdit: {
title: '是否可编辑',
type: 'boolean',
widget: 'BooleanSwitch',
displayType: 'row',
labelWidth: 120,
description: '勾选后可直接输入选项'
},
'控件样式': {
title: '控件样式',
type: 'object',
properties: {}
},
showType: {
title: '显示方式',
type: 'string',
enum: ['下拉', '平铺'],
default: '下拉',
enumNames: ['下拉', '平铺'],
displayType: 'row',
labelWidth: 80,
},
labelWidth: {
title: '标签宽度',
description: '默认值110',
default: 110,
type: 'number',
widget: 'slider',
max: 400,
props: {
hideNumber: true,
}, },
}, },
], }
}, },
{ {
title: '选择器控件', text: '平铺值选择器',
widgets: [ name: '平铺值选择器',
{ schema: {
text: '值选择器', title: '平铺值选择器',
name: '值选择器', name: '平铺值选择器',
schema: { type: 'string',
title: '值选择器', widget: 'TileSelect',
name: '值选择器', require: true,
type: 'string', },
widget: 'ValueSelect', setting: {
require: true, fieldName: {
}, title: '字段名称',
setting: { type: 'string',
...commonSettings, widget: 'FieldNames',
dataSource: { required: true,
title: '数据来源', displayType: 'row',
type: 'string', labelWidth: 80,
widget: 'DataSource',
displayType: 'row',
labelWidth: 80,
},
isMultiple: {
title: '是否多选',
type: 'boolean',
widget: 'BooleanSwitch',
displayType: 'row',
labelWidth: 80,
},
...switchSettings,
...elementSettings,
}
}, },
{ base: {
text: '平铺值选择器', title: '基础信息',
name: '平铺值选择器', type: 'object',
schema: { properties: {
title: '平铺值选择器', fieldName: {
name: '平铺值选择器', title: '字段名称',
type: 'string',
widget: 'TileSelect',
require: true,
},
setting: {
...commonSettings,
options: {
title: '可选值',
type: 'string', type: 'string',
widget: 'EnumOptions', widget: 'FieldNames',
displayType: 'row', required: true,
labelWidth: 80,
},
isMultiple: {
title: '是否多选',
name: '是否多选',
type: 'boolean',
widget: 'BooleanSwitch',
displayType: 'row', displayType: 'row',
labelWidth: 80, labelWidth: 80,
}, },
...switchSettings, title: {
...elementSettings, title: '标题',
} required: true,
},
{
text: '搜索选择器',
name: '搜索选择器',
schema: {
title: '搜索选择器',
name: '搜索选择器',
type: 'string',
widget: 'SearchSelect',
require: true,
},
setting: {
...commonSettings,
options: {
title: '数据字典',
type: 'string', type: 'string',
widget: 'input', widget: 'htmlInput',
displayType: 'row',
labelWidth: 80,
},
isMultiple: {
title: '是否多选',
name: '是否多选',
type: 'boolean',
widget: 'BooleanSwitch',
displayType: 'row', displayType: 'row',
labelWidth: 80, labelWidth: 80,
}, },
...switchSettings,
...elementSettings,
} }
}, },
{ // ...commonSettings,
text: '关联选择器', }
name: '关联选择器', },
schema: { {
title: '关联选择器', text: '搜索选择器',
name: '关联选择器', name: '搜索选择器',
type: 'string', schema: {
widget: 'CascadeSelect', title: '搜索选择器',
require: true, name: '搜索选择器',
}, type: 'string',
setting: { widget: 'SearchSelect',
...commonSettings, require: true,
dependencies: { },
title: '关联字段', setting: {
type: 'array', ...commonSettings,
widget: 'CascadeField', options: {
displayType: 'row', title: '数据字典',
labelWidth: 80, type: 'string',
}, widget: 'input',
// isMultiple: { displayType: 'row',
// title: '是否多选', labelWidth: 80,
// type: 'boolean',
// widget: 'BooleanSwitch',
// displayType: 'row',
// labelWidth: 80,
// },
...switchSettings,
...elementSettings,
}
}, },
{ isMultiple: {
text: '人员选择器', title: '是否多选',
name: '人员选择器', name: '是否多选',
schema: { type: 'boolean',
title: '人员选择器', widget: 'BooleanSwitch',
name: '人员选择器', displayType: 'row',
type: 'string', labelWidth: 80,
widget: 'PersonSelect',
require: true,
},
setting: {
...commonSettings,
// dependencies: {
// title: '关联字段',
// type: 'array',
// widget: 'CascadeField',
// displayType: 'row',
// labelWidth: 80,
// },
// isMultiple: {
// title: '是否多选',
// type: 'boolean',
// widget: 'BooleanSwitch',
// displayType: 'row',
// labelWidth: 80,
// },
...switchSettings,
...elementSettings,
}
}, },
], ...switchSettings,
...elementSettings,
}
},
{
text: '关联选择器',
name: '关联选择器',
schema: {
title: '关联选择器',
name: '关联选择器',
type: 'string',
widget: 'CascadeSelect',
require: true,
},
setting: {
...commonSettings,
dependencies: {
title: '关联字段',
type: 'array',
widget: 'CascadeField',
displayType: 'row',
labelWidth: 80,
},
// isMultiple: {
// title: '是否多选',
// type: 'boolean',
// widget: 'BooleanSwitch',
// displayType: 'row',
// labelWidth: 80,
// },
...switchSettings,
...elementSettings,
}
},
{
text: '人员选择器',
name: '人员选择器',
schema: {
title: '人员选择器',
name: '人员选择器',
type: 'string',
widget: 'PersonSelect',
require: true,
},
setting: {
...commonSettings,
// dependencies: {
// title: '关联字段',
// type: 'array',
// widget: 'CascadeField',
// displayType: 'row',
// labelWidth: 80,
// },
// isMultiple: {
// title: '是否多选',
// type: 'boolean',
// widget: 'BooleanSwitch',
// displayType: 'row',
// labelWidth: 80,
// },
...switchSettings,
...elementSettings,
}
},
]
const settings = [
{
title: '布局控件',
widgets: layoutWidgets
},
{
title: '文本控件',
widgets: textWidgets
},
{
title: '选择器控件',
widgets: selectWidgets,
}, },
{ {
title: '时间控件', title: '时间控件',
...@@ -382,6 +734,12 @@ const settings = [ ...@@ -382,6 +734,12 @@ const settings = [
labelWidth: 80, labelWidth: 80,
}, },
...switchSettings, ...switchSettings,
width: {
title: '元素宽度',
type: 'string',
widget: 'percentSlider',
default: '100%',
},
...elementSettings, ...elementSettings,
}, },
}, },
...@@ -416,7 +774,7 @@ const settings = [ ...@@ -416,7 +774,7 @@ const settings = [
widget: 'RelationForm', widget: 'RelationForm',
}, },
setting: { setting: {
$id: { fieldName: {
title: '字段名称', title: '字段名称',
type: 'string', type: 'string',
widget: 'FieldNames', widget: 'FieldNames',
...@@ -447,6 +805,12 @@ const settings = [ ...@@ -447,6 +805,12 @@ const settings = [
labelWidth: 80, labelWidth: 80,
default: false default: false
}, },
width: {
title: '元素宽度',
type: 'string',
widget: 'percentSlider',
default: '100%',
},
...elementSettings, ...elementSettings,
}, },
}, },
......
import React, { useRef, useMemo, useState, useContext } from 'react' import React, { useRef, useMemo, useEffect, useState, useContext, createContext } from 'react'
import { ConfigProvider, message } from 'antd' import { ConfigProvider, message } from 'antd'
import Generator from 'fr-generator' import Generator from 'fr-generator'
import { settings, baseSettings, globalSettings } from './config' import { settings, baseSettings, globalSettings } from './config'
import widgets from '../widgets' import widgets from '../widgets'
export const GlobalStore = createContext(null)
const FormDesigner = (props) => { const FormDesigner = (props) => {
const { tableName } = props
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) const { getPrefixCls } = useContext(ConfigProvider.ConfigContext)
const prefixCls = getPrefixCls('parse-form') const prefixCls = getPrefixCls('pandaXform')
const prefixClsWithoutParseForm = getPrefixCls() const pandaXform = getPrefixCls()
const [table, setTable] = useState()
const ref = useRef(null) const ref = useRef(null)
const getTableField = () => {
}
const getTableInfo = () => {
}
const getTable = () => {
}
useEffect(() => {
if (tableName) {
getTable()
}
}, [tableName])
const extraButtons = [ const extraButtons = [
{ {
text: '提交', text: '提交',
...@@ -19,6 +43,7 @@ const FormDesigner = (props) => { ...@@ -19,6 +43,7 @@ const FormDesigner = (props) => {
if (errors.length) { if (errors.length) {
return message.error('请按照提示完善表单内容') return message.error('请按照提示完善表单内容')
} }
console.log('ref.current', ref.current)
props.setSchema && props.setSchema(ref.current.getValue()) props.setSchema && props.setSchema(ref.current.getValue())
} }
} }
...@@ -44,24 +69,28 @@ const FormDesigner = (props) => { ...@@ -44,24 +69,28 @@ const FormDesigner = (props) => {
} }
return ( return (
<div className={prefixCls}> <GlobalStore.Provider
<Generator value={{}}
configProvider={{ prefixCls: prefixClsWithoutParseForm }} >
mapping={{ <div className={prefixCls}>
object: 'Header', <Generator
}} configProvider={{ prefixCls: pandaXform }}
validation={true} mapping={{
ref={ref} object: 'Header',
extraButtons={extraButtons} }}
onChange={onChange} validation={true}
onSchemaChange={onSchemaChange} ref={ref}
widgets={widgets} extraButtons={extraButtons}
onCanvasSelect={onCanvasSelect} onChange={onChange}
settings={settings} onSchemaChange={onSchemaChange}
commonSettings={baseSettings} widgets={widgets}
globalSettings={globalSettings} onCanvasSelect={onCanvasSelect}
/> settings={settings}
</div> commonSettings={baseSettings}
globalSettings={globalSettings}
/>
</div>
</GlobalStore.Provider>
) )
} }
......
...@@ -55,7 +55,7 @@ const schema = { ...@@ -55,7 +55,7 @@ const schema = {
}, },
}, },
"required": [], "required": [],
"className": "ant-parse-form-group" "className": "ant-pandaXform-group"
}, },
}, },
"required": [], "required": [],
......
...@@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef, useContext, createContext, forwardR ...@@ -2,7 +2,6 @@ import React, { useState, useEffect, useRef, useContext, createContext, forwardR
import FormRender, { useForm } from 'form-render' import FormRender, { useForm } from 'form-render'
import { ConfigProvider } from 'antd' import { ConfigProvider } from 'antd'
import widgets from '../widgets' import widgets from '../widgets'
// import schema from './data'
export const GlobalStore = createContext(null) export const GlobalStore = createContext(null)
...@@ -17,14 +16,18 @@ const XRender = (props, ref) => { ...@@ -17,14 +16,18 @@ const XRender = (props, ref) => {
const { schema } = props const { schema } = props
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) const { getPrefixCls } = useContext(ConfigProvider.ConfigContext)
const prefixCls = getPrefixCls('parse-form') const prefixCls = getPrefixCls('pandaXform')
const prefixClsWithoutParseForm = getPrefixCls() const pandaXform = getPrefixCls()
const [extraData, setExtraData] = useState({}) const [extraData, setExtraData] = useState({})
const [parseFormData, setParseFormData] = useState({}) const [parseFormData, setParseFormData] = useState({})
const [listData, setListData] = useState([]) const [listData, setListData] = useState([])
const form = useForm() const form = useForm()
const onClick = () => {
console.log('form', form)
}
return ( return (
<GlobalStore.Provider <GlobalStore.Provider
value={{ value={{
...@@ -35,9 +38,9 @@ const XRender = (props, ref) => { ...@@ -35,9 +38,9 @@ const XRender = (props, ref) => {
listData, listData,
setParseFormData, setParseFormData,
}}> }}>
<div className={prefixCls}> <div className={prefixCls} onClick={() => onClick()}>
<FormRender <FormRender
configProvider={{ prefixCls: prefixClsWithoutParseForm }} configProvider={{ prefixCls: pandaXform }}
form={form} form={form}
schema={schema} schema={schema}
// onChange={form.setValues} // onChange={form.setValues}
......
...@@ -5,9 +5,9 @@ import './index.less' ...@@ -5,9 +5,9 @@ import './index.less'
const Header = (props) => { const Header = (props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) const { getPrefixCls } = useContext(ConfigProvider.ConfigContext)
const prefixCls = getPrefixCls('parse-form') const prefixCls = getPrefixCls('pandaXform')
const { title } = props const { title, value } = props
return ( return (
<div> <div>
......
@import '~antd/es/style/themes/default.less'; @import '~antd/es/style/themes/default.less';
@parse-form-prefix-cls: ~'@{ant-prefix}-parse-form'; @pandaXform: ~'@{ant-prefix}-pandaXform';
.@{parse-form-prefix-cls} { .@{pandaXform} {
.@{parse-form-prefix-cls}-title { .@{pandaXform}-title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 16px; font-size: 16px;
} }
.@{parse-form-prefix-cls}-bg { .@{pandaXform}-bg {
width: 3px; width: 3px;
height: 16px; height: 16px;
margin-right: 10px; margin-right: 10px;
......
...@@ -8,11 +8,46 @@ import { downloadFunc, filenameVerification } from '../../../../utils/utils' ...@@ -8,11 +8,46 @@ import { downloadFunc, filenameVerification } from '../../../../utils/utils'
const accepts = { const accepts = {
'图片': ['.bmp', '.gif', '.jpeg', 'tiff', '.png', '.svg', '.jpg'], '图片': ['.bmp', '.gif', '.jpeg', 'tiff', '.png', '.svg', '.jpg'],
'文档': ['.docx', '.xlsx', '.pdf'], // '文档': ['.docx', '.xlsx', '.pdf'],
'文档': [],
'视频': ['.mp4'], '视频': ['.mp4'],
'音频': ['.mp3'], '音频': ['.mp3'],
} }
const getFileType = (fileName) => {
if (fileName) {
//图片
if (fileName.includes('jpg')) {
return 'jpg'
}
if (fileName.includes('png')) {
return 'png'
}
if (fileName.includes('svg')) {
return 'svg'
}
//文档
if (fileName.includes('docx')) {
return 'docx'
}
if (fileName.includes('xlsx')) {
return 'xlsx'
}
if (fileName.includes('pdf')) {
return 'pdf'
}
//音频
if (fileName.includes('mp3')) {
return 'mp3'
}
//视频
if (fileName.includes('mp4')) {
return 'mp4'
}
}
return null
}
const FileUpload = (props) => { const FileUpload = (props) => {
const { value, schema, onChange } = props const { value, schema, onChange } = props
...@@ -69,14 +104,7 @@ const FileUpload = (props) => { ...@@ -69,14 +104,7 @@ const FileUpload = (props) => {
} }
}, },
onPreview: (file) => { onPreview: (file) => {
let fileType = null let fileType = getFileType(file.name)
if (file.name.includes('docx')) {
fileType = 'docx'
} else if (file.name.includes('xlsx')) {
fileType = 'xlsx'
} else if (file.name.includes('jpg')) {
fileType = 'jpg'
}
if (fileType) { if (fileType) {
setShowFile({ name: file.name, fileType: fileType, filePath: file.url }) setShowFile({ name: file.name, fileType: fileType, filePath: file.url })
setVisible(true) setVisible(true)
...@@ -86,10 +114,11 @@ const FileUpload = (props) => { ...@@ -86,10 +114,11 @@ const FileUpload = (props) => {
const iconRender = (file, listType) => { const iconRender = (file, listType) => {
if (listType !== 'text') { if (listType !== 'text') {
if (file.name.includes('docx')) { let fileType = getFileType(file.name)
return <div className={styles.iconImg} type='docx'></div> if (fileType) {
} else if (file.name.includes('xlsx')) { return <div className={styles.iconImg} type={fileType}></div>
return <div className={styles.iconImg} type='xlsx'></div> } else {
return <div className={styles.iconImg} type={'通用'}></div>
} }
} }
return <FileOutlined /> return <FileOutlined />
...@@ -135,7 +164,7 @@ const FileUpload = (props) => { ...@@ -135,7 +164,7 @@ const FileUpload = (props) => {
{listType !== 'picture-card' ? <Button icon={<UploadOutlined />}>{placeholder || '上传'}</Button> : (disabled ? null : (placeholder || '+ 上传'))} {listType !== 'picture-card' ? <Button icon={<UploadOutlined />}>{placeholder || '上传'}</Button> : (disabled ? null : (placeholder || '+ 上传'))}
</Upload> </Upload>
<Modal <Modal
width={'80%'} width={'70%'}
title={showFile.name} title={showFile.name}
visible={visible} visible={visible}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
......
@imgSrc: '../../../../assets/images'; @imgSrc: '../../../../assets/images';
.uploadBox { .uploadBox {
.iconImg { .iconImg {
width:45px; width: 45px;
height: 45px; height: 45px;
margin: 0 auto; margin: 0 auto;
&[type='通用'] {
background: url('@{imgSrc}/通用.png');
background-size: 100% 100%;
}
&[type='jpg'] {
background: url('@{imgSrc}/JPG.png');
background-size: 100% 100%;
}
&[type='png'] {
background: url('@{imgSrc}/PNG.png');
background-size: 100% 100%;
}
&[type="mp3"] {
background: url('@{imgSrc}/音乐.png');
background-size: 100% 100%;
}
&[type="mp4"] {
background: url('@{imgSrc}/视频.png');
background-size: 100% 100%;
}
&[type='docx'] { &[type='docx'] {
background: url('@{imgSrc}/Word.png'); background: url('@{imgSrc}/Word.png');
background-size: 100% 100%; background-size: 100% 100%;
...@@ -12,5 +32,9 @@ ...@@ -12,5 +32,9 @@
background: url('@{imgSrc}/Excel.png'); background: url('@{imgSrc}/Excel.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
&[type='PDF'] {
background: url('@{imgSrc}/PDF.png');
background-size: 100% 100%;
}
} }
} }
\ No newline at end of file
...@@ -2,42 +2,13 @@ import React, { useEffect, useMemo } from 'react' ...@@ -2,42 +2,13 @@ import React, { useEffect, useMemo } from 'react'
import { Radio, Checkbox } from 'antd' import { Radio, Checkbox } from 'antd'
import './index.less' import './index.less'
const TileSelect = ({ value, onChange, schema }) => { const TileSelect = (props) => {
const { presetValue, isMultiple, options, isDisable } = schema console.log('props', props)
console.log('isDisable', schema)
const enums = useMemo(() => {
return options ? options.split(',') : []
}, [options])
const showValue = useMemo(() => {
if (value) {
if (isMultiple) {
return value.split(',')
}
return value
}
return presetValue
}, [value, presetValue, isMultiple])
return ( return (
<div className='tileSelect' disabled={isDisable}> <div className='tileSelect'>
{ 123
isMultiple ?
<Checkbox.Group
disabled={isDisable}
onChange={value => onChange(value.join(','))}
value={showValue}
>
{enums.map(v => <Checkbox key={v} value={v}>{v}</Checkbox>)}
</Checkbox.Group>
:
<Radio.Group onChange={e => onChange(e.target.value)} value={showValue} disabled={isDisable}>
{enums.map(v => <Radio key={v} value={v}>{v}</Radio>)}
</Radio.Group>
}
</div> </div>
) )
......
// 选择器,值选择器 // 选择器,值选择器
import React, { useState, useMemo } from 'react' import React, { useState, useMemo } from 'react'
import { Select } from 'antd' import { Select, Checkbox, Radio } from 'antd'
import style from '../../style' import styels from './index.less'
const { Option } = Select const { Option } = Select
const ValueSelect = ({ value, onChange, schema }) => { const ValueSelect = ({ value, onChange, schema }) => {
const { config, placeholder, disabled, options, presetValue } = schema const { placeholder, disabled, options, presetValue, showType, isMultiple, isEdit } = schema
const mode = useMemo(() => { console.log('options', options)
if (config && config.includes('n')) {
return 'multiple'
}
return null
}, [config])
const valueShow = useMemo(() => { const valueShow = useMemo(() => {
if (mode) { if (isMultiple) {
return (value || presetValue) ? (value || presetValue).split(',') : null return (value || presetValue) ? (value || presetValue).split(',') : []
} else { } else {
return value || presetValue || null return value || presetValue || null
} }
}, [presetValue, value]) }, [presetValue, value])
const enums = useMemo(() => {
return Array.isArray(options) ? options.map(v => v.value) : []
// if (options) {
// return options.split(',')
// }
}, [options])
const handleChange = value => { const handleChange = value => {
if (mode) { if (isEdit) {
onChange(value.join(','))
} else { } else {
onChange(value) if (isMultiple) {
onChange(value.join(','))
} else {
onChange(value)
}
} }
} }
return ( // console.log('valueShow', valueShow, disabled)
<Select
style={style} if (showType === '下拉') {
mode={mode} return (
disabled={disabled} <Select
showArrow={!disabled} style={{ width: '100%' }}
optionFilterProp="children" mode={isMultiple ? 'multiple' : ''}
value={valueShow} disabled={disabled}
placeholder={placeholder} showArrow={!disabled}
onChange={handleChange} // optionFilterProp="children"
> value={valueShow}
{options && options.split(',').map(v => <Option value={v} key={v}>{v}</Option>)} placeholder={placeholder}
</Select> onChange={handleChange}
) >
{enums.map(v => <Option value={v} key={v}>{v}</Option>)}
</Select>
)
}
if (showType === '平铺') {
return (
<div className={styels.tileSelect} type={disabled ? 'disabled' : ''}>
{
isMultiple ?
<Checkbox.Group
disabled={disabled}
onChange={value => onChange(value.join(','))}
value={valueShow}
>
{enums.map(v => <Checkbox key={v} value={v}>{v}</Checkbox>)}
</Checkbox.Group>
:
<Radio.Group onChange={e => onChange(e.target.value)} value={valueShow} disabled={disabled}>
{enums.map(v => <Radio key={v} value={v}>{v}</Radio>)}
</Radio.Group>
}
</div>
)
}
return null
} }
export default ValueSelect export default ValueSelect
@import '~antd/es/style/themes/default.less';
.tileSelect {
width: 100%;
min-height: 30px;
&[type='disabled'] {
background: #f8fafc;
}
padding: 0 5px;
.@{ant-prefix}-checkbox-wrapper {
margin-top: 4px;
}
.@{ant-prefix}-radio-wrapper {
margin-top: 4px;
}
}
\ No newline at end of file
...@@ -8,6 +8,8 @@ const BooleanSwitch = (props) => { ...@@ -8,6 +8,8 @@ const BooleanSwitch = (props) => {
return ( return (
<Switch <Switch
checked={value} checked={value}
checkedChildren='是'
unCheckedChildren='否'
onChange={(checked) => onChange(checked)} onChange={(checked) => onChange(checked)}
/> />
) )
......
...@@ -91,7 +91,7 @@ const FieldNames = (props) => { ...@@ -91,7 +91,7 @@ const FieldNames = (props) => {
} }
return ( return (
<Select onChange={change} style={style} value={valueShow}> <Select onChange={change} style={{ ...style, maxWidth: '208px' }} value={valueShow}>
{ {
field.map(v => <Select.Option key={v} value={v}>{v}</Select.Option>) field.map(v => <Select.Option key={v} value={v}>{v}</Select.Option>)
} }
......
import React, { useMemo, useState, useContext } from 'react'
import { Input, Modal, ConfigProvider } from 'antd'
import Icon, { EllipsisOutlined } from '@ant-design/icons'
import * as icons from '@ant-design/icons'
import './index.less'
const iconList = Object.keys(icons).filter((item) => typeof icons[item] === 'object')
const InputAddon = (props) => {
const { value, onChange } = props
const [visible, setVisible] = useState(false)
const [icon, setIcon] = useState()
// const valueShow = useMemo(() => {
// return value ? value : ''
// }, [value])
const inputChange = (e) => {
onChange(e.target.value)
}
const onOk = () => {
setVisible(false)
onChange(icon)
}
const iconClick = () => {
setVisible(true)
}
const onCancel = () => {
setVisible(false)
}
return (
<div className={`inputAddon`}>
<Input value={value} onChange={inputChange} addonAfter={<EllipsisOutlined onClick={iconClick} />} />
<Modal
width={'50%'}
getContainer={false}
title='图标选择'
visible={visible}
onOk={onOk}
onCancel={onCancel}
bodyStyle={{ height: '450px', overflow: 'auto' }}
>
<div className={`iconBox`}>
{
iconList.map((v, i) => {
if (i === 0) return null
return (
<div key={v} className={`iconItem`} active={JSON.stringify(icon === v)} onClick={() => setIcon(v)}>
<Icon component={icons[v]} />
</div>
)
})
}
</div>
</Modal>
</div>
)
}
export default InputAddon
\ No newline at end of file
@import '~antd/es/style/themes/default.less';
@pandaXform: ~'@{ant-prefix}-pandaXform';
.@{pandaXform} {
.inputAddon {
.iconBox {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
.iconItem {
display: flex;
align-items: center;
justify-content: center;
width: 70px;
height: 70px;
font-size: 50px;
&:hover {
cursor: pointer;
}
&[active='true'] {
transition: all 0.3s ease;
background: #4096ff;
border-radius: 5px;
color: white;
transform: scale(1.2);
}
}
}
}
}
\ No newline at end of file
import React, { useMemo, useEffect, useState } from 'react'
import { Input } from 'antd'
const Placeholder = (props) => {
const { value, addons, onChange } = props
const { title } = addons.formData
const inputChange = (e) => {
onChange(e.target.value)
}
useEffect(() => {
if (!value) {
onChange(`请输入${title}`)
}
}, [title, value])
return (
<Input value={value} onChange={inputChange} />
)
}
export default Placeholder
\ No newline at end of file
import React, { useMemo } from 'react'
import { Select } from 'antd'
const options = [
{
label: '邮箱(email)',
value: '^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$',
message: '邮箱校验未通过'
},
{
label: '身份证号(identity)',
value: '(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)',
message: '手机号校验未通过'
},
{
label: '手机号(mobile)',
value: '/^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/',
message: '手机号校验未通过',
},
{
label: '银行卡号(bankAccount)',
value: '^(\\d{16}|\\d{19})$',
message: '银行卡号校验未通过'
},
]
const VerifyTextInput = (props) => {
const { value, onChange } = props
const valueShow = useMemo(() => {
return Array.isArray(value) && value.length ? value[0].pattern : null
}, [value])
const selectChange = (value) => {
const item = options.find(v => v.value === value)
onChange([{
pattern: value,
message: item.message,
}])
}
return (
<Select
allowClear
value={valueShow}
options={options}
onChange={selectChange}
/>
)
}
export default VerifyTextInput
\ No newline at end of file
import React, { useMemo, useState } from 'react'
import { Input, Modal, Popover, Select } from 'antd'
import { EllipsisOutlined } from '@ant-design/icons'
const options = [
{ label: '本人姓名', value: '【本人姓名】' },
{ label: '本人部门', value: '【本人部门】' },
]
const InputDefault = (props) => {
console.log('props', props)
const { value, onChange } = props
const [visible, setVisible] = useState(false)
const style = useMemo(() => {
},)
const iconClick = () => {
setVisible(true)
}
const onOk = () => {
}
const onCancel = () => {
setVisible(false)
}
const selectChange = (value) => {
onChange(value)
}
const content = (
<Select
style={{ width: '100%' }}
options={options}
onChange={selectChange}
>
</Select>
)
return (
<div className='InputDefault'>
<Input
value={value}
onChange={(e) => onChange(e.target.value)}
style={style}
addonAfter={
<Popover
placement="topRight"
content={content}
title="数据将从环境变量读取"
trigger="click"
>
<EllipsisOutlined />
</Popover>
}
/>
</div>
)
}
export default InputDefault
\ No newline at end of file
import InputDefault from "./InputDefault"
const base = {
InputDefault,
}
export default base
\ No newline at end of file
import base from './base'
import BooleanSwitch from './BooleanSwitch' import BooleanSwitch from './BooleanSwitch'
import EnumOptions from './EnumOptions' import EnumOptions from './EnumOptions'
import FieldNames from './FieldNames' import FieldNames from './FieldNames'
import InputAddon from './InputAddon'
import Placeholder from './Placeholder'
import CascadeField from './CascadeField' import CascadeField from './CascadeField'
import DateSelect from './DateSelect' import DateSelect from './DateSelect'
import DataSource from './DataSource' import DataSource from './DataSource'
import VerifyTextInput from './VerifyTextInput'
const settings = { const settings = {
...base,
BooleanSwitch, BooleanSwitch,
EnumOptions, EnumOptions,
FieldNames, FieldNames,
InputAddon,
Placeholder,
CascadeField, CascadeField,
DateSelect, DateSelect,
DataSource, DataSource,
VerifyTextInput,
} }
export default settings export default settings
\ No newline at end of file
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef, useMemo } from 'react' import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef, useMemo } from 'react'
import './index.less' import styles from './index.less'
import { request } from '@wisdom-utils/utils' import { request } from '@wisdom-utils/utils'
import { Editor, Toolbar } from '@wangeditor/editor-for-react' import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import { DomEditor } from '@wangeditor/editor' import { DomEditor } from '@wangeditor/editor'
...@@ -68,7 +68,7 @@ const RichText = forwardRef(({ schema, onChange, value }, ref) => { ...@@ -68,7 +68,7 @@ const RichText = forwardRef(({ schema, onChange, value }, ref) => {
return ( return (
<div className='richText' disabled={disabled}> <div className={styles.richText} disabled={disabled}>
{ {
!disabled ? !disabled ?
<Toolbar <Toolbar
......
@import '~antd/es/style/themes/default.less'; @import '~antd/es/style/themes/default.less';
@parse-form-prefix-cls: ~'@{ant-prefix}-parse-form'; @pandaXform-prefix-cls: ~'@{ant-prefix}-pandaXform';
.richText { .richText {
border: 1px solid #ccc; border: 1px solid #ccc;
z-index: 100; z-index: 100;
......
import React, { useState, useEffect, useMemo } from 'react' import React, { useState, useEffect, useMemo } from 'react'
import { Input } from 'antd' import { Input } from 'antd'
import Icon, { EllipsisOutlined } from '@ant-design/icons'
import * as icons from '@ant-design/icons'
const TextInput = ({ value, onChange, schema }) => { const iconList = Object.keys(icons).filter((item) => typeof icons[item] === 'object')
console.log('文本输入框', schema) const TextInput = (props) => {
const { disabled, placeholder, presetValue } = schema const { value, onChange, schema } = props
const { disabled, placeholder, presetValue, prefix, suffix, maxLength, rules } = schema
const valueShow = useMemo(() => { const valueShow = useMemo(() => {
return presetValue || value return presetValue || value
...@@ -15,13 +18,18 @@ const TextInput = ({ value, onChange, schema }) => { ...@@ -15,13 +18,18 @@ const TextInput = ({ value, onChange, schema }) => {
onChange(e.target.value) onChange(e.target.value)
} }
console.log('rules', rules)
return ( return (
<Input <Input
rules={rules}
disabled={disabled} disabled={disabled}
value={valueShow} value={valueShow}
rows={5} maxLength={maxLength}
placeholder={placeholder} placeholder={disabled ? null : placeholder}
onChange={handleChange} onChange={handleChange}
addonBefore={iconList.includes(prefix) ? <Icon component={icons[prefix]} /> : prefix}
addonAfter={iconList.includes(suffix) ? <Icon component={icons[suffix]} /> : suffix}
/> />
) )
......
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
@disabledBgColor: rgb(248 ,250, 252); @disabledBgColor: rgb(248 ,250, 252);
@disabledColor: rgba(0, 0, 0, 0.7); @disabledColor: rgba(0, 0, 0, 0.7);
@parse-form-prefix-cls: ~'@{ant-prefix}-parse-form'; @pandaXform-prefix-cls: ~'@{ant-prefix}-pandaXform';
//@parse-form-prefix-cls: ~'ant-parse-form'; .@{pandaXform-prefix-cls} {
.@{parse-form-prefix-cls} {
.dnd-container { .dnd-container {
height: 700px; height: 700px;
...@@ -179,17 +177,17 @@ ...@@ -179,17 +177,17 @@
justify-content: flex-end; justify-content: flex-end;
padding: 10px 10px; padding: 10px 10px;
.@{parse-form-prefix-cls}-operate-reset { .@{pandaXform-prefix-cls}-operate-reset {
margin-right: 10px; margin-right: 10px;
} }
.parse-form-text-area { .pandaXform-text-area {
width: 100% !important; width: 100% !important;
} }
} }
&-group { &-group {
.@{parse-form-prefix-cls}-title { .@{pandaXform-prefix-cls}-title {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
...@@ -230,7 +228,7 @@ ...@@ -230,7 +228,7 @@
} }
} }
.@{parse-form-prefix-cls}-divider-horizontal { .@{pandaXform-prefix-cls}-divider-horizontal {
margin: 10px; margin: 10px;
&:first-child { &:first-child {
...@@ -238,7 +236,7 @@ ...@@ -238,7 +236,7 @@
} }
} }
.@{parse-form-prefix-cls}-input[disabled] { .@{pandaXform-prefix-cls}-input[disabled] {
background-color: #ffffff60 !important; background-color: #ffffff60 !important;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment