Browse Source
* add funtion conditions task * update conditions tasks * update conditions for ui * update conditions * update * revert * updatepull/2/head
bao liang
5 years ago
committed by
GitHub
25 changed files with 1098 additions and 87 deletions
@ -0,0 +1,79 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.common.task.conditions; |
||||
|
||||
import org.apache.dolphinscheduler.common.enums.DependentRelation; |
||||
import org.apache.dolphinscheduler.common.model.DependentTaskModel; |
||||
import org.apache.dolphinscheduler.common.task.AbstractParameters; |
||||
|
||||
import java.util.List; |
||||
|
||||
public class ConditionsParameters extends AbstractParameters { |
||||
|
||||
//depend node list and state, only need task name
|
||||
private List<DependentTaskModel> dependTaskList; |
||||
private DependentRelation dependRelation; |
||||
|
||||
// node list to run when success
|
||||
private List<String> successNode; |
||||
|
||||
// node list to run when failed
|
||||
private List<String> failedNode; |
||||
|
||||
|
||||
@Override |
||||
public boolean checkParameters() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public List<String> getResourceFilesList() { |
||||
return null; |
||||
} |
||||
|
||||
public List<DependentTaskModel> getDependTaskList() { |
||||
return dependTaskList; |
||||
} |
||||
|
||||
public void setDependTaskList(List<DependentTaskModel> dependTaskList) { |
||||
this.dependTaskList = dependTaskList; |
||||
} |
||||
|
||||
public DependentRelation getDependRelation() { |
||||
return dependRelation; |
||||
} |
||||
|
||||
public void setDependRelation(DependentRelation dependRelation) { |
||||
this.dependRelation = dependRelation; |
||||
} |
||||
|
||||
public List<String> getSuccessNode() { |
||||
return successNode; |
||||
} |
||||
|
||||
public void setSuccessNode(List<String> successNode) { |
||||
this.successNode = successNode; |
||||
} |
||||
|
||||
public List<String> getFailedNode() { |
||||
return failedNode; |
||||
} |
||||
|
||||
public void setFailedNode(List<String> failedNode) { |
||||
this.failedNode = failedNode; |
||||
} |
||||
} |
@ -0,0 +1,145 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.server.worker.task.conditions; |
||||
|
||||
import org.apache.dolphinscheduler.common.Constants; |
||||
import org.apache.dolphinscheduler.common.enums.DependResult; |
||||
import org.apache.dolphinscheduler.common.enums.ExecutionStatus; |
||||
import org.apache.dolphinscheduler.common.model.DependentItem; |
||||
import org.apache.dolphinscheduler.common.model.DependentTaskModel; |
||||
import org.apache.dolphinscheduler.common.task.AbstractParameters; |
||||
import org.apache.dolphinscheduler.common.task.dependent.DependentParameters; |
||||
import org.apache.dolphinscheduler.common.utils.DependentUtils; |
||||
import org.apache.dolphinscheduler.common.utils.JSONUtils; |
||||
import org.apache.dolphinscheduler.dao.entity.ProcessInstance; |
||||
import org.apache.dolphinscheduler.dao.entity.TaskInstance; |
||||
import org.apache.dolphinscheduler.server.worker.task.AbstractTask; |
||||
import org.apache.dolphinscheduler.server.worker.task.TaskProps; |
||||
import org.apache.dolphinscheduler.service.bean.SpringApplicationContext; |
||||
import org.apache.dolphinscheduler.service.process.ProcessService; |
||||
import org.slf4j.Logger; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
public class ConditionsTask extends AbstractTask { |
||||
|
||||
|
||||
/** |
||||
* dependent parameters |
||||
*/ |
||||
private DependentParameters dependentParameters; |
||||
|
||||
/** |
||||
* process dao |
||||
*/ |
||||
private ProcessService processService; |
||||
|
||||
/** |
||||
* taskInstance |
||||
*/ |
||||
private TaskInstance taskInstance; |
||||
|
||||
/** |
||||
* processInstance |
||||
*/ |
||||
private ProcessInstance processInstance; |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
private Map<String, ExecutionStatus> completeTaskList = new ConcurrentHashMap<>(); |
||||
|
||||
/** |
||||
* constructor |
||||
* |
||||
* @param taskProps task props |
||||
* @param logger logger |
||||
*/ |
||||
public ConditionsTask(TaskProps taskProps, Logger logger) { |
||||
super(taskProps, logger); |
||||
} |
||||
|
||||
@Override |
||||
public void init() throws Exception { |
||||
logger.info("conditions task initialize"); |
||||
|
||||
this.processService = SpringApplicationContext.getBean(ProcessService.class); |
||||
|
||||
this.dependentParameters = JSONUtils.parseObject(this.taskProps.getDependence(), DependentParameters.class); |
||||
|
||||
this.taskInstance = processService.findTaskInstanceById(taskProps.getTaskInstId()); |
||||
|
||||
if(taskInstance == null){ |
||||
throw new Exception("cannot find the task instance!"); |
||||
} |
||||
|
||||
List<TaskInstance> taskInstanceList = processService.findValidTaskListByProcessId(taskInstance.getProcessInstanceId()); |
||||
for(TaskInstance task : taskInstanceList){ |
||||
this.completeTaskList.putIfAbsent(task.getName(), task.getState()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void handle() throws Exception { |
||||
|
||||
String threadLoggerInfoName = String.format(Constants.TASK_LOG_INFO_FORMAT, taskProps.getTaskAppId()); |
||||
Thread.currentThread().setName(threadLoggerInfoName); |
||||
|
||||
List<DependResult> modelResultList = new ArrayList<>(); |
||||
for(DependentTaskModel dependentTaskModel : dependentParameters.getDependTaskList()){ |
||||
|
||||
List<DependResult> itemDependResult = new ArrayList<>(); |
||||
for(DependentItem item : dependentTaskModel.getDependItemList()){ |
||||
itemDependResult.add(getDependResultForItem(item)); |
||||
} |
||||
DependResult modelResult = DependentUtils.getDependResultForRelation(dependentTaskModel.getRelation(), itemDependResult); |
||||
modelResultList.add(modelResult); |
||||
} |
||||
DependResult result = DependentUtils.getDependResultForRelation( |
||||
dependentParameters.getRelation(), modelResultList |
||||
); |
||||
logger.info("the conditions task depend result : {}", result); |
||||
exitStatusCode = (result == DependResult.SUCCESS) ? |
||||
Constants.EXIT_CODE_SUCCESS : Constants.EXIT_CODE_FAILURE; |
||||
} |
||||
|
||||
private DependResult getDependResultForItem(DependentItem item){ |
||||
|
||||
DependResult dependResult = DependResult.SUCCESS; |
||||
if(!completeTaskList.containsKey(item.getDepTasks())){ |
||||
logger.info("depend item: {} have not completed yet.", item.getDepTasks()); |
||||
dependResult = DependResult.FAILED; |
||||
return dependResult; |
||||
} |
||||
ExecutionStatus executionStatus = completeTaskList.get(item.getDepTasks()); |
||||
if(executionStatus != item.getStatus()){ |
||||
logger.info("depend item : {} expect status: {}, actual status: {}" ,item.getDepTasks(), item.getStatus().toString(), executionStatus.toString()); |
||||
dependResult = DependResult.FAILED; |
||||
} |
||||
logger.info("depend item: {}, depend result: {}", |
||||
item.getDepTasks(), dependResult); |
||||
return dependResult; |
||||
} |
||||
|
||||
@Override |
||||
public AbstractParameters getParameters() { |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,231 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
<template> |
||||
<div class="dep-list-model"> |
||||
<div v-for="(el,$index) in dependItemList" :key='$index' class="list" @click="itemIndex = $index"> |
||||
<x-select style="width: 150px;" v-model="el.depTasks" :disabled="isDetails"> |
||||
<x-option v-for="item in preNode" :key="item.value" :value="item.value" :label="item.label"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select style="width: 116px;" v-model="el.status" :disabled="isDetails"> |
||||
<x-option v-for="item in nodeStatusList || []" :key="item.value" :value="item.value" :label="item.label"> |
||||
</x-option> |
||||
</x-select> |
||||
<template v-if="isInstance"> |
||||
<span class="instance-state"> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'SUCCESS'" data-toggle="tooltip" data-container="body" :title="$t('success')"></i> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'WAITING'" data-toggle="tooltip" data-container="body" :title="$t('waiting')"></i> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'FAILED'" data-toggle="tooltip" data-container="body" :title="$t('failed')"></i> |
||||
</span> |
||||
</template> |
||||
<span class="operation"> |
||||
<a href="javascript:" class="delete" @click="!isDetails && _remove($index)"> |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" data-container="body" :title="$t('delete')" ></i> |
||||
</a> |
||||
<a href="javascript:" class="add" @click="!isDetails && _add()" v-if="$index === (dependItemList.length - 1)"> |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" data-container="body" :title="$t('Add')"></i> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { cycleList, dateValueList, nodeStatusList } from './commcon' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'node-status', |
||||
data () { |
||||
return { |
||||
list: [], |
||||
definitionList: [], |
||||
projectList: [], |
||||
cycleList: cycleList, |
||||
isInstance: false, |
||||
itemIndex: null, |
||||
nodeStatusList: nodeStatusList |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
dependItemList: Array, |
||||
index: Number, |
||||
dependTaskList:Array, |
||||
preNode: Array |
||||
}, |
||||
model: { |
||||
prop: 'dependItemList', |
||||
event: 'dependItemListEvent' |
||||
}, |
||||
methods: { |
||||
/** |
||||
* add task |
||||
*/ |
||||
_add () { |
||||
// btn loading |
||||
this.isLoading = true |
||||
this.$emit('dependItemListEvent', _.concat(this.dependItemList, this._rtNewParams())) |
||||
|
||||
// remove tooltip |
||||
this._removeTip() |
||||
}, |
||||
/** |
||||
* remove task |
||||
*/ |
||||
_remove (i) { |
||||
this.dependTaskList[this.index].dependItemList.splice(i,1) |
||||
this._removeTip() |
||||
if (!this.dependItemList.length || this.dependItemList.length === 0) { |
||||
this.$emit('on-delete-all', { |
||||
index: this.index |
||||
}) |
||||
} |
||||
}, |
||||
_getProjectList () { |
||||
return new Promise((resolve, reject) => { |
||||
this.projectList = _.map(_.cloneDeep(this.store.state.dag.projectListS), v => { |
||||
return { |
||||
value: v.id, |
||||
label: v.name |
||||
} |
||||
}) |
||||
resolve() |
||||
}) |
||||
}, |
||||
_getProcessByProjectId (id) { |
||||
return new Promise((resolve, reject) => { |
||||
this.store.dispatch('dag/getProcessByProjectId', { projectId: id }).then(res => { |
||||
this.definitionList = _.map(_.cloneDeep(res), v => { |
||||
return { |
||||
value: v.id, |
||||
label: v.name |
||||
} |
||||
}) |
||||
resolve(res) |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* get dependItemList |
||||
*/ |
||||
_getDependItemList (ids, is = true) { |
||||
return new Promise((resolve, reject) => { |
||||
if (is) { |
||||
this.store.dispatch('dag/getProcessTasksList', { processDefinitionId: ids }).then(res => { |
||||
resolve(['ALL'].concat(_.map(res, v => v.name))) |
||||
}) |
||||
} else { |
||||
this.store.dispatch('dag/getTaskListDefIdAll', { processDefinitionIdList: ids }).then(res => { |
||||
resolve(res) |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
_rtNewParams () { |
||||
return { |
||||
depTasks: '', |
||||
status: '' |
||||
} |
||||
}, |
||||
_rtOldParams (value,depTasksList, item) { |
||||
return { |
||||
depTasks: '', |
||||
status: '' |
||||
} |
||||
}, |
||||
/** |
||||
* remove tip |
||||
*/ |
||||
_removeTip () { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
beforeCreate () { |
||||
}, |
||||
created () { |
||||
// is type projects-instance-details |
||||
this.isInstance = this.router.history.current.name === 'projects-instance-details' |
||||
// get processlist |
||||
this._getProjectList().then(() => { |
||||
let projectId = this.projectList[0].value |
||||
if (!this.dependItemList.length) { |
||||
this.$emit('dependItemListEvent', _.concat(this.dependItemList, this._rtNewParams())) |
||||
} else { |
||||
// get definitionId ids |
||||
let ids = _.map(this.dependItemList, v => v.definitionId).join(',') |
||||
// get item list |
||||
this._getDependItemList(ids, false).then(res => { |
||||
_.map(this.dependItemList, (v, i) => { |
||||
this._getProcessByProjectId(v.projectId).then(definitionList => { |
||||
this.$set(this.dependItemList, i, this._rtOldParams(v.definitionId, ['ALL'].concat(_.map(res[v.definitionId] || [], v => v.name)), v)) |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: {} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.dep-list-model { |
||||
position: relative; |
||||
min-height: 32px; |
||||
.list { |
||||
margin-bottom: 6px; |
||||
.operation { |
||||
padding-left: 4px; |
||||
a { |
||||
i { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
.delete { |
||||
color: #ff0000; |
||||
} |
||||
.add { |
||||
color: #0097e0; |
||||
} |
||||
} |
||||
} |
||||
.instance-state { |
||||
display: inline-block; |
||||
width: 24px; |
||||
.iconfont { |
||||
font-size: 20px; |
||||
vertical-align: middle; |
||||
cursor: pointer; |
||||
margin-left: 6px; |
||||
&.icon-SUCCESS { |
||||
color: #33cc00; |
||||
} |
||||
&.icon-WAITING { |
||||
color: #888888; |
||||
} |
||||
&.icon-FAILED { |
||||
color: #F31322; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,265 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
<template> |
||||
<div class="dependence-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('Custom Parameters')}}</div> |
||||
<div slot="content"> |
||||
<div class="dep-opt"> |
||||
<a href="javascript:" |
||||
@click="!isDetails && _addDep()" |
||||
class="add-dep"> |
||||
<i v-if="!isLoading" class="iconfont" :class="_isDetails" data-toggle="tooltip" :title="$t('Add')"> |
||||
 |
||||
</i> |
||||
<i v-if="isLoading" class="iconfont fa fa-spin" data-toggle="tooltip" :title="$t('Add')"> |
||||
 |
||||
</i> |
||||
</a> |
||||
</div> |
||||
<div class="dep-box"> |
||||
<span |
||||
class="dep-relation" |
||||
@click="!isDetails && _setGlobalRelation()" |
||||
v-if="dependTaskList.length"> |
||||
{{relation === 'AND' ? $t('and') : $t('or')}} |
||||
</span> |
||||
<div class="dep-list" v-for="(el,$index) in dependTaskList" :key='$index'> |
||||
<span class="dep-line-pie" |
||||
v-if="el.dependItemList.length" |
||||
@click="!isDetails && _setRelation($index)"> |
||||
{{el.relation === 'AND' ? $t('and') : $t('or')}} |
||||
</span> |
||||
<i class="iconfont dep-delete" |
||||
data-toggle="tooltip" |
||||
data-container="body" |
||||
:class="_isDetails" |
||||
@click="!isDetails && _deleteDep($index)" |
||||
:title="$t('delete')" > |
||||
 |
||||
</i> |
||||
<m-node-status |
||||
:dependTaskList='dependTaskList' |
||||
v-model="el.dependItemList" |
||||
@on-delete-all="_onDeleteAll" |
||||
@getDependTaskList="getDependTaskList" |
||||
:index="$index" |
||||
:rear-list = "rearList" |
||||
:pre-node = "preNode"> |
||||
</m-node-status> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import mListBox from './_source/listBox' |
||||
import mNodeStatus from './_source/nodeStatus' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'dependence', |
||||
data () { |
||||
return { |
||||
relation: 'AND', |
||||
dependTaskList: [], |
||||
isLoading: false |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object, |
||||
preNode: Array, |
||||
rearList: Array |
||||
}, |
||||
methods: { |
||||
_addDep () { |
||||
if (!this.isLoading) { |
||||
this.isLoading = true |
||||
this.dependTaskList.push({ |
||||
dependItemList: [], |
||||
relation: 'AND' |
||||
}) |
||||
} |
||||
}, |
||||
_deleteDep (i) { |
||||
// remove index dependent |
||||
this.dependTaskList.splice(i, 1) |
||||
// remove tootip |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
}, |
||||
_onDeleteAll (i) { |
||||
this.dependTaskList.map((item,i)=>{ |
||||
if(item.dependItemList.length === 0){ |
||||
this.dependTaskList.splice(i,1) |
||||
} |
||||
}) |
||||
// this._deleteDep(i) |
||||
}, |
||||
_setGlobalRelation () { |
||||
this.relation = this.relation === 'AND' ? 'OR' : 'AND' |
||||
}, |
||||
getDependTaskList(i){ |
||||
// console.log('getDependTaskList',i) |
||||
}, |
||||
_setRelation (i) { |
||||
this.dependTaskList[i].relation = this.dependTaskList[i].relation === 'AND' ? 'OR' : 'AND' |
||||
}, |
||||
_verification () { |
||||
this.$emit('on-dependent', { |
||||
relation: this.relation, |
||||
dependTaskList: _.map(this.dependTaskList, v => { |
||||
return { |
||||
relation: v.relation, |
||||
dependItemList: _.map(v.dependItemList, v1 => _.omit(v1, ['depTasksList', 'state', 'dateValueList'])) |
||||
} |
||||
}) |
||||
}) |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
dependTaskList (e) { |
||||
setTimeout(() => { |
||||
this.isLoading = false |
||||
}, 600) |
||||
} |
||||
}, |
||||
beforeCreate () { |
||||
}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
let dependentResult = $(`#${o.id}`).data('dependent-result') || {} |
||||
// Does not represent an empty object backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.relation = _.cloneDeep(o.dependence.relation) || 'AND' |
||||
this.dependTaskList = _.cloneDeep(o.dependence.dependTaskList) || [] |
||||
let defaultState = this.isDetails ? 'WAITING' : '' |
||||
// Process instance return status display matches by key |
||||
_.map(this.dependTaskList, v => _.map(v.dependItemList, v1 => v1.state = dependentResult[`${v1.definitionId}-${v1.depTasks}-${v1.cycle}-${v1.dateValue}`] || defaultState)) |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
destroyed () { |
||||
}, |
||||
computed: {}, |
||||
components: { mListBox, mNodeStatus } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.dependence-model { |
||||
margin-top: -10px; |
||||
.dep-opt { |
||||
margin-bottom: 10px; |
||||
padding-top: 3px; |
||||
line-height: 24px; |
||||
.add-dep { |
||||
color: #0097e0; |
||||
margin-right: 10px; |
||||
i { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
} |
||||
.dep-list { |
||||
margin-bottom: 16px; |
||||
position: relative; |
||||
border-left: 1px solid #eee; |
||||
padding-left: 16px; |
||||
margin-left: -16px; |
||||
transition: all 0.2s ease-out; |
||||
padding-bottom: 20px; |
||||
&:hover{ |
||||
border-left: 1px solid #0097e0; |
||||
transition: all 0.2s ease-out; |
||||
.dep-line-pie { |
||||
transition: all 0.2s ease-out; |
||||
border: 1px solid #0097e0; |
||||
background: #0097e0; |
||||
color: #fff; |
||||
} |
||||
} |
||||
.dep-line-pie { |
||||
transition: all 0.2s ease-out; |
||||
position: absolute; |
||||
width: 20px; |
||||
height: 20px; |
||||
border: 1px solid #e2e2e2; |
||||
text-align: center; |
||||
top: 50%; |
||||
margin-top: -20px; |
||||
z-index: 1; |
||||
left: -10px; |
||||
border-radius: 10px; |
||||
background: #fff; |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
&::selection { |
||||
background:transparent; |
||||
} |
||||
&::-moz-selection { |
||||
background:transparent; |
||||
} |
||||
&::-webkit-selection { |
||||
background:transparent; |
||||
} |
||||
} |
||||
.dep-delete { |
||||
position: absolute; |
||||
bottom: -6px; |
||||
left: 14px; |
||||
font-size: 18px; |
||||
color: #ff0000; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
.dep-box { |
||||
border-left: 4px solid #eee; |
||||
margin-left: -46px; |
||||
padding-left: 42px; |
||||
position: relative; |
||||
.dep-relation { |
||||
position: absolute; |
||||
width: 20px; |
||||
height: 20px; |
||||
border: 1px solid #e2e2e2; |
||||
text-align: center; |
||||
top: 50%; |
||||
margin-top: -10px; |
||||
z-index: 1; |
||||
left: -12px; |
||||
border-radius: 10px; |
||||
background: #fff; |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
&::selection { |
||||
background:transparent; |
||||
} |
||||
&::-moz-selection { |
||||
background:transparent; |
||||
} |
||||
&::-webkit-selection { |
||||
background:transparent; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in new issue