mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2026-03-24 06:22:26 +00:00
447 lines
8.4 KiB
Vue
447 lines
8.4 KiB
Vue
<template>
|
|
<div class="cl-dept-tree">
|
|
<div class="cl-dept-tree__header">
|
|
<div>组织架构</div>
|
|
|
|
<ul class="cl-dept-tree__op">
|
|
<li>
|
|
<el-tooltip content="刷新">
|
|
<i class="el-icon-refresh" @click="refresh()"></i>
|
|
</el-tooltip>
|
|
</li>
|
|
|
|
<li v-if="drag && !isMini">
|
|
<el-tooltip content="拖动排序">
|
|
<i class="el-icon-s-operation" @click="isDrag = true"></i>
|
|
</el-tooltip>
|
|
</li>
|
|
|
|
<li v-show="isDrag" class="no">
|
|
<el-button type="text" size="mini" @click="treeOrder(true)">保存</el-button>
|
|
<el-button type="text" size="mini" @click="treeOrder(false)">取消</el-button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="cl-dept-tree__container" @contextmenu.stop.prevent="openCM">
|
|
<el-tree
|
|
v-loading="loading"
|
|
node-key="id"
|
|
highlight-current
|
|
default-expand-all
|
|
:data="list"
|
|
:props="{
|
|
label: 'name'
|
|
}"
|
|
:draggable="isDrag"
|
|
:allow-drag="allowDrag"
|
|
:allow-drop="allowDrop"
|
|
:expand-on-click-node="false"
|
|
@node-contextmenu="openCM"
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div class="cl-dept-tree__node">
|
|
<span class="cl-dept-tree__node-label" @click="rowClick(data)">{{
|
|
node.label
|
|
}}</span>
|
|
<span
|
|
v-if="isMini"
|
|
class="cl-dept-tree__node-icon"
|
|
@click="openCM($event, data, node)"
|
|
>
|
|
<i class="el-icon-more"></i>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</div>
|
|
|
|
<cl-form :ref="setRefs('form')" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, inject, onMounted, ref } from "vue";
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
import { ContextMenu } from "cl-admin-crud-vue3";
|
|
import { useRefs } from "/@/core";
|
|
import { deepTree, isArray, revDeepTree, isPc } from "/@/core/utils";
|
|
|
|
export default defineComponent({
|
|
name: "cl-dept-tree",
|
|
|
|
props: {
|
|
drag: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
level: {
|
|
type: Number,
|
|
default: 99
|
|
}
|
|
},
|
|
|
|
emits: ["list-change", "row-click", "user-add"],
|
|
|
|
setup(props, { emit }) {
|
|
const { refs, setRefs } = useRefs();
|
|
|
|
// 树形列表
|
|
const list = ref<any[]>([]);
|
|
|
|
// 加载中
|
|
const loading = ref<boolean>(false);
|
|
|
|
// 是否能拖动
|
|
const isDrag = ref<boolean>(false);
|
|
|
|
// 请求服务
|
|
const service = inject<any>("service");
|
|
|
|
// 允许托的规则
|
|
function allowDrag({ data }: any) {
|
|
return data.parentId;
|
|
}
|
|
|
|
// 允许放的规则
|
|
function allowDrop(_: any, dropNode: any) {
|
|
return dropNode.data.parentId;
|
|
}
|
|
|
|
// 刷新
|
|
async function refresh() {
|
|
isDrag.value = false;
|
|
loading.value = true;
|
|
|
|
await service.system.dept.list().then((res: any[]) => {
|
|
list.value = deepTree(res);
|
|
emit("list-change", list.value);
|
|
});
|
|
|
|
loading.value = false;
|
|
}
|
|
|
|
// 获取 ids
|
|
function rowClick(e: any) {
|
|
const ids = e.children ? revDeepTree(e.children).map((e) => e.id) : [];
|
|
ids.unshift(e.id);
|
|
emit("row-click", { item: e, ids });
|
|
}
|
|
|
|
// 编辑部门
|
|
function rowEdit(e: any) {
|
|
const method = e.id ? "update" : "add";
|
|
|
|
refs.value.form.open({
|
|
title: "编辑部门",
|
|
width: "550px",
|
|
props: {
|
|
labelWidth: "100px"
|
|
},
|
|
items: [
|
|
{
|
|
label: "部门名称",
|
|
prop: "name",
|
|
value: e.name,
|
|
component: {
|
|
name: "el-input",
|
|
props: {
|
|
placeholder: "请填写部门名称"
|
|
}
|
|
},
|
|
rules: {
|
|
required: true,
|
|
message: "部门名称不能为空"
|
|
}
|
|
},
|
|
{
|
|
label: "上级部门",
|
|
prop: "parentId",
|
|
value: e.parentName || "...",
|
|
component: {
|
|
name: "el-input",
|
|
props: {
|
|
disabled: true
|
|
}
|
|
}
|
|
},
|
|
{
|
|
label: "排序",
|
|
prop: "orderNum",
|
|
value: e.orderNum || 0,
|
|
component: {
|
|
name: "el-input-number",
|
|
props: {
|
|
"controls-position": "right",
|
|
min: 0,
|
|
max: 100
|
|
}
|
|
}
|
|
}
|
|
],
|
|
on: {
|
|
submit: (data: any, { done, close }: any) => {
|
|
service.system.dept[method]({
|
|
id: e.id,
|
|
parentId: e.parentId,
|
|
name: data.name,
|
|
orderNum: data.orderNum
|
|
})
|
|
.then(() => {
|
|
ElMessage.success(`新增部门${data.name}成功`);
|
|
close();
|
|
refresh();
|
|
})
|
|
.catch((err: string) => {
|
|
ElMessage.error(err);
|
|
done();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 删除部门
|
|
function rowDel(e: any) {
|
|
const del = async (f: boolean) => {
|
|
await service.system.dept
|
|
.delete({
|
|
ids: [e.id],
|
|
deleteUser: f
|
|
})
|
|
.then(() => {
|
|
if (f) {
|
|
ElMessage.success("删除成功");
|
|
} else {
|
|
ElMessageBox.confirm(
|
|
`“${e.name}” 部门的用户已成功转移到 “${e.parentName}” 部门。`,
|
|
"删除成功"
|
|
);
|
|
}
|
|
});
|
|
|
|
refresh();
|
|
};
|
|
|
|
ElMessageBox.confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", {
|
|
type: "warning",
|
|
confirmButtonText: "直接删除",
|
|
cancelButtonText: "保留用户",
|
|
distinguishCancelAndClose: true
|
|
})
|
|
.then(() => {
|
|
del(true);
|
|
})
|
|
.catch((action: string) => {
|
|
if (action == "cancel") {
|
|
del(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 部门排序
|
|
function treeOrder(f: boolean) {
|
|
if (f) {
|
|
ElMessageBox.confirm("部门架构已发生改变,是否保存?", "提示", {
|
|
type: "warning"
|
|
})
|
|
.then(async () => {
|
|
const ids: any[] = [];
|
|
|
|
const deep = (list: any[], pid: any) => {
|
|
list.forEach((e) => {
|
|
e.parentId = pid;
|
|
ids.push(e);
|
|
|
|
if (e.children && isArray(e.children)) {
|
|
deep(e.children, e.id);
|
|
}
|
|
});
|
|
};
|
|
|
|
deep(list.value, null);
|
|
|
|
await service.system.dept
|
|
.order(
|
|
ids.map((e, i) => {
|
|
return {
|
|
id: e.id,
|
|
parentId: e.parentId,
|
|
orderNum: i
|
|
};
|
|
})
|
|
)
|
|
.then(() => {
|
|
ElMessage.success("更新排序成功");
|
|
})
|
|
.catch((err: string) => {
|
|
ElMessage.error(err);
|
|
});
|
|
|
|
refresh();
|
|
isDrag.value = false;
|
|
})
|
|
.catch(() => null);
|
|
} else {
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
// 右键菜单
|
|
function openCM(e: any, d?: any, n?: any) {
|
|
if (!d) {
|
|
d = list.value[0] || {};
|
|
}
|
|
|
|
ContextMenu.open(e, {
|
|
list: [
|
|
{
|
|
label: "新增",
|
|
"suffix-icon": "el-icon-plus",
|
|
hidden: n && n.level >= props.level,
|
|
callback: (_: any, done: Function) => {
|
|
rowEdit({
|
|
name: "",
|
|
parentName: d.name,
|
|
parentId: d.id
|
|
});
|
|
done();
|
|
}
|
|
},
|
|
{
|
|
label: "编辑",
|
|
"suffix-icon": "el-icon-edit",
|
|
callback: (_: any, done: Function) => {
|
|
rowEdit(d);
|
|
done();
|
|
}
|
|
},
|
|
{
|
|
label: "删除",
|
|
"suffix-icon": "el-icon-delete",
|
|
hidden: !d.parentId,
|
|
callback: (_: any, done: Function) => {
|
|
rowDel(d);
|
|
done();
|
|
}
|
|
},
|
|
{
|
|
label: "新增成员",
|
|
"suffix-icon": "el-icon-user",
|
|
callback: (_: any, done: Function) => {
|
|
emit("user-add", d);
|
|
done();
|
|
}
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
onMounted(function () {
|
|
refresh();
|
|
});
|
|
|
|
return {
|
|
refs,
|
|
list,
|
|
loading,
|
|
isDrag,
|
|
isMini: !isPc(),
|
|
setRefs,
|
|
openCM,
|
|
allowDrag,
|
|
allowDrop,
|
|
refresh,
|
|
rowClick,
|
|
rowEdit,
|
|
rowDel,
|
|
treeOrder
|
|
};
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.cl-dept-tree {
|
|
height: 100%;
|
|
width: 100%;
|
|
|
|
&__header {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 40px;
|
|
padding: 0 10px;
|
|
background-color: #fff;
|
|
letter-spacing: 1px;
|
|
position: relative;
|
|
|
|
div {
|
|
font-size: 14px;
|
|
flex: 1;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
i {
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
&__op {
|
|
display: flex;
|
|
|
|
li {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
list-style: none;
|
|
margin-left: 5px;
|
|
padding: 5px;
|
|
cursor: pointer;
|
|
|
|
&:not(.no):hover {
|
|
background-color: #eee;
|
|
}
|
|
}
|
|
}
|
|
|
|
&__container {
|
|
height: calc(100% - 40px);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
|
|
.el-tree-node__content {
|
|
height: 36px;
|
|
margin: 0 5px;
|
|
}
|
|
}
|
|
|
|
&__node {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 100%;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
|
|
&-label {
|
|
display: flex;
|
|
align-items: center;
|
|
flex: 1;
|
|
height: 100%;
|
|
font-size: 14px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
&-icon {
|
|
height: 28px;
|
|
width: 28px;
|
|
line-height: 28px;
|
|
text-align: center;
|
|
margin-right: 5px;
|
|
}
|
|
}
|
|
}
|
|
</style>
|