Files
dvcp_v2_webapp/project/oms/apps/develop/AppAiCode/detailLayout.vue

1050 lines
31 KiB
Vue
Raw Normal View History

2022-06-28 09:14:39 +08:00
<template>
<div class="detailLayout">
<el-scrollbar class="left">
<div class="left-item" v-for="(component, index) in components" :key="index">
<div class="left-item__title">
<h2>{{ component.label }}</h2>
<span>{{ component.tips }}</span>
</div>
<div class="left-item__list">
<draggable
class="components-draggable"
:list="component.children"
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
:sort="false"
:move="onMove"
:clone="cloneComponent"
@end="onEnd">
<div class="left-item__item" v-for="(item, i) in component.children" :key="i" @click="clone(item)">
<i class="iconfont" :class="item.icon"></i>
<span>{{ item.fixedLabel }}</span>
</div>
</draggable>
</div>
</div>
</el-scrollbar>
<el-scrollbar class="middle">
<div class="middle-content">
<div class="middle-content__wrapper">
<el-form label-width="100px" label-position="right">
<draggable
class="middle-draggable"
style="height: 100%;"
:animation="340"
scroll
v-model="targetList"
element="div"
@end="onElEnd"
:sort="true">
<ai-card
:class="[groupIndex === j && isGroup ? 'active' : '']"
:data-index="j"
@click.native.stop="groupIndex = j, activeIndex = -1, isGroup = true"
:title="group.groupName" v-for="(group, j) in targetList"
:key="j">
<template #content>
<draggable
class="ai-form"
style="height: 100%;"
v-model="group.column"
:animation="340"
scroll
@end="onElEnd"
element="div"
filter=".components-filter"
draggable=".components-item"
group="componentsGroup"
:sort="true">
<div
class="components-item"
v-for="(item, i) in group.column"
:style="{width: item.grid * 100 + '%'}"
:class="{
'components-filter': item.isInit,
'active': groupIndex === j && activeIndex === i
}"
@click.stop="groupIndex = j, activeIndex = i, isGroup = false"
:key="i">
<div class="left-item__item--remove" title="删除字段" v-show="!item.isInit && groupIndex === j && activeIndex === i"
@click.stop="removeItem(j, i)">
<i class="iconfont iconDelete"></i>
<span>删除字段</span>
</div>
<el-form-item style="width: 100%;" :label="item.fieldName" :rules="[{ required: item.mustFill === '1' }]">
<template v-if="(item.type === 'textarea')">
<el-input :disabled="item.disable === '1'" :rows="item.lineNumber" size="small" type="textarea" :placeholder="item.fieldTips"
v-model="item.defaultValue"></el-input>
</template>
<template v-if="(item.type === 'resident')">
<el-input :disabled="item.disable === '1'" size="small" placeholder="请选择人员" v-model="item.defaultValue">
<template slot="append">选择人员</template>
</el-input>
</template>
<template v-if="(item.type === 'gird')">
<el-input :disabled="item.disable === '1'" size="small" placeholder="请选择网格" v-model="item.defaultValue">
<template slot="append">选择网格</template>
</el-input>
</template>
<template v-if="item.type === 'radio'">
<el-radio-group v-model="item.defaultValue" :disabled="item.disable === '1'">
<el-radio :label="field.label" v-for="(field, index) in item.options" :key="index">{{ field.label }}</el-radio>
</el-radio-group>
</template>
<template v-if="item.type === 'number'">
<el-input-number v-model="item.defaultValue" :placeholder="item.fieldTips"></el-input-number>
</template>
<template v-if="item.type === 'rtf'">
<ai-editor v-model="item.defaultValue" :placeholder="item.fieldTips" :instance="instance"/>
</template>
<template v-if="item.type === 'time'">
<el-date-picker
v-model="item.defaultValue"
size="small"
:placeholder="item.fieldTips">
</el-date-picker>
</template>
<template v-if="item.type === 'date'">
<el-date-picker
v-model="item.defaultValue"
size="small"
:placeholder="item.fieldTips">
</el-date-picker>
</template>
<template v-if="item.type === 'datetime'">
<el-date-picker
v-model="item.defaultValue"
size="small"
:placeholder="item.fieldTips">
</el-date-picker>
</template>
<template v-if="item.type === 'onOff'">
<el-switch active-value="1" inactive-value="0" v-model="item.defaultValue"></el-switch>
</template>
<template v-if="['input', 'phone', 'name', 'idNumber'].includes(item.type)">
<el-input :disabled="item.disable === '1'" :maxlength="Number(item.maxLength)" size="small" :placeholder="item.fieldTips"
v-model="item.defaultValue"></el-input>
</template>
<template v-if="item.type === 'area'">
<ai-area-get
:instance="instance"
always-show/>
</template>
<template v-if="item.type === 'select'">
<el-select :disabled="item.disable === '1'" style="width: 100%;" size="small" :placeholder="item.fieldTips"
v-model="item.defaultValue">
<el-option
v-for="(filed, index) in item.options"
:key="index"
:label="filed.label"
:value="filed.label">
</el-option>
</el-select>
</template>
<template v-if="item.type === 'checkbox'">
<el-checkbox-group v-model="item.defaultValue" :disabled="item.disable === '1'">
<el-checkbox :label="field.label" v-for="(field, index) in item.options" :key="index">{{ field.label }}</el-checkbox>
</el-checkbox-group>
</template>
<template v-if="item.type === 'upload'">
<div class="left-item__item--upload">
<i class="iconfont iconAdd"></i>
<span>添加附件</span>
</div>
</template>
</el-form-item>
</div>
</draggable>
</template>
</ai-card>
</draggable>
</el-form>
</div>
</div>
</el-scrollbar>
<el-scrollbar class="right">
<div class="right-item" v-if="isGroup">
<div class="right-item__title no-solid">
<h2>分组名称</h2>
</div>
<div class="right-item__content">
<el-input placeholder="请输入分组名称" :maxlength="32" show-word-limit v-model="currTarget.groupName"></el-input>
</div>
</div>
<div class="layout-right__del" @click="removeGroup" v-if="isGroup && targetList.length > 1 && !currTarget.isInit">
<span>删除分组</span>
</div>
<template v-if="activeIndex > -1">
<div class="right-item">
<div class="right-item__title no-solid">
<h2>绑定字段</h2>
</div>
<div class="right-item__content">
2022-07-01 18:00:00 +08:00
<ai-select v-model="currTarget.prop" :selectList="propOps" @change="handleAutoFieldName"/>
2022-06-28 09:14:39 +08:00
</div>
</div>
</template>
<div class="right-item" v-if="['select', 'date', 'time', 'input', 'datetime', 'textarea', 'rtf'].includes(currTarget.type)">
<div class="right-item__title no-solid">
<h2>占位符</h2>
</div>
<div class="right-item__content">
<el-input placeholder="请输入占位符" :maxlength="32" show-word-limit v-model="currTarget.fieldTips"></el-input>
</div>
</div>
<div class="right-item right-item__select" v-if="['select', 'checkbox', 'radio'].includes(currTarget.type)">
<div class="right-item__title no-solid">
<h2>选项设置</h2>
</div>
<div class="right-item__select--wrapper">
<draggable
v-model="currTarget.options"
:animation="340"
group="select"
handle=".mover"
:sort="true">
<div class="select-item" v-for="(item, index) in currTarget.options" :key="index">
<i class="iconfont iconjdq_led_show mover"></i>
<el-input placeholder="请输入选项名" :maxlength="30" show-word-limit v-model="item.label"></el-input>
<i class="iconfont iconDelete" @click="removeOptions(index)"></i>
</div>
</draggable>
</div>
<el-button type="text" class="add-select" @click="addOptions">添加选项</el-button>
</div>
<div class="right-item__group" v-if="activeIndex > -1" key="radio">
<div class="right-item" v-if="['select', 'radio'].includes(currTarget.type)">
<div class="right-item__title">
<h2>默认值</h2>
</div>
<div class="right-item__content">
<el-select placeholder="请选择默认值" style="width: 100%;" clearable v-model="currTarget.defaultValue">
<el-option
v-for="(filed, index) in currTarget.options"
:key="index"
:label="filed.label"
:value="filed.label">
</el-option>
</el-select>
</div>
</div>
<div class="right-item" v-if="['checkbox'].includes(currTarget.type)" key="checkbox">
<div class="right-item__title">
<h2>默认值</h2>
</div>
<div class="right-item__content">
<el-select placeholder="请选择默认值" style="width: 100%;" clearable collapse-tags :multiple="true" v-model="currTarget.defaultValue">
<el-option
v-for="(filed, index) in currTarget.options"
:key="index"
:label="filed.label"
:value="filed.label">
</el-option>
</el-select>
</div>
</div>
<div class="right-item__group">
<div class="right-item" v-if="['onOff'].includes(currTarget.type)">
<div class="right-item__title">
<h2>默认值</h2>
</div>
<div class="right-item__content">
<el-switch v-model="currTarget.defaultValue" active-value="1" inactive-value="0" active-text="是" inactive-text="否"></el-switch>
</div>
</div>
</div>
<div class="right-item__group">
<div class="right-item" v-if="['number'].includes(currTarget.type)">
<div class="right-item__title">
<h2>默认值</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" v-model="currTarget.defaultValue" label="默认值"></el-input-number>
</div>
</div>
</div>
<div class="right-item__group">
<div class="right-item" v-if="['number'].includes(currTarget.type)">
<div class="right-item__title">
<h2>最大值</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" v-model="currTarget.maxValue" label="最大值"></el-input-number>
</div>
</div>
</div>
<div class="right-item__group">
<div class="right-item" v-if="['number'].includes(currTarget.type)">
<div class="right-item__title">
<h2>最小值</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" v-model="currTarget.minValue" label="最小值"></el-input-number>
</div>
</div>
</div>
<div class="right-item__group">
<div class="right-item" v-if="['number'].includes(currTarget.type)">
<div class="right-item__title">
<h2>小数精度</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" step-strictly :min="0" v-model="currTarget.decimalPlaces" label="小数精度"></el-input-number>
</div>
</div>
</div>
<div class="right-item" v-if="['input', 'textarea', 'phone'].includes(currTarget.type)">
<div class="right-item__title">
<h2>最多输入字符</h2>
</div>
<div class="right-item__content">
<el-input placeholder="字符个数" v-model="currTarget.maxLength">
<span slot="append"></span>
</el-input>
</div>
</div>
<div class="right-item" v-if="['area'].includes(currTarget.type)">
<div class="right-item__title">
<h2>默认值</h2>
</div>
<div class="right-item__content">
<ai-area-get v-model="currTarget.defaultValue" :instance="instance" always-show></ai-area-get>
</div>
</div>
<div class="right-item" v-if="currTarget.type === 'upload'">
<div class="right-item__title">
<h2>文件大小MB</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" v-model="currTarget.fileChoseSize" :min="1" :max="40" label="文件大小"></el-input-number>
</div>
</div>
<div class="right-item" v-if="currTarget.type === 'upload'">
<div class="right-item__title">
<h2>最大上传数</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" step-strictly v-model="currTarget.fileMaxCount" :min="1" :max="20" label="最大上传数"></el-input-number>
</div>
</div>
<div class="right-item">
<div class="right-item__title">
<h2>是否整行显示</h2>
<el-switch v-model="currTarget.grid" :active-value="1" :inactive-value="0.5"></el-switch>
</div>
</div>
<div class="right-item" v-if="currTarget.type === 'textarea'">
<div class="right-item__title">
<h2>输入框行高</h2>
</div>
<div class="right-item__content">
<el-input-number style="width: 100%;" v-model="currTarget.lineNumber" :min="3" :max="10" label="输入框行高"></el-input-number>
</div>
</div>
<div class="right-item">
<div class="right-item__title no-solid">
<div class="right-item__title--left">
<h2>是否必填</h2>
</div>
<el-switch v-model="currTarget.mustFill" active-value="1" inactive-value="0"></el-switch>
</div>
</div>
<div class="right-item" v-if="currTarget.type !== 'rtf'">
<div class="right-item__title no-solid">
<div class="right-item__title--left">
<h2>是否禁用</h2>
</div>
<el-switch v-model="currTarget.disable" active-value="1" inactive-value="0"></el-switch>
</div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import options from './config'
export default {
name: 'detailLayout',
model: {
prop: 'value',
event: 'change',
},
props: {
instance: Function,
dict: Object,
form: {default: () => ({})},
value: Array
},
components: {draggable},
data() {
let {config: components} = options
return {
isGroup: false,
components,
groupIndex: -1,
activeIndex: -1,
currTarget: {},
targetList: []
}
},
watch: {
activeIndex() {
if (this.activeIndex > -1 && this.groupIndex > -1 && !this.isGroup) {
const filed = this.targetList[this.groupIndex].column[this.activeIndex]
this.currTarget = filed
if (filed.type === 'checkbox' && !filed.defaultValue.length) {
setTimeout(() => {
this.$set(this.currTarget, 'defaultValue', [2])
this.$forceUpdate()
this.$set(this.currTarget, 'defaultValue', [])
}, 100)
}
} else if (this.groupIndex > -1 && this.isGroup) {
this.currTarget = this.targetList[this.groupIndex]
} else this.currTarget = {}
},
groupIndex() {
if (this.activeIndex > -1 && this.groupIndex > -1 && !this.isGroup) {
const filed = this.targetList[this.groupIndex].column[this.activeIndex]
this.currTarget = filed
this.$set(this.currTarget, 'defaultValue', filed.defaultValue)
if (filed.type === 'checkbox' && !filed.defaultValue.length) {
this.$nextTick(() => {
this.$set(this.currTarget, 'defaultValue', [])
this.$nextTick(() => {
this.$forceUpdate()
})
})
}
} else if (this.groupIndex > -1 && this.isGroup) {
this.currTarget = this.targetList[this.groupIndex]
} else this.currTarget = {}
},
targetList: {
deep: true,
handler(v) {
this.$emit('change', v)
}
}
},
computed: {
currComponentTitle() {
return '表单设置'
},
propOps: v => v.form.props?.map(e => ({dictValue: e.prop, dictName: e.label})),
},
methods: {
initValue() {
let unwatch = this.$watch('value', (v) => {
if (this.targetList.length > 0) unwatch && unwatch()
else if (!!v) {
this.targetList = this.$copy(v)
unwatch && unwatch()
}
}, {immediate: true})
},
removeItem(j, i) {
this.groupIndex = -1
this.activeIndex = -1
this.targetList[j].column.splice(i, 1)
},
removeGroup() {
if (this.targetList.length === 1) {
return this.$message.error('分组不能小于1')
}
this.targetList.splice(this.groupIndex, 1)
this.groupIndex = 0
this.isGroup = true
this.activeIndex = -1
},
isUnique(type) {
const list = this.components[0].children.map(v => v.type)
return list.indexOf(type) > -1
},
addOptions() {
const len = this.targetList[this.groupIndex].column[this.activeIndex].options.length
let label = `选项${len + 1}`
const index = this.targetList[this.groupIndex].column[this.activeIndex].options.findIndex(v => label === v.label)
if (index > -1) {
label = `新选项${len + 1}`
}
this.targetList[this.groupIndex].column[this.activeIndex].options.push({
label: label,
value: ''
})
},
removeOptions(index) {
const len = this.targetList[this.groupIndex].column[this.activeIndex].options.length
if (len === 2) {
return this.$message.error('选项不能少于2个')
}
this.targetList[this.groupIndex].column[this.activeIndex].options.splice(index, 1)
},
onEnd(e) {
const el = e.to.parentElement.parentElement
this.isGroup = false
this.activeIndex = e.newIndex
this.groupIndex = Number(el.getAttribute('data-index'))
},
onElEnd(e) {
if (this.isGroup) {
this.groupIndex = e.newIndex
} else {
this.activeIndex = e.newIndex
}
},
clone(e) {
if (e.type === 'group') {
this.targetList.push(JSON.parse(JSON.stringify(e)))
this.$nextTick(() => {
this.isGroup = true
this.groupIndex = this.targetList.length - 1
this.activeIndex = -1
})
return
}
const list = []
this.targetList.map(v => {
v.column.forEach(item => {
list.push(item.type)
})
})
if (list.indexOf(e.type) > -1 && ['name', 'idNumber', 'phone', 'area'].includes(e.type)) {
return this.$message.error('信息组件不能重复添加')
}
if (this.isGroup) {
this.targetList[this.groupIndex].column.push(JSON.parse(JSON.stringify(e)))
} else {
if (this.targetList[0]) this.targetList[0]?.column.push(JSON.parse(JSON.stringify(e)))
else return this.$message.error("请先添加分组!")
}
this.$nextTick(() => {
this.groupIndex = this.isGroup ? this.groupIndex : 0
this.activeIndex = this.targetList[0].column.length - 1
})
},
onMove(e) {
const el = e.draggedContext.element
if (el.type === 'group') {
return false
}
const list = []
this.targetList.map(v => {
v.column.forEach(item => {
list.push(item.type)
})
})
return !(list.indexOf(el.type) > -1 && ['name', 'idNumber', 'phone', 'area'].includes(el.type));
},
cloneComponent(e) {
if (e.type === 'group') {
this.targetList.push(JSON.parse(JSON.stringify(e)))
return
}
const list = []
this.targetList.map(v => {
v.column.forEach(item => {
list.push(item.type)
})
})
if (list.indexOf(e.type) > -1 && ['name', 'idNumber', 'phone', 'area'].includes(e.type)) {
this.$message.error('信息组件不能重复添加')
}
return JSON.parse(JSON.stringify(e))
2022-07-01 18:00:00 +08:00
},
handleAutoFieldName(v) {
this.currTarget.fieldName = this.propOps.find(e => e.dictValue == v)?.dictName || this.currTarget.fieldName
2022-06-28 09:14:39 +08:00
}
},
created() {
this.initValue()
}
}
</script>
<style lang="scss" scoped>
.layout-right__del {
position: absolute;
bottom: 100px;
left: 0;
z-index: 11;
width: 100%;
padding: 0 16px;
span {
display: block;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 6px;
color: #F46;
cursor: pointer;
border: 1px solid #F46;
&:hover {
opacity: 0.7;
}
}
}
.detailLayout {
display: flex;
.ai-form .el-form-item {
margin-bottom: 0;
}
.right-item__maintitle {
height: 62px;
line-height: 62px;
margin-bottom: 20px;
border-bottom: 1px solid #EEEEEE;
color: #222222;
h2 {
font-size: 14px;
}
}
::v-deep .ai-card {
cursor: move;
&.active {
background: #f6f7ff;
}
}
::v-deep .ai-detail__content {
height: calc(100% - 52px) !important;
padding: 0 !important;
overflow: hidden !important;
}
.ai-dialog__success {
::v-deep .ai-dialog__content {
max-height: initial !important;
}
}
.middle-draggable {
// display: flex;
// justify-content: space-between;
// flex-wrap: wrap;
.left-item__item {
width: 50%;
min-height: 73px;
}
.el-date-editor.el-input {
width: 100%;
}
& > span {
display: block;
width: 100%;
height: 100%;
min-height: 600px;
padding-bottom: 20px;
}
.components-item {
position: relative;
margin-bottom: 16px;
padding: 16px 16px;
cursor: move;
&::after {
position: absolute;
left: 0;
top: 0;
z-index: 1111;
width: 100%;
height: 100%;
content: ' ';
}
.left-item__item--remove {
display: flex;
position: absolute;
align-items: center;
justify-content: center;
right: 4px;
top: 4px;
z-index: 1113;
width: 84px;
height: 28px;
background: #FF4466;
border-radius: 2px;
cursor: pointer;
color: #fff;
font-size: 12px;
i {
margin-right: 6px;
font-size: 12px;
&:hover {
color: #fff;
}
}
&:hover {
opacity: 0.8;
}
}
&:hover {
background: #f6f7ff;
}
&.active {
background: #f6f7ff;
}
.left-item__item--upload {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 120px;
height: 120px;
line-height: 1;
border-radius: 6px;
border: 1px dashed #bbb;
i {
font-size: 24px;
color: #8899bb;
}
span {
margin-top: 10px;
font-size: 12px;
color: #555;
}
}
.text-item {
input {
display: block;
width: 100%;
height: 40px;
border: none;
border-bottom: 1px solid #ddd;
&:focus {
outline: none;
}
&:disabled {
background: #fff;
}
}
}
.textarea-item {
textarea {
width: 100%;
height: 120px;
resize: none;
border: 1px solid #ddd;
padding: 10px;
&:focus {
outline: none;
}
&:disabled {
background: #fff;
}
}
}
.radio-item {
display: flex;
align-items: center;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
label {
margin-left: 10px;
}
img {
margin-left: 10px;
}
}
}
.left-item__item--title {
display: flex;
align-items: center;
margin-bottom: 10px;
i {
margin-right: 5px;
color: #E22120;
}
h2 {
color: #333333;
font-size: 15px;
}
}
}
.middle-content {
width: 96%;
margin: 0 auto;
padding: 0px 0 1px;
.middle-content__wrapper {
// min-height: 800px;
// background: #fff;
& > div {
&.active {
background: #f6f7ff;
}
}
.radio-item {
img {
width: 40px;
height: 40px;
}
}
}
}
div {
box-sizing: border-box;
}
.right-item__select--wrapper {
.select-item {
display: flex;
align-items: center;
}
}
::v-deep .ai-detail__title {
margin: 0 !important;
margin-bottom: 4px !important;
padding: 0 20px;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.08);
}
::v-deep .ai-detail__content--wrapper {
display: flex;
max-width: 100% !important;
height: 100% !important;
padding: 0 !important;
background: #F5F6F9;
}
.middle {
flex: 1;
height: 100%;
::v-deep .el-scrollbar__view {
height: 100%;
}
}
.add-select {
height: auto;
line-height: 1;
margin: 10px 0 0 26px;
padding: 0;
}
.right-item__select--wrapper {
.select-item {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
i {
margin-right: 8px;
color: #8c9dbd;
}
.mover {
cursor: move;
}
.iconDelete {
cursor: pointer;
margin-left: 10px;
}
}
::v-deep .el-upload-list__item {
width: 40px !important;
height: 40px !important;
object-fit: cover;
}
.config-item__select {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: 1px solid #D0D4DC;
&:hover {
opacity: 0.7;
}
i {
font-size: 18px;
}
}
}
.right {
width: 320px;
height: 100%;
overflow: hidden;
background: #FFFFFF;
.el-checkbox {
display: block;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.right-item {
2022-07-01 18:00:00 +08:00
margin: 20px 0;
2022-06-28 09:14:39 +08:00
padding: 0 20px;
.right-item__tips {
margin-top: 10px;
color: #888888;
font-size: 12px;
}
}
.right-item__title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.right-item__title--left {
display: flex;
align-items: center;
i {
color: #888888;
font-size: 12px;
font-style: normal;
}
}
h2 {
color: #222222;
font-size: 14px;
}
}
}
.left {
width: 280px;
height: 100%;
overflow: hidden;
background: #FFFFFF;
.left-item {
padding: 0 20px;
&:last-child {
padding-bottom: 20px;
}
.left-item__title {
display: flex;
align-items: baseline;
margin-bottom: 20px;
h2 {
color: #222222;
font-size: 14px;
font-weight: 700;
}
span {
color: #888888;
font-size: 12px;
}
}
}
.left-item {
margin-top: 20px;
}
}
}
.left-item__item {
display: flex;
align-items: center;
height: 40px;
margin-bottom: 10px;
width: 100%;
padding: 0 13px;
background: #FFFFFF;
border-radius: 2px;
color: #222222;
font-size: 12px;
border: 1px solid #E4E8EF;
cursor: move;
&:hover {
border: 1px dashed #2367ff;
color: #2367ff;
i {
color: #2367ff;
}
}
i {
margin-right: 13px;
font-size: 14px;
color: #8899BB;
}
&:last-child {
margin-bottom: 0px;
}
}
</style>