Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
bigDataSystem
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张伯涛
bigDataSystem
Commits
aa645764
Commit
aa645764
authored
Nov 28, 2024
by
罗林杰
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修改数据库到数据库配置页面
parent
9c06e647
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
850 additions
and
217 deletions
+850
-217
data.ts
src/views/realTimeSync/dataBaseToDataBase/data.ts
+385
-3
dataBaseData.ts
src/views/realTimeSync/dataBaseToDataBase/dataBaseData.ts
+94
-27
optionPage.vue
src/views/realTimeSync/dataBaseToDataBase/optionPage.vue
+371
-187
No files found.
src/views/realTimeSync/dataBaseToDataBase/data.ts
View file @
aa645764
...
@@ -256,14 +256,40 @@ export const mappingRuleConfigurationColumns: BasicColumn[] = [
...
@@ -256,14 +256,40 @@ export const mappingRuleConfigurationColumns: BasicColumn[] = [
title
:
'规则名称'
,
title
:
'规则名称'
,
dataIndex
:
'ruleName'
,
dataIndex
:
'ruleName'
,
width
:
200
,
width
:
200
,
edit
:
true
,
editable
:
true
,
editComponent
:
'Input'
,
},
},
{
{
title
:
'规则内容'
,
title
:
'规则类型'
,
dataIndex
:
'ruleContent'
,
dataIndex
:
'ruleType'
,
width
:
100
,
},
{
title
:
'数据表'
,
dataIndex
:
'dataTable'
,
width
:
120
,
edit
:
true
,
editable
:
true
,
editComponent
:
'Select'
,
editComponentProps
:
{
options
:
[
{
label
:
'数据表1'
,
value
:
'数据表1'
},
{
label
:
'数据表2'
,
value
:
'数据表2'
},
],
},
},
{
title
:
'规则详情'
,
dataIndex
:
'ruleDetail'
,
width
:
200
,
edit
:
true
,
editable
:
true
,
editComponent
:
'Input'
,
},
},
{
{
title
:
'执行顺序'
,
title
:
'执行顺序'
,
width
:
10
0
,
width
:
5
0
,
dataIndex
:
'executionSequence'
,
dataIndex
:
'executionSequence'
,
slots
:
{
customRender
:
'executionSequence'
},
slots
:
{
customRender
:
'executionSequence'
},
},
},
...
@@ -323,3 +349,359 @@ export const tableDetailFormSchema: FormSchema[] = [
...
@@ -323,3 +349,359 @@ export const tableDetailFormSchema: FormSchema[] = [
colProps
:
{
lg
:
12
,
md
:
12
},
colProps
:
{
lg
:
12
,
md
:
12
},
},
},
];
];
export
const
sourceSideConfigurationFormSchema
:
FormSchema
[]
=
[
{
field
:
'sourceType'
,
label
:
'源端类型'
,
component
:
'Select'
,
required
:
true
,
componentProps
:
{
placeholder
:
'请选择源端类型'
,
options
:
[
{
label
:
'MYSQL'
,
value
:
'1'
},
{
label
:
'ORACLE'
,
value
:
'2'
},
{
label
:
'KUNDB'
,
value
:
'3'
},
],
},
},
{
field
:
'analysisType'
,
label
:
' '
,
component
:
'Select'
,
componentProps
:
{
options
:
[
{
label
:
'本地解析'
,
value
:
'1'
},
{
label
:
'Xstream'
,
value
:
'2'
},
],
},
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
,
},
{
field
:
'version'
,
label
:
' '
,
component
:
'Select'
,
componentProps
:
{
options
:
[
{
label
:
'高可用版本'
,
value
:
'1'
},
{
label
:
'分布式版本'
,
value
:
'2'
},
],
},
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
,
},
{
field
:
'analysisAlter1'
,
label
:
''
,
slot
:
'analysisAlter1'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'1'
,
},
{
field
:
'analysisAlter1'
,
label
:
''
,
slot
:
'analysisAlter1'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'2'
,
},
{
field
:
'RpcIp1'
,
label
:
'Rpc地址'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'1'
,
},
{
field
:
'RpcIp2'
,
label
:
'Rpc地址'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
&&
model
.
version
===
'2'
,
},
{
field
:
'mysqlServer'
,
label
:
'MYSQL server'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
&&
model
.
version
===
'1'
,
},
{
field
:
'dataSource'
,
label
:
'数据源'
,
component
:
'Select'
,
required
:
true
,
componentProps
:
{
placeholder
:
'请选择数据源'
,
options
:
[
{
label
:
'KunOB数据源'
,
value
:
'1'
},
{
label
:
'MongoDB数据源'
,
value
:
'2'
},
{
label
:
'AmazonS3'
,
value
:
'3'
},
],
},
},
{
field
:
'dataBase'
,
label
:
'数据库'
,
component
:
'Select'
,
componentProps
:
{
mode
:
'multiple'
,
placeholder
:
'请选择数据源'
,
options
:
[
{
label
:
'KunOBDB'
,
value
:
'1'
},
{
label
:
'MongoDB'
,
value
:
'2'
},
{
label
:
'AmazonS3DB'
,
value
:
'3'
},
],
},
},
{
field
:
'xstreaName'
,
label
:
'xstream服务名称'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'2'
,
},
{
field
:
'loadType'
,
label
:
'数据表获取方式'
,
component
:
'RadioGroup'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[
{
label
:
'直接选表'
,
value
:
'1'
},
{
label
:
'规则过滤'
,
value
:
'2'
},
],
},
},
{
field
:
'include'
,
label
:
'包含'
,
component
:
'InputTextArea'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
},
{
field
:
'exclude'
,
label
:
'排除'
,
component
:
'InputTextArea'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
},
{
field
:
'getMetadata'
,
label
:
' '
,
slot
:
'getMetadata'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'1'
,
},
{
field
:
'previewMetadata'
,
label
:
' '
,
slot
:
'previewMetadata'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
},
];
export
const
kafkaOptionsColumns
:
BasicColumn
[]
=
[
{
title
:
''
,
editable
:
true
,
edit
:
true
,
dataIndex
:
'name'
,
width
:
120
,
},
];
export
const
kafkaOptionsFormSchema
:
FormSchema
[]
=
[
{
field
:
'kafkaOptions1'
,
label
:
''
,
slot
:
'kafkaOptions1'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'topicType'
,
label
:
'Topic配置方式'
,
component
:
'RadioGroup'
,
defaultValue
:
'1'
,
required
:
true
,
componentProps
:
{
options
:
[{
label
:
'自定义数量'
,
value
:
'1'
}],
},
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'kafkaOptions2'
,
label
:
''
,
slot
:
'kafkaOptions2'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'num'
,
label
:
'Topic数量'
,
component
:
'InputNumber'
,
componentProps
:
{
min
:
1
,
max
:
50
,
},
defaultValue
:
1
,
required
:
true
,
colProps
:
{
lg
:
4
,
md
:
4
},
},
{
field
:
'kafkaOptionsGenerate'
,
label
:
''
,
slot
:
'kafkaOptionsGenerate'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'kafkaOptions3'
,
label
:
''
,
slot
:
'kafkaOptions3'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
];
export
const
mappingRulesTopicTableColumns
:
BasicColumn
[]
=
[
{
title
:
'Topic'
,
dataIndex
:
'name'
,
width
:
140
,
},
{
title
:
'Db'
,
dataIndex
:
'dataBase'
,
width
:
120
,
edit
:
true
,
editable
:
true
,
editComponent
:
'Select'
,
editComponentProps
:
{
showArrow
:
true
,
mode
:
'multiple'
,
options
:
[
{
label
:
'请选择'
,
value
:
''
},
{
label
:
'fbs_1_flashsyncA_stable3.flashsync_stable1'
,
value
:
'1'
},
{
label
:
'fbs_1_flashsyncA_stable3.flashsync_stable2'
,
value
:
'2'
},
],
},
},
{
title
:
'Table名称表达式'
,
dataIndex
:
'remark'
,
width
:
100
,
edit
:
true
,
editable
:
true
,
},
];
export
const
mappingRulesTableColumns
:
BasicColumn
[]
=
[
{
title
:
'源表名'
,
dataIndex
:
'dataSourceName'
,
width
:
120
,
},
{
title
:
'Topic名称'
,
dataIndex
:
'topicName'
,
width
:
120
,
},
];
export
const
tableMappingFormSchema
:
FormSchema
[]
=
[
{
field
:
'dataSource'
,
label
:
'目标数据源'
,
component
:
'Select'
,
componentProps
:
{
placeholder
:
'请选择数据源'
,
options
:
[
{
label
:
'KunOBDB'
,
value
:
'1'
},
{
label
:
'MongoDB'
,
value
:
'2'
},
{
label
:
'AmazonS3DB'
,
value
:
'3'
},
],
},
required
:
true
,
},
];
export
const
tableMappingColumns
:
BasicColumn
[]
=
[
{
title
:
'规则名称'
,
dataIndex
:
'name'
,
width
:
140
,
},
{
title
:
'执行顺序'
,
width
:
50
,
dataIndex
:
'executionSequence'
,
slots
:
{
customRender
:
'executionSequence'
},
},
];
export
const
writePolicyColumns
:
BasicColumn
[]
=
[
{
title
:
'Topic名称'
,
dataIndex
:
'topicName'
,
width
:
120
,
},
{
title
:
'表数量'
,
dataIndex
:
'tableNum'
,
width
:
120
,
},
{
title
:
'并发数'
,
dataIndex
:
'concurrency'
,
edit
:
true
,
editable
:
true
,
width
:
120
,
},
{
title
:
'条数'
,
dataIndex
:
'num'
,
edit
:
true
,
editable
:
true
,
width
:
120
,
},
{
title
:
'时间间隔/s'
,
dataIndex
:
'timeInterval'
,
edit
:
true
,
editable
:
true
,
width
:
120
,
},
];
export
const
policyOptionFormSchema
:
FormSchema
[]
=
[
{
field
:
'policyOption'
,
label
:
''
,
slot
:
'policyOption'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'ddl'
,
label
:
'ddl变更策略'
,
component
:
'Select'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[{
label
:
'DB-DB源端DDL处理默认策略'
,
value
:
'1'
}],
},
},
{
field
:
'data'
,
label
:
'数据积压策略'
,
component
:
'Select'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[{
label
:
'数据积压默认策略'
,
value
:
'1'
}],
},
},
{
field
:
'exception'
,
label
:
'数据异常策略'
,
component
:
'Select'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[{
label
:
'目标端数据异常默认策略'
,
value
:
'1'
}],
},
},
{
field
:
'system'
,
label
:
'系统异常策略'
,
component
:
'Select'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[{
label
:
'系统异常默认策略'
,
value
:
'1'
}],
},
},
{
field
:
'config'
,
label
:
'一致性检查策略'
,
component
:
'Select'
,
defaultValue
:
'1'
,
componentProps
:
{
options
:
[{
label
:
'一致性检查默认策略'
,
value
:
'1'
}],
},
},
];
src/views/realTimeSync/dataBaseToDataBase/dataBaseData.ts
View file @
aa645764
import
{
tableMappingColumns
}
from
'@/views/realTimeSync/dataBaseToDataBase/data'
;
export
const
tableList
:
any
[]
=
[
export
const
tableList
:
any
[]
=
[
{
{
businessId
:
1
,
businessId
:
1
,
...
@@ -737,33 +739,6 @@ export const notCustomSQLTableList = [
...
@@ -737,33 +739,6 @@ export const notCustomSQLTableList = [
fieldType
:
'VARCHAR(100,0)'
,
fieldType
:
'VARCHAR(100,0)'
,
},
},
];
];
export
const
mappingRuleConfigurationTableList
=
[
{
businessId
:
'1'
,
ruleName
:
'[单表] 字段映射规则'
,
ruleContent
:
'源表名:table_1'
,
},
{
businessId
:
'2'
,
ruleName
:
'[全局] 新增字段规则'
,
ruleContent
:
'新增字段名称:a, 字段类型:string, 字段表达式:asd'
,
},
{
businessId
:
'3'
,
ruleName
:
'[全局] 字段名称映射规则'
,
ruleContent
:
'原字段名称:未配置, 目标字段名称:未配置'
,
},
{
businessId
:
'4'
,
ruleName
:
'[全局] 字段类型映射规则'
,
ruleContent
:
'源字段类型:timestamp, 目标字段类型:string'
,
},
{
businessId
:
'5'
,
ruleName
:
'[单表] 字段映射规则'
,
ruleContent
:
'源表名:table_2'
,
},
];
export
const
tableTreeData
=
[
export
const
tableTreeData
=
[
{
{
title
:
'bm_datasource'
,
title
:
'bm_datasource'
,
...
@@ -994,3 +969,95 @@ export const tableDetailData = [
...
@@ -994,3 +969,95 @@ export const tableDetailData = [
detail
:
'VARCHAR(255,0)'
,
detail
:
'VARCHAR(255,0)'
,
},
},
];
];
export
const
filterRuleCardList
=
[
{
title
:
'数据操作过滤'
,
type
:
'数据操作过滤'
,
description
:
'支持在数据同步阶段对当前任务选中表操作过滤'
,
},
{
title
:
'数据值过滤'
,
type
:
'数据值过滤'
,
description
:
'支持在数据同步阶段对当前任务选中表的数据操作过滤'
,
},
{
title
:
'字段过滤'
,
type
:
'字段过滤'
,
description
:
'支持在数据同步阶段对当前任务选中表的字段操作过滤'
,
},
];
export
const
mappingRulesTopicTableData
:
any
[]
=
[
{
name
:
'0a5c81a6fd364805961918cd9764241d_01'
,
dataBase
:
''
,
},
{
name
:
'0a5c81a6fd364805961918cd9764241d_03'
,
dataBase
:
''
,
},
{
name
:
'0a5c81a6fd364805961918cd9764241d_02'
,
dataBase
:
''
,
},
];
export
const
mappingRulesTableData
:
any
[]
=
[
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_01'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable1'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_03'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable2'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_02'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable3'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_02'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable1'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_01'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable2'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_03'
,
dataSourceName
:
'fbs_1_flashsyncA_stable3.flashsync_stable3'
,
},
];
export
const
tableMappingData
:
any
[]
=
[
{
businessId
:
'1'
,
name
:
'0a5c81a6fd364805961918cd9764241d_01'
,
dataBase
:
''
,
},
{
businessId
:
'2'
,
name
:
'0a5c81a6fd364805961918cd9764241d_03'
,
dataBase
:
''
,
},
];
export
const
writePolicyData
:
any
[]
=
[
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_01'
,
tableNum
:
'3'
,
concurrency
:
'1'
,
num
:
'5000'
,
timeInterval
:
'1'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_02'
,
tableNum
:
'3'
,
concurrency
:
'1'
,
num
:
'5000'
,
timeInterval
:
'1'
,
},
{
topicName
:
'0a5c81a6fd364805961918cd9764241d_03'
,
tableNum
:
'3'
,
concurrency
:
'1'
,
num
:
'5000'
,
timeInterval
:
'1'
,
},
];
src/views/realTimeSync/dataBaseToDataBase/optionPage.vue
View file @
aa645764
...
@@ -121,25 +121,46 @@
...
@@ -121,25 +121,46 @@
</div>
</div>
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '1'"
key=
"2"
tab=
"过滤规则"
>
<TabPane
v-if=
"tabKey === '1'"
key=
"2"
tab=
"过滤规则"
>
<!-- <BasicForm>-->
<List>
<Row
:gutter=
"16"
>
<!-- </BasicForm>-->
<
template
v-for=
"item in filterRuleCardList"
:key=
"item.title"
>
</TabPane>
<Col
:span=
"5"
>
<TabPane
v-if=
"tabKey === '2'"
key=
"3"
tab=
"Kafka配置"
>
<ListItem>
<!-- <BasicForm>-->
<Card
:hoverable=
"true"
class=
"sceneSelectionCard"
>
<div
class=
"sceneSelectionTitle"
>
<!-- </BasicForm>-->
{{
item
.
title
}}
</TabPane>
<a-button
<TabPane
v-if=
"tabKey === '2'"
key=
"4"
tab=
"映射规则"
>
type=
"primary"
style=
"float: right"
@
click=
"addProperty(item.type)"
>
添加规则
</a-button
>
</div>
<div
class=
"sceneSelectionDescription"
>
{{
item
.
description
}}
</div>
</Card>
</ListItem>
</Col>
</
template
>
</Row>
</List>
<Alert
show-icon
message=
" 1、notin、in的过滤规则,请用英文逗号将枚举值隔开,若有特殊字符请添加转义
2、仅支持数值型、时问类型的字段进行大小比较选定范围
3、时间类型的仅支持YYYY-MMM-DD HH:mm:ss格式的值比较"
type=
"info"
style=
"font-size: 15px; white-space: pre-wrap"
/>
<BasicTable
title=
"数据转换规则"
@
register=
"registerMappingRuleConfigurationTable"
>
<BasicTable
title=
"数据转换规则"
@
register=
"registerMappingRuleConfigurationTable"
>
<
template
#
toolbar
>
<
template
#
toolbar
>
<a-button
<a-button
:disabled=
"getRowSelection().selectedRowKeys
<
=
0
"
:disabled=
"getRowSelection().selectedRowKeys
<
=
0
"
type=
"primary"
type=
"primary"
@
click=
"handleDeleteRules"
@
click=
"handleDeleteRules"
>
删除
规则
</a-button
>
删除
</a-button
>
>
<a-button
type=
"primary"
@
click=
"handleAddRule"
>
新增规则
</a-button>
</
template
>
</
template
>
<
template
#
executionSequence=
"{ index }"
>
<
template
#
executionSequence=
"{ index }"
>
<div
style=
"display: flex; justify-content: center; color: dimgray"
>
<div
style=
"display: flex; justify-content: center; color: dimgray"
>
...
@@ -165,10 +186,6 @@
...
@@ -165,10 +186,6 @@
<TableAction
<TableAction
stopButtonPropagation
stopButtonPropagation
:actions=
"[
:actions=
"[
{
icon: 'iconamoon:edit',
onClick: handleEdit.bind(null, record),
},
{
{
icon: 'ic:outline-delete-outline',
icon: 'ic:outline-delete-outline',
onClick: handleDeleteRule.bind(null, record),
onClick: handleDeleteRule.bind(null, record),
...
@@ -179,10 +196,120 @@
...
@@ -179,10 +196,120 @@
</template>
</template>
</BasicTable>
</BasicTable>
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '2'"
key=
"3"
tab=
"Kafka配置"
>
<BasicForm
@
register=
"registerKafkaOptionsForm"
>
<
template
#
kafkaOptionsGenerate
>
<a-button
@
click=
"kafkaOptionsGenerate"
type=
"primary"
>
生成Topic列表
</a-button>
</
template
>
<
template
#
kafkaOptions1
>
<Alert
show-icon
message=
"实时同步解析后数据暂存Kaia中待目标端消费,故提供页面配置供用户配置T0pic信息,支持用户自定义Topic数量和名称;Topic数量清根据源端选择表进行选择,此处数量不宜过大
否则服务负载过大可能导致整体延迟较高;Topic名称将会根据创建任务的UUID自动生成,用户也可以根据业务手动修改保存。"
type=
"info"
style=
"font-size: 13px"
/>
</
template
>
<
template
#
kafkaOptions2
>
<Alert
show-icon
message=
"推荐设置的数量不要超过当前选择表的数量,且不支持在任务启动后切换模式,并不建议随意删除、修改Topic否则会导致数据丢失。"
type=
"warning"
style=
"font-size: 13px"
/>
</
template
>
<
template
#
kafkaOptions3
>
<Alert
show-icon
message=
"修改Topic数量配置后,请一定要点击生成Topic列表,否则映射规则无法应用最新的配置。"
type=
"info"
style=
"font-size: 13px"
/>
</
template
>
</BasicForm>
<div
style=
"width: 40%"
>
<BasicTable
@
register=
"registerKafkaOptionsTable"
/>
</div>
</TabPane>
<TabPane
v-if=
"tabKey === '2'"
key=
"4"
tab=
"映射规则"
>
<div
class=
"flex"
>
<div
style=
"width: 50%"
>
<BasicForm
@
register=
"registerMappingRulesForm"
>
<
template
#
mappingRulesBtn
>
<a-button
@
click=
"mappingRules"
type=
"primary"
>
预览
</a-button>
</
template
>
<
template
#
mappingRules1
>
<Alert
show-icon
message=
"根据Kafka配置中的Topic数量,用户可以根据源端表业务更新流量进行映射规则配置。"
type=
"info"
style=
"font-size: 14px"
/>
</
template
>
<
template
#
mappingRules2
>
<Alert
show-icon
message=
"源端表没有匹配的topic,会按照默认策略进行匹配topic。不支持用户在发布版本并运行任务后在修改topic映射规则,可能会导致数据去失。"
type=
"warning"
style=
"font-size: 14px"
/>
</
template
>
<
template
#
mappingRulesTable
v-if=
"mappingRulesTopicTableShow"
>
<BasicTable
@
register=
"registerMappingRulesTopicTable"
/>
</
template
>
</BasicForm>
</div>
<div
style=
"width: 50%"
>
<BasicTable
v-if=
"mappingRulesTableShow"
@
register=
"registerMappingRulesTable"
/>
</div>
</div>
</TabPane>
<TabPane
v-if=
"tabKey === '3'"
key=
"5"
tab=
"表映射"
>
<TabPane
v-if=
"tabKey === '3'"
key=
"5"
tab=
"表映射"
>
<!-- <BasicForm>-->
<div
class=
"flex"
>
<div
style=
"width: 30%"
>
<!-- </BasicForm>-->
<BasicForm
@
register=
"registerTableMappingForm"
/>
<BasicTable
title=
"库名映射规则"
@
register=
"registerTableMappingTable"
>
<
template
#
toolbar
>
<a-button
type=
"primary"
@
click=
"handleDeleteRules"
>
删除
</a-button>
</
template
>
<
template
#
executionSequence=
"{ index }"
>
<div
style=
"display: flex; justify-content: center; color: dimgray"
>
<Icon
style=
"font-size: 30px !important"
icon=
"material-symbols-light:keyboard-double-arrow-up"
@
click=
"handleMoveTop(index)"
/>
<Icon
style=
"font-size: 20px !important; margin-right: 5px"
icon=
"oui:arrow-down"
@
click=
"handleMoveDown(index)"
/>
<Icon
style=
"font-size: 20px !important"
icon=
"oui:arrow-up"
@
click=
"handleMoveUp(index)"
/>
</div>
</
template
>
<
template
#
bodyCell=
"{ column, record }"
>
<template
v-if=
"column.key === 'action'"
>
<TableAction
stopButtonPropagation
:actions=
"[
{
icon: 'ic:outline-delete-outline',
onClick: handleDeleteRule.bind(null, record),
},
]"
/>
</
template
>
</template>
</BasicTable>
</div>
<div
style=
"width: 70%"
>
<BasicTable
v-if=
"mappingRulesTableShow"
@
register=
"registerMappingRulesTable"
/>
</div>
</div>
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '3'"
key=
"6"
tab=
"字段映射"
>
<TabPane
v-if=
"tabKey === '3'"
key=
"6"
tab=
"字段映射"
>
<!-- <BasicForm>-->
<!-- <BasicForm>-->
...
@@ -190,25 +317,30 @@
...
@@ -190,25 +317,30 @@
<!-- </BasicForm>-->
<!-- </BasicForm>-->
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '3'"
key=
"7"
tab=
"策略"
>
<TabPane
v-if=
"tabKey === '3'"
key=
"7"
tab=
"策略"
>
<!-- <BasicForm>-->
<Description
size=
"middle"
title=
"写入速率策略"
:bordered=
"false"
/>
<Alert
<!-- </BasicForm>-->
show-icon
message=
"并发数是指对处理每个topic在目标端创建的最大连接数,一张表最多创建1个连接。"
type=
"info"
style=
"font-size: 14px"
/>
<BasicTable
@
register=
"registerWritePolicyTable"
/>
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '4'"
key=
"8"
tab=
"策略配置"
>
<TabPane
v-if=
"tabKey === '4'"
key=
"8"
tab=
"策略配置"
>
<!-- <BasicTable>-->
<Description
size=
"middle"
title=
"服务策略配置"
:bordered=
"false"
/>
<BasicForm
@
register=
"registerPolicyOptionsForm"
>
<!-- </BasicTable>-->
<
template
#
policyOption
>
<!-- <BasicTable>-->
<Alert
show-icon
<!-- </BasicTable>-->
message=
"服务策略请在策略指标配置中心配置。"
type=
"info"
style=
"font-size: 15px"
/>
</
template
>
</BasicForm>
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '4'"
key=
"9"
tab=
"服务配置"
>
<TabPane
v-if=
"tabKey === '4'"
key=
"9"
tab=
"服务配置"
>
<!-- <BasicTable>-->
<!-- <BasicTable>-->
<!-- </BasicTable>-->
<!-- <BasicTable>-->
<!-- </BasicTable>-->
</TabPane>
</TabPane>
<TabPane
v-if=
"tabKey === '5'"
key=
"10"
tab=
"数据流向"
>
<TabPane
v-if=
"tabKey === '5'"
key=
"10"
tab=
"数据流向"
>
<Img
src=
"src/assets/images/dataDirection.png"
style=
"width: 1800px"
/>
<Img
src=
"src/assets/images/dataDirection.png"
style=
"width: 1800px"
/>
...
@@ -219,13 +351,12 @@
...
@@ -219,13 +351,12 @@
</Tabs>
</Tabs>
</div>
</div>
<GetMetadataModal
@
register=
"registerGetMetadataModal"
/>
<GetMetadataModal
@
register=
"registerGetMetadataModal"
/>
<AddDataConversionRuleModal
@
register=
"registerAddRuleModal"
/>
<SaveModal
@
register=
"registerSaveModal"
/>
<SaveModal
@
register=
"registerSaveModal"
/>
<VersionManageModal
@
register=
"registerVersionManageModal"
/>
<VersionManageModal
@
register=
"registerVersionManageModal"
/>
</PageWrapper>
</PageWrapper>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
Tabs
,
TabPane
,
Alert
,
Modal
}
from
'ant-design-vue'
;
import
{
Tabs
,
TabPane
,
Alert
,
Col
,
ListItem
,
List
,
Card
,
Row
}
from
'ant-design-vue'
;
import
{
ref
,
onMounted
}
from
'vue'
;
import
{
ref
,
onMounted
}
from
'vue'
;
import
{
BasicForm
,
FormSchema
,
useForm
}
from
'@/components/Form'
;
import
{
BasicForm
,
FormSchema
,
useForm
}
from
'@/components/Form'
;
import
{
import
{
...
@@ -234,186 +365,101 @@
...
@@ -234,186 +365,101 @@
getMetadataColumns
,
getMetadataColumns
,
tableDetailColumns
,
tableDetailColumns
,
tableDetailFormSchema
,
tableDetailFormSchema
,
sourceSideConfigurationFormSchema
,
kafkaOptionsColumns
,
kafkaOptionsFormSchema
,
mappingRulesTopicTableColumns
,
mappingRulesTableColumns
,
tableMappingFormSchema
,
tableMappingColumns
,
writePolicyColumns
,
policyOptionFormSchema
,
}
from
'./data'
;
}
from
'./data'
;
import
Icon
from
'@/components/Icon/Icon.vue'
;
import
Icon
from
'@/components/Icon/Icon.vue'
;
import
{
useMessage
}
from
'@/hooks/web/useMessage'
;
import
{
useMessage
}
from
'@/hooks/web/useMessage'
;
import
{
PageWrapper
}
from
'@/components/Page'
;
import
{
PageWrapper
}
from
'@/components/Page'
;
import
{
useRoute
}
from
'vue-router'
;
import
{
useRoute
}
from
'vue-router'
;
import
{
import
{
mappingRuleConfigurationTableList
,
dataDirectionData
,
dataDirectionData
,
getMetadataTableList
,
getMetadataTableList
,
tableDetailData
,
tableDetailData
,
filterRuleCardList
,
mappingRulesTopicTableData
,
mappingRulesTableData
,
tableMappingData
,
writePolicyData
,
}
from
'./dataBaseData'
;
}
from
'./dataBaseData'
;
import
{
router
}
from
'@/router'
;
import
{
router
}
from
'@/router'
;
import
{
BasicTable
,
useTable
,
TableAction
}
from
'@/components/Table'
;
import
{
BasicTable
,
useTable
,
TableAction
}
from
'@/components/Table'
;
import
{
useModal
}
from
'@/components/Modal'
;
import
{
useModal
}
from
'@/components/Modal'
;
import
GetMetadataModal
from
'./getMetadataModal.vue'
;
import
GetMetadataModal
from
'./getMetadataModal.vue'
;
import
AddDataConversionRuleModal
from
'@/views/dataIntegration/dataLoading/dataEntryLake/addDataConversionRuleModal.vue'
;
import
SaveModal
from
'@/views/dataIntegration/dataLoading/dataEntryLake/saveModal.vue'
;
import
SaveModal
from
'@/views/dataIntegration/dataLoading/dataEntryLake/saveModal.vue'
;
import
VersionManageModal
from
'@/views/dataIntegration/dataLoading/dataEntryLake/versionManageModal.vue'
;
import
VersionManageModal
from
'@/views/dataIntegration/dataLoading/dataEntryLake/versionManageModal.vue'
;
import
{
Description
}
from
'@/components/Description'
;
const
emit
=
defineEmits
([
'success'
,
'register'
]);
const
emit
=
defineEmits
([
'success'
,
'register'
]);
const
{
createMessage
,
createConfirm
}
=
useMessage
();
const
{
createMessage
,
createConfirm
}
=
useMessage
();
const
route
=
useRoute
();
const
route
=
useRoute
();
let
mappingRuleConfigurationTable
=
ref
(
mappingRuleConfigurationTableList
);
const
mappingRuleConfigurationTable
=
ref
([]);
const
kafkaOptionDataTable
=
ref
([]);
const
tabKey
=
ref
(
'1'
);
const
tabKey
=
ref
(
'1'
);
let
getMetadataTable
=
ref
(
getMetadataTableList
);
const
n
=
ref
(
'1'
);
const
mappingRulesTableShow
=
ref
(
false
);
const
mappingRulesTopicTableShow
=
ref
(
false
);
const
getMetadataTable
=
ref
(
getMetadataTableList
);
const
tableDetail
=
ref
(
false
);
const
tableDetail
=
ref
(
false
);
const
sourceSideConfigurationFormSchema
:
FormSchema
[]
=
[
const
[
registerGetMetadataModal
,
{
openModal
:
openGetMetadataModal
}]
=
useModal
();
{
const
[
registerSaveModal
,
{
openModal
:
openSaveModal
}]
=
useModal
();
field
:
'sourceType'
,
const
[
registerVersionManageModal
,
{
openModal
:
openVersionManageModal
}]
=
useModal
();
label
:
'源端类型'
,
component
:
'Select'
,
const
mappingRulesFormSchema
:
FormSchema
[]
=
[
required
:
true
,
componentProps
:
{
placeholder
:
'请选择源端类型'
,
options
:
[
{
label
:
'MYSQL'
,
value
:
'1'
},
{
label
:
'ORACLE'
,
value
:
'2'
},
{
label
:
'KUNDB'
,
value
:
'3'
},
],
},
},
{
field
:
'analysisType'
,
label
:
' '
,
component
:
'Select'
,
componentProps
:
{
options
:
[
{
label
:
'本地解析'
,
value
:
'1'
},
{
label
:
'Xstream'
,
value
:
'2'
},
],
},
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
,
},
{
field
:
'version'
,
label
:
' '
,
component
:
'Select'
,
componentProps
:
{
options
:
[
{
label
:
'高可用版本'
,
value
:
'1'
},
{
label
:
'分布式版本'
,
value
:
'2'
},
],
},
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
,
},
{
field
:
'analysisAlter1'
,
label
:
''
,
slot
:
'analysisAlter1'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'1'
,
},
{
{
field
:
'
analysisAlter
1'
,
field
:
'
mappingRules
1'
,
label
:
''
,
label
:
''
,
slot
:
'analysisAlter1'
,
slot
:
'mappingRules1'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'2'
,
colProps
:
{
lg
:
24
,
md
:
24
},
},
{
field
:
'RpcIp1'
,
label
:
'Rpc地址'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'1'
,
},
{
field
:
'RpcIp2'
,
label
:
'Rpc地址'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
&&
model
.
version
===
'2'
,
},
{
field
:
'mysqlServer'
,
label
:
'MYSQL server'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'3'
&&
model
.
version
===
'1'
,
},
{
field
:
'dataSource'
,
label
:
'数据源'
,
component
:
'Select'
,
required
:
true
,
componentProps
:
{
placeholder
:
'请选择数据源'
,
options
:
[
{
label
:
'KunOB数据源'
,
value
:
'1'
},
{
label
:
'MongoDB数据源'
,
value
:
'2'
},
{
label
:
'AmazonS3'
,
value
:
'3'
},
],
},
},
},
{
{
field
:
'dataBase'
,
field
:
'topicMappingType'
,
label
:
'数据库'
,
label
:
'Topic映射关系'
,
component
:
'Select'
,
componentProps
:
{
mode
:
'multiple'
,
placeholder
:
'请选择数据源'
,
options
:
[
{
label
:
'KunOBDB'
,
value
:
'1'
},
{
label
:
'MongoDB'
,
value
:
'2'
},
{
label
:
'AmazonS3DB'
,
value
:
'3'
},
],
},
},
{
field
:
'xstreaName'
,
label
:
'xstream服务名称'
,
component
:
'Input'
,
ifShow
:
({
model
})
=>
model
.
sourceType
===
'2'
&&
model
.
analysisType
===
'2'
,
},
{
field
:
'loadType'
,
label
:
'数据表获取方式'
,
component
:
'RadioGroup'
,
component
:
'RadioGroup'
,
defaultValue
:
'1'
,
defaultValue
:
'1'
,
componentProps
:
{
required
:
true
,
componentProps
:
({
formModel
})
=>
({
onChange
:
()
=>
{
mappingRulesTopicTableShow
.
value
=
formModel
.
topicMappingType
===
'2'
;
},
options
:
[
options
:
[
{
label
:
'
直接选表
'
,
value
:
'1'
},
{
label
:
'
默认
'
,
value
:
'1'
},
{
label
:
'
规则过滤
'
,
value
:
'2'
},
{
label
:
'
自定义
'
,
value
:
'2'
},
],
],
},
}),
},
colProps
:
{
lg
:
24
,
md
:
24
},
{
field
:
'include'
,
label
:
'包含'
,
component
:
'InputTextArea'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
},
},
{
{
field
:
'
exclude
'
,
field
:
'
mappingRules2
'
,
label
:
'
排除
'
,
label
:
''
,
component
:
'InputTextArea
'
,
slot
:
'mappingRules2
'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
colProps
:
{
lg
:
24
,
md
:
24
}
,
},
},
{
{
field
:
'
getMetadata
'
,
field
:
'
mappingRulesTable
'
,
label
:
'
'
,
label
:
''
,
slot
:
'
getMetadata
'
,
slot
:
'
mappingRulesTable
'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'1'
,
colProps
:
{
lg
:
24
,
md
:
24
}
,
},
},
{
{
field
:
'
previewMetadata
'
,
field
:
'
mappingRulesBtn
'
,
label
:
'
'
,
label
:
''
,
slot
:
'
previewMetadata
'
,
slot
:
'
mappingRulesBtn
'
,
ifShow
:
({
model
})
=>
model
.
loadType
===
'2'
,
colProps
:
{
lg
:
24
,
md
:
24
}
,
},
},
];
];
const
[
registerGetMetadataModal
,
{
openModal
:
openGetMetadataModal
}]
=
useModal
();
const
[
registerAddRuleModal
,
{
openModal
:
openAddRuleModal
}]
=
useModal
();
const
[
registerSaveModal
,
{
openModal
:
openSaveModal
}]
=
useModal
();
const
[
registerVersionManageModal
,
{
openModal
:
openVersionManageModal
}]
=
useModal
();
const
[
const
[
registerSourceSideConfigurationForm
,
registerSourceSideConfigurationForm
,
{
{
validate
:
validateSourceSideConfigurationForm
,
submit
:
submitSourceSideConfiguration
},
getFieldsValue
,
validate
:
validateSourceSideConfigurationForm
,
submit
:
submitSourceSideConfiguration
,
},
]
=
useForm
({
]
=
useForm
({
labelWidth
:
180
,
labelWidth
:
180
,
labelAlign
:
'left'
,
labelAlign
:
'left'
,
...
@@ -424,8 +470,47 @@
...
@@ -424,8 +470,47 @@
span
:
23
,
span
:
23
,
},
},
});
});
const
[
registerKafkaOptionsForm
,
{
getFieldsValue
}]
=
useForm
({
const
[
registerMappingRuleConfigurationTable
,
{
getRowSelection
}]
=
useTable
({
labelWidth
:
110
,
labelAlign
:
'left'
,
baseColProps
:
{
lg
:
24
,
md
:
24
},
schemas
:
kafkaOptionsFormSchema
,
showActionButtonGroup
:
false
,
actionColOptions
:
{
span
:
23
,
},
});
const
[
registerMappingRulesForm
]
=
useForm
({
labelWidth
:
110
,
labelAlign
:
'left'
,
baseColProps
:
{
lg
:
24
,
md
:
24
},
schemas
:
mappingRulesFormSchema
,
showActionButtonGroup
:
false
,
actionColOptions
:
{
span
:
23
,
},
});
const
[
registerTableMappingForm
]
=
useForm
({
labelWidth
:
110
,
labelAlign
:
'left'
,
baseColProps
:
{
lg
:
12
,
md
:
12
},
schemas
:
tableMappingFormSchema
,
showActionButtonGroup
:
false
,
actionColOptions
:
{
span
:
23
,
},
});
const
[
registerPolicyOptionsForm
]
=
useForm
({
labelWidth
:
180
,
labelAlign
:
'left'
,
baseColProps
:
{
lg
:
24
,
md
:
24
},
schemas
:
policyOptionFormSchema
,
showActionButtonGroup
:
false
,
actionColOptions
:
{
span
:
23
,
},
});
const
[
registerMappingRuleConfigurationTable
,
{
getRowSelection
,
reload
}]
=
useTable
({
api
:
async
()
=>
{
api
:
async
()
=>
{
const
response
=
{
const
response
=
{
pageNu
:
'1'
,
pageNu
:
'1'
,
...
@@ -446,7 +531,7 @@
...
@@ -446,7 +531,7 @@
rowSelection
:
true
,
rowSelection
:
true
,
bordered
:
false
,
bordered
:
false
,
actionColumn
:
{
actionColumn
:
{
width
:
10
0
,
width
:
5
0
,
title
:
'操作'
,
title
:
'操作'
,
dataIndex
:
'action'
,
dataIndex
:
'action'
,
// slots: { customRender: 'action' },
// slots: { customRender: 'action' },
...
@@ -461,6 +546,53 @@
...
@@ -461,6 +546,53 @@
scroll
:
{
y
:
500
},
scroll
:
{
y
:
500
},
});
});
const
[
registerKafkaOptionsTable
]
=
useTable
({
title
:
'Topic列表'
,
dataSource
:
kafkaOptionDataTable
,
columns
:
kafkaOptionsColumns
,
pagination
:
false
,
bordered
:
false
,
striped
:
false
,
showIndexColumn
:
false
,
scroll
:
{
y
:
200
},
});
const
[
registerMappingRulesTopicTable
]
=
useTable
({
dataSource
:
mappingRulesTopicTableData
,
columns
:
mappingRulesTopicTableColumns
,
pagination
:
false
,
showIndexColumn
:
false
,
scroll
:
{
y
:
200
},
});
const
[
registerMappingRulesTable
]
=
useTable
({
dataSource
:
mappingRulesTableData
,
columns
:
mappingRulesTableColumns
,
pagination
:
true
,
showIndexColumn
:
false
,
scroll
:
{
y
:
500
},
});
const
[
registerWritePolicyTable
]
=
useTable
({
dataSource
:
writePolicyData
,
columns
:
writePolicyColumns
,
pagination
:
true
,
showIndexColumn
:
false
,
scroll
:
{
y
:
500
},
});
const
[
registerTableMappingTable
]
=
useTable
({
dataSource
:
tableMappingData
,
columns
:
tableMappingColumns
,
pagination
:
false
,
showIndexColumn
:
false
,
actionColumn
:
{
width
:
50
,
title
:
'操作'
,
dataIndex
:
'action'
,
},
scroll
:
{
y
:
200
},
});
const
[
registerDataSourceTable
]
=
useTable
({
const
[
registerDataSourceTable
]
=
useTable
({
api
:
async
()
=>
{
api
:
async
()
=>
{
const
response
=
{
const
response
=
{
...
@@ -511,11 +643,27 @@
...
@@ -511,11 +643,27 @@
});
});
function
handleGetMetadata
(
type
)
{
function
handleGetMetadata
(
type
)
{
tableDetail
.
value
=
true
;
if
(
type
===
'get'
)
{
if
(
type
===
'get'
)
{
openGetMetadataModal
(
true
);
openGetMetadataModal
(
true
);
}
}
tableDetail
.
value
=
true
;
}
function
kafkaOptionsGenerate
()
{
const
num
=
getFieldsValue
().
num
;
const
data
=
[];
for
(
let
i
=
1
;
i
<
num
+
1
;
i
++
)
{
data
.
push
({
businessId
:
i
,
name
:
'0a5c81a6fd364805961918cd9764241d_0'
+
i
,
});
}
}
kafkaOptionDataTable
.
value
=
data
;
}
function
mappingRules
()
{
mappingRulesTableShow
.
value
=
true
;
}
onMounted
(()
=>
{});
onMounted
(()
=>
{});
function
handleChangeTab
(
key
)
{
function
handleChangeTab
(
key
)
{
...
@@ -548,25 +696,50 @@
...
@@ -548,25 +696,50 @@
});
});
}
}
function
handleAddRule
()
{
openAddRuleModal
();
}
function
handleVersionManagement
()
{
function
handleVersionManagement
()
{
openVersionManageModal
(
true
,
{
openVersionManageModal
(
true
,
{
title
:
'数据加载版本管理'
,
title
:
'数据加载版本管理'
,
});
});
}
}
function
handleEdit
(
record
)
{
function
handleDeleteRules
()
{
createMessage
.
success
(
'编辑:'
+
record
);
const
selectedKeys
=
getRowSelection
().
selectedRowKeys
;
if
(
selectedKeys
!==
undefined
)
{
selectedKeys
.
forEach
((
key
)
=>
{
const
index
=
mappingRuleConfigurationTable
.
value
.
findIndex
(
(
item
)
=>
item
.
businessId
===
key
,
);
if
(
index
!==
-
1
)
{
mappingRuleConfigurationTable
.
value
.
splice
(
index
,
1
);
}
});
createMessage
.
success
(
'批量删除数据转换规则成功'
);
}
}
function
handleDeleteRule
(
record
)
{
createMessage
.
success
(
'删除规则成功'
+
record
);
}
}
function
handleDeleteRules
()
{
/**新增属性*/
createMessage
.
success
(
'批量删除数据转换规则成功'
+
getRowSelection
().
selectedRowKeys
);
function
addProperty
(
type
)
{
const
data
=
{
businessId
:
n
.
value
+
1
,
name
:
''
,
dataTable
:
''
,
ruleDetail
:
''
,
ruleType
:
type
,
};
mappingRuleConfigurationTable
.
value
.
push
(
data
);
n
.
value
++
;
reload
();
}
/** 删除按钮*/
function
handleDeleteRule
(
record
:
Recordable
)
{
mappingRuleConfigurationTable
.
value
.
splice
(
mappingRuleConfigurationTable
.
value
.
findIndex
(
(
item
)
=>
item
.
businessId
===
record
.
businessId
,
),
1
,
);
createMessage
.
success
(
'删除成功!'
);
reload
();
}
}
function
handleMoveTop
(
source
)
{
function
handleMoveTop
(
source
)
{
...
@@ -623,4 +796,15 @@
...
@@ -623,4 +796,15 @@
align-items
:
center
;
align-items
:
center
;
}
}
}
}
.sceneSelectionDescription
{
font-size
:
14px
;
color
:
gray
;
text-align
:
left
;
line-height
:
1
.2
;
}
.sceneSelectionTitle
{
font-size
:
18px
;
font-weight
:
bold
;
margin-bottom
:
8px
;
}
</
style
>
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment