神仙都没用 69d0f2d756 小改主题
2024-12-26 17:32:45 +08:00

468 lines
8.6 KiB
Vue

<template>
<div class="dept-tree">
<div class="dept-tree__header">
<el-text>组织架构</el-text>
<div class="dept-tree__op">
<div class="item" @click="refresh()">
<el-tooltip content="刷新">
<el-icon>
<refresh-icon />
</el-icon>
</el-tooltip>
</div>
<div class="item" v-if="drag && !browser.isMini" @click="isDrag = true">
<el-tooltip content="拖动排序">
<el-icon>
<operation />
</el-icon>
</el-tooltip>
</div>
<div class="btns" v-show="isDrag">
<el-button type="success" size="small" @click="treeOrder(true)">保存</el-button>
<el-button size="small" @click="treeOrder(false)">取消</el-button>
</div>
</div>
</div>
<div class="dept-tree__container" @contextmenu.stop.prevent="onContextMenu">
<el-scrollbar>
<el-tree
v-loading="loading"
node-key="id"
default-expand-all
:data="list"
:props="{
label: 'name'
}"
highlight-current
:draggable="isDrag"
:allow-drag="allowDrag"
:allow-drop="allowDrop"
:expand-on-click-node="false"
@node-contextmenu="onContextMenu"
@node-click="rowClick"
>
<template #default="{ node, data }">
<div class="dept-tree__node">
<span
class="dept-tree__node-label"
:class="{
'is-active': data.id == ViewGroup?.selected?.id
}"
>{{ node.label }}</span
>
<span
v-if="browser.isMini"
class="dept-tree__node-icon"
@click="onContextMenu($event, data, node)"
>
<el-icon>
<more-filled />
</el-icon>
</span>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
<cl-form ref="Form" />
</div>
</template>
<script lang="ts" name="dept-list" setup>
import { nextTick, onMounted, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useCool } from '/@/cool';
import { deepTree, revDeepTree } from '/@/cool/utils';
import { isArray } from 'lodash-es';
import { ContextMenu, useForm } from '@cool-vue/crud';
import { Refresh as RefreshIcon, Operation, MoreFilled } from '@element-plus/icons-vue';
import { checkPerm } from '/$/base';
import { useViewGroup } from '/@/plugins/view';
const props = defineProps({
drag: {
type: Boolean,
default: true
},
level: {
type: Number,
default: 99
}
});
const emit = defineEmits(['refresh', 'user-add']);
const { service, browser } = useCool();
const Form = useForm();
const { ViewGroup } = useViewGroup();
// 树形列表
const list = ref<Eps.BaseSysDepartmentEntity[]>([]);
// 加载中
const loading = ref(false);
// 是否能拖动
const isDrag = ref(false);
// 允许托的规则
function allowDrag({ data }: any) {
return data.parentId;
}
// 允许放的规则
function allowDrop(_: any, dropNode: any) {
return dropNode.data.parentId;
}
// 刷新
async function refresh() {
loading.value = true;
isDrag.value = false;
await service.base.sys.department.list().then(res => {
list.value = deepTree(res);
if (!ViewGroup.value?.selected) {
rowClick();
}
});
loading.value = false;
}
// 获取 ids
function rowClick(item?: Eps.BaseSysDepartmentEntity) {
if (!item) {
item = list.value[0];
}
if (item) {
const ids = item.children ? revDeepTree(item.children).map(e => e.id) : [];
ids.unshift(item.id);
// 选择
ViewGroup.value?.select(item);
nextTick(() => {
// 刷新列表
emit('refresh', { page: 1, departmentIds: ids });
});
}
}
// 编辑部门
function rowEdit(item: Eps.BaseSysDepartmentEntity) {
const method = item.id ? 'update' : 'add';
Form.value?.open({
title: '编辑部门',
width: '550px',
props: {
labelWidth: '100px'
},
items: [
{
label: '部门名称',
prop: 'name',
component: {
name: 'el-input'
},
required: true
},
{
label: '上级部门',
prop: 'parentName',
component: {
name: 'el-input',
props: {
disabled: true
}
}
},
{
label: '排序',
prop: 'orderNum',
component: {
name: 'el-input-number',
props: {
'controls-position': 'right',
min: 0,
max: 100
}
}
}
],
form: {
...item
},
on: {
submit(data, { done, close }) {
service.base.sys.department[method]({
id: item.id,
parentId: item.parentId,
name: data.name,
orderNum: data.orderNum
})
.then(() => {
ElMessage.success(`新增部门 “${data.name}” 成功`);
close();
refresh();
})
.catch(err => {
ElMessage.error(err.message);
done();
});
}
}
});
}
// 删除部门
function rowDel(item: Eps.BaseSysDepartmentEntity) {
async function del(f: boolean) {
await service.base.sys.department
.delete({
ids: [item.id],
deleteUser: f
})
.then(() => {
// 删除当前
if (ViewGroup.value?.selected?.id == item.id) {
rowClick();
}
if (f) {
ElMessage.success('删除成功');
} else {
ElMessageBox.confirm(
`${item.name}” 部门的用户已成功转移到 “${item.parentName}” 部门。`,
'删除成功'
);
}
});
refresh();
}
ElMessageBox.confirm(`此操作将会删除 “${item.name}” 部门的所有用户,是否确认?`, '提示', {
type: 'warning',
confirmButtonText: '直接删除',
cancelButtonText: '保留用户',
distinguishCancelAndClose: true
})
.then(() => {
del(true);
})
.catch(action => {
if (action == 'cancel') {
del(false);
}
});
}
// 部门排序
function treeOrder(f: boolean) {
if (f) {
ElMessageBox.confirm('部门架构已发生改变,是否保存?', '提示', {
type: 'warning'
})
.then(async () => {
const ids: any[] = [];
function 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.base.sys.department
.order(
ids.map((e, i) => {
return {
id: e.id,
parentId: e.parentId,
orderNum: i
};
})
)
.then(() => {
ElMessage.success('更新排序成功');
})
.catch(err => {
ElMessage.error(err.message);
});
refresh();
isDrag.value = false;
})
.catch(() => null);
} else {
refresh();
}
}
// 右键菜单
function onContextMenu(e: any, d?: any, n?: any) {
if (!d) {
d = list.value[0] || {};
}
// 权限
const perm = service.base.sys.department.permission;
ContextMenu.open(e, {
list: [
{
label: '新增',
hidden: (n && n.level >= props.level) || !checkPerm(perm.add),
callback(done) {
rowEdit({
name: '',
parentName: d.name,
parentId: d.id
});
done();
}
},
{
label: '编辑',
hidden: !checkPerm(perm.update),
callback(done) {
rowEdit(d);
done();
}
},
{
label: '删除',
hidden: !d.parentId || !checkPerm(perm.delete),
callback(done) {
rowDel(d);
done();
}
},
{
label: '新增成员',
hidden: !checkPerm(perm.add),
callback(done) {
emit('user-add', d);
done();
}
}
]
});
}
onMounted(function () {
refresh();
});
</script>
<style lang="scss" scoped>
.dept-tree {
height: 100%;
width: 100%;
:deep(.el-tree-node__label) {
display: block;
height: 100%;
width: 100%;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 10px;
position: relative;
}
&__op {
display: flex;
align-items: center;
.item {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
margin-left: 5px;
cursor: pointer;
border-radius: 4px;
font-size: 16px;
height: 26px;
width: 26px;
&:hover {
background-color: var(--el-fill-color-light);
}
}
.btns {
margin-left: 10px;
.el-button + .el-button {
margin-left: 10px;
}
}
}
&__container {
height: calc(100% - 40px);
:deep(.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;
&.is-active {
color: var(--color-primary);
}
}
&-icon {
display: flex;
align-items: center;
justify-content: center;
background-color: #eee;
height: 26px;
width: 26px;
text-align: center;
margin-right: 5px;
border-radius: 6px;
}
}
}
</style>