mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-01-02 10:45:27 +08:00
【v3.8.3】组织机构部门大改造(支持子公司、岗位与不能功能划分更清晰,岗位可以设置上下级,岗位可以设置职级,支持回报关系)
This commit is contained in:
97
jeecgboot-vue3/src/views/system/depart/TenantDepartList.vue
Normal file
97
jeecgboot-vue3/src/views/system/depart/TenantDepartList.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree
|
||||
v-if="showDepart"
|
||||
ref="leftTree"
|
||||
@select="onTreeSelect"
|
||||
@rootTreeData="onRootTreeData"
|
||||
:isTenantDepart="true"
|
||||
:loginTenantName="loginTenantName"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%" :class="[`${prefixCls}`]">
|
||||
<a-tabs v-show="departData != null" defaultActiveKey="base-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
|
||||
<div style="padding: 20px">
|
||||
<DepartFormTab v-if="showDepart" :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" :isTenantDepart="true" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-show="departData == null" style="padding-top: 40px">
|
||||
<a-empty description="尚未选择部门" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="TenantDepartList">
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DepartLeftTree from '/@/views/system/depart/components/DepartLeftTree.vue';
|
||||
import DepartFormTab from '/@/views/system/depart/components/DepartFormTab.vue';
|
||||
import { getLoginTenantName } from '@/views/system/tenant/tenant.api';
|
||||
import { tenantSaasMessage } from '@/utils/common/compUtils';
|
||||
|
||||
const { prefixCls } = useDesign('tenant-depart-manage');
|
||||
provide('prefixCls', prefixCls);
|
||||
|
||||
// 给子组件定义一个ref变量
|
||||
const leftTree = ref();
|
||||
//是否显示部门
|
||||
const showDepart = ref(false);
|
||||
|
||||
// 当前选中的部门信息
|
||||
const departData = ref({});
|
||||
const rootTreeData = ref<any[]>([]);
|
||||
const loginTenantName = ref<string>('');
|
||||
|
||||
/**
|
||||
* 获取租户名称
|
||||
*/
|
||||
getTenantName();
|
||||
|
||||
async function getTenantName() {
|
||||
loginTenantName.value = await getLoginTenantName();
|
||||
if (loginTenantName.value) {
|
||||
showDepart.value = true;
|
||||
} else {
|
||||
showDepart.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
departData.value = data;
|
||||
}
|
||||
|
||||
// 左侧树rootTreeData触发
|
||||
function onRootTreeData(data) {
|
||||
rootTreeData.value = data;
|
||||
}
|
||||
|
||||
function onSuccess() {
|
||||
leftTree.value.loadRootTreeData();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//提示信息
|
||||
tenantSaasMessage('租户部门');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-tenant-depart-manage';
|
||||
|
||||
.@{prefix-cls} {
|
||||
background: @component-background;
|
||||
|
||||
&--box {
|
||||
.ant-tabs-nav {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
|
||||
<BasicForm @register="registerForm" />
|
||||
<BasicForm @register="registerForm" >
|
||||
<template #depPostParentId="{ model, field }">
|
||||
<a-tree-select v-model:value="depPostValue" :treeData="treeData" allowClear treeCheckable @select="treeSelect">
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
<template #tagRender="{option}">
|
||||
<span style="margin-left: 10px" v-if="orgNameMap[option.id]">{{orgNameMap[option.id]}}</span>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@ -12,6 +23,8 @@
|
||||
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
import { getDepartPathNameByOrgCode } from "@/utils/common/compUtils";
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({
|
||||
@ -23,10 +36,15 @@
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
|
||||
const treeData = ref<any>([]);
|
||||
//上级岗位
|
||||
const depPostValue = ref<any>([]);
|
||||
//上级岗位名称映射
|
||||
const orgNameMap = ref<Record<string, string>>({});
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
schemas: useBasicFormSchema(treeData).basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
@ -37,6 +55,17 @@
|
||||
// 当前是否为添加子级
|
||||
let isChild = unref(data?.isChild);
|
||||
let categoryOptions = isChild ? orgCategoryOptions.child : orgCategoryOptions.root;
|
||||
|
||||
if(data.record?.orgCategory && data.record?.orgCategory === '2'){
|
||||
categoryOptions = orgCategoryOptions.childDepartPost;
|
||||
}
|
||||
if(data.record?.orgCategory && data.record?.orgCategory === '3'){
|
||||
categoryOptions = orgCategoryOptions.childPost;
|
||||
}
|
||||
if(data.record?.depPostParentId){
|
||||
orgNameMap.value[data.record.depPostParentId] = await getDepartPathNameByOrgCode('', '', data.record.depPostParentId);
|
||||
depPostValue.value = [data.record.depPostParentId];
|
||||
}
|
||||
// 隐藏不需要展示的字段
|
||||
updateSchema([
|
||||
{
|
||||
@ -62,11 +91,14 @@
|
||||
if (typeof record !== 'object') {
|
||||
record = {};
|
||||
}
|
||||
let orgCategory = data.record?.orgCategory;
|
||||
let company = orgCategory === '1' || orgCategory === '4';
|
||||
delete data.record?.orgCategory;
|
||||
// 赋默认值
|
||||
record = Object.assign(
|
||||
{
|
||||
departOrder: 0,
|
||||
orgCategory: categoryOptions[0].value,
|
||||
orgCategory: company?categoryOptions[1].value:categoryOptions[0].value,
|
||||
},
|
||||
record
|
||||
);
|
||||
@ -79,6 +111,11 @@
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
let values = await validate();
|
||||
if(depPostValue.value && depPostValue.value.length > 0){
|
||||
values.depPostParentId = depPostValue.value[0];
|
||||
}else{
|
||||
values.depPostParentId = "";
|
||||
}
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
@ -89,4 +126,29 @@
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树选中事件
|
||||
*
|
||||
* @param info
|
||||
* @param keys
|
||||
*/
|
||||
async function treeSelect(keys,info) {
|
||||
if (info.checkable) {
|
||||
//解决闪动问题
|
||||
orgNameMap.value[info.id] = "";
|
||||
depPostValue.value = [info.value];
|
||||
orgNameMap.value[info.id] = await getDepartPathNameByOrgCode(info.orgCode,info.label,info.id);
|
||||
} else {
|
||||
depPostValue.value = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-select-selector .ant-select-selection-item){
|
||||
svg {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<BasicForm @register="registerForm" />
|
||||
<BasicForm @register="registerForm" >
|
||||
<template #depPostParentId="{ model, field }">
|
||||
<a-tree-select v-model:value="depPostValue" :treeData="treeData" allowClear treeCheckable @select="treeSelect">
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
<template #tagRender="{ option }">
|
||||
<span style="margin-left: 10px">{{ orgNameMap[option.id] }}</span>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
|
||||
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]">
|
||||
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
|
||||
@ -14,8 +25,10 @@
|
||||
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
import { useBasicFormSchema, orgCategoryOptions, positionChange } from '../depart.data';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
import { getDepartPathNameByOrgCode } from '@/utils/common/compUtils';
|
||||
|
||||
const { prefixCls } = useDesign('j-depart-form-content');
|
||||
|
||||
@ -29,10 +42,15 @@
|
||||
const isUpdate = ref<boolean>(true);
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const treeData = ref<any>([]);
|
||||
//上级岗位
|
||||
const depPostValue = ref<any>([]);
|
||||
//上级岗位名称映射
|
||||
const orgNameMap = ref<Record<string, string>>({});
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
schemas: useBasicFormSchema(treeData).basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
@ -59,6 +77,11 @@
|
||||
record = {};
|
||||
}
|
||||
model.value = record;
|
||||
if (record.depPostParentId) {
|
||||
orgNameMap.value[record.depPostParentId] = await getDepartPathNameByOrgCode('', '', record.depPostParentId);
|
||||
depPostValue.value = [record.depPostParentId];
|
||||
}
|
||||
positionChange(record.positionId, record, treeData);
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...record });
|
||||
},
|
||||
@ -104,6 +127,11 @@
|
||||
loading.value = true;
|
||||
let values = await validate();
|
||||
values = Object.assign({}, model.value, values);
|
||||
if (depPostValue.value && depPostValue.value.length > 0) {
|
||||
values.depPostParentId = depPostValue.value[0];
|
||||
} else {
|
||||
values.depPostParentId = '';
|
||||
}
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//刷新列表
|
||||
@ -113,6 +141,22 @@
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树选中事件
|
||||
*
|
||||
* @param info
|
||||
* @param keys
|
||||
*/
|
||||
async function treeSelect(keys, info) {
|
||||
if (info.checkable) {
|
||||
orgNameMap.value[info.id] = '';
|
||||
depPostValue.value = [info.value];
|
||||
orgNameMap.value[info.id] = await getDepartPathNameByOrgCode(info.orgCode, info.label, info.id);
|
||||
} else {
|
||||
depPostValue.value = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
@ -126,3 +170,10 @@
|
||||
/*end 兼容暗夜模式*/
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-select-selector .ant-select-selection-item){
|
||||
svg{
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<div class="j-table-operator" style="width: 100%">
|
||||
<div class="j-table-operator" style="width: 100%;display: flex;align-items: center">
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddDepart">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddChildDepart()">添加下级</a-button>
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls">
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls" v-if="!isTenantDepart">
|
||||
<a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button>
|
||||
</a-upload>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步企微?</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步钉钉?</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls" v-if="!isTenantDepart">导出</a-button>
|
||||
<template v-if="checkedKeys.length > 0">
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
@ -25,6 +23,10 @@
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<Icon icon="ant-design:question-circle-outlined" style="margin-left: 10px;cursor: pointer" @click="tipShow = true"></Icon>
|
||||
<div v-if="loginTenantName" style="margin-left: 10px;"
|
||||
>当前登录租户: <span class="tenant-name">{{ loginTenantName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-alert type="info" show-icon class="alert" style="margin-bottom: 8px">
|
||||
<template #message>
|
||||
@ -54,8 +56,9 @@
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@check="onCheck"
|
||||
@select="onSelect"
|
||||
style="overflow-y: auto;height: calc(100vh - 330px);"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, dataRef }">
|
||||
<template #title="{ key: treeKey, title, dataRef, data }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<Popconfirm
|
||||
:open="visibleTreeKey === treeKey"
|
||||
@ -66,12 +69,12 @@
|
||||
@confirm="onDelete(dataRef)"
|
||||
@openChange="onVisibleChange"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<TreeIcon :orgCategory="dataRef.orgCategory" :title="title"></TreeIcon>
|
||||
</Popconfirm>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="">
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</a-menu-item>
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)" v-if="data.orgCategory !== '3'">添加下级</a-menu-item>
|
||||
<a-menu-item key="2" @click="visibleTreeKey = treeKey">
|
||||
<span style="color: red">删除</span>
|
||||
</a-menu-item>
|
||||
@ -85,22 +88,44 @@
|
||||
</a-spin>
|
||||
<DepartFormModal :rootTreeData="treeData" @register="registerModal" @success="loadRootTreeData" />
|
||||
</a-card>
|
||||
<a-modal v-model:open="tipShow" :footer="null" title="部门规则说明" :width="800">
|
||||
<ul class="departmentalRulesTip">
|
||||
<li>当前部门机构设置支持集团组织架构,第一级默认为公司,下级可创建子公司、部门和岗位。</li>
|
||||
<li><br/></li>
|
||||
<li>1、岗位下不能添加下级。</li>
|
||||
<li>2、部门下不能直接添加子公司。</li>
|
||||
<li>3、子公司下可继续添加子公司。</li>
|
||||
<li>4、岗位需配置职务级别,岗位的级别高低和上下级关系均以职务级别及上级岗位设置为准。</li>
|
||||
<li>5、董事长岗位仅可选择上级公司(子公司或总公司)各部门的所有岗位为上级岗位。</li>
|
||||
<li>6、非董事长岗位仅可选择当前父级部门及本部门内级别更高的岗位为上级岗位。</li>
|
||||
<li><br/></li>
|
||||
<li><b>特别说明:</b>董事长相关逻辑为固定写死,职务等级“董事长”的表述请勿修改。</li>
|
||||
</ul>
|
||||
<div style="height: 10px"></div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, unref } from 'vue';
|
||||
import { inject, nextTick, ref, unref, defineEmits } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useMethods } from '/@/hooks/system/useMethods';
|
||||
import { Api, deleteBatchDepart, queryDepartTreeSync } from '../depart.api';
|
||||
import { Api, deleteBatchDepart, queryDepartAndPostTreeSync } from '../depart.api';
|
||||
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select', 'rootTreeData']);
|
||||
const { createMessage } = useMessage();
|
||||
const { handleImportXls, handleExportXls } = useMethods();
|
||||
const props = defineProps({
|
||||
//是否为租户部门
|
||||
isTenantDepart: { default: false, type: Boolean },
|
||||
//当前登录租户
|
||||
loginTenantName: { default: "", type: String },
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 部门树列表数据
|
||||
@ -121,6 +146,8 @@
|
||||
const visibleTreeKey = ref<any>(null);
|
||||
// 搜索关键字
|
||||
const searchKeyword = ref('');
|
||||
// 提示弹窗是否显示
|
||||
const tipShow = ref<boolean>(false);
|
||||
|
||||
// 注册 modal
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
@ -130,7 +157,7 @@
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
const result = await queryDepartTreeSync();
|
||||
const result = await queryDepartAndPostTreeSync();
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
@ -158,7 +185,7 @@
|
||||
// 加载子级部门信息
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
const result = await queryDepartAndPostTreeSync({
|
||||
pid: treeNode.dataRef.id,
|
||||
});
|
||||
if (result.length == 0) {
|
||||
@ -231,7 +258,11 @@
|
||||
createMessage.warning('请先选择一个部门');
|
||||
return;
|
||||
}
|
||||
const record = { parentId: data.id };
|
||||
if(data.orgCategory === '3'){
|
||||
createMessage.warning('岗位下无法添加子级!');
|
||||
return;
|
||||
}
|
||||
const record = { parentId: data.id, orgCategory: data.orgCategory };
|
||||
openModal(true, { isUpdate: false, isChild: true, record });
|
||||
}
|
||||
|
||||
@ -241,7 +272,7 @@
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result = await searchByKeywords({ keyWord: value });
|
||||
let result = await searchByKeywords({ keyWord: value, orgCategory: "1,2,3,4" });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
@ -336,3 +367,18 @@
|
||||
loadRootTreeData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.departmentalRulesTip{
|
||||
margin: 20px;
|
||||
background-color: #f8f9fb;
|
||||
color: #99a1a9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
.tenant-name {
|
||||
text-decoration: underline;
|
||||
margin: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="treeData && treeData.length > 0">
|
||||
<BasicTree ref="basicTree" :treeData="treeData" :checkStrictly="true" style="height: 500px; overflow: auto"></BasicTree>
|
||||
</template>
|
||||
<a-empty v-else description="无岗位消息" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { BasicTree } from '/@/components/Tree/index';
|
||||
import { getRankRelation } from '../depart.api';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
});
|
||||
// 当前选中的部门ID,可能会为空,代表未选择部门
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
const basicTree = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
//树的全部节点信息
|
||||
const treeData = ref<any[]>([]);
|
||||
|
||||
watch(departId, (val) => loadData(val), { immediate: true });
|
||||
|
||||
async function loadData(val) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await getRankRelation({ departId: val }).then((res) => {
|
||||
if (res.success) {
|
||||
treeData.value = res.result;
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.depart-rule-tree :deep(.scrollbar__bar) {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser" :disabled="!orgCode">新建用户</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser" :disabled="!orgCode || props.data?.orgCategory === '3'"
|
||||
>添加已有用户</a-button
|
||||
>
|
||||
</template>
|
||||
<!-- 插槽:行内操作按钮 -->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<UserDrawer @register="registerDrawer" @success="onUserDrawerSuccess" />
|
||||
<UserSelectModal ref="userSelectModalRef" rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
import UserDrawer from '/@/views/system/user/UserDrawer.vue';
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
|
||||
import { queryByOrgCodeForAddressList } from '../depart.api';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
import { userColumns } from '@/views/system/depart/depart.data';
|
||||
import { linkDepartUserBatch } from '@/views/system/departUser/depart.user.api';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const props = defineProps({
|
||||
data: { require: true, type: Object },
|
||||
});
|
||||
const userSelectModalRef: any = ref(null);
|
||||
// 当前选中的部门code,可能会为空,代表未选择部门
|
||||
const orgCode = computed(() => props.data?.orgCode);
|
||||
// 当前部门id
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 12, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
// 列表页面公共参数、方法
|
||||
const { tableContext, createMessage } = useListPage({
|
||||
tableProps: {
|
||||
api: queryByOrgCodeForAddressList,
|
||||
columns: userColumns,
|
||||
canResize: false,
|
||||
rowKey: 'id',
|
||||
formConfig: {
|
||||
// schemas: userInfoSearchFormSchema,
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'left',
|
||||
labelCol: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 9,
|
||||
xl: 7,
|
||||
xxl: 5,
|
||||
},
|
||||
// 操作按钮配置
|
||||
actionColOptions: {
|
||||
...adaptiveColProps,
|
||||
style: { textAlign: 'left' },
|
||||
},
|
||||
},
|
||||
tableSetting: { cacheKey: 'depart_user_userInfo' },
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
return Object.assign(params, { orgCode: orgCode.value });
|
||||
},
|
||||
immediate: !!orgCode.value,
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setProps, setLoading, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => reload()
|
||||
);
|
||||
//注册drawer
|
||||
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
|
||||
const [registerUserAuthDrawer, userAuthDrawer] = useDrawer();
|
||||
// 注册用户选择 modal
|
||||
const [registerSelUserModal, selUserModal] = useModal();
|
||||
|
||||
// 清空选择的行
|
||||
function clearSelection() {
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
async function createUser() {
|
||||
if (!departId.value) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
} else {
|
||||
let mainDepPostId = '';
|
||||
let selecteddeparts = departId.value;
|
||||
if (props.data?.orgCategory === '3') {
|
||||
mainDepPostId = departId.value;
|
||||
selecteddeparts = props.data.parentId;
|
||||
}
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
// 初始化负责部门
|
||||
nextDepartOptions: { value: props.data?.key, label: props.data?.title },
|
||||
//初始化岗位
|
||||
record: {
|
||||
mainDepPostId: mainDepPostId,
|
||||
activitiSync: 1,
|
||||
userIdentity: 1,
|
||||
selecteddeparts: selecteddeparts,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 查看用户详情
|
||||
function showUserDetail(record) {
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
showFooter: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑用户信息
|
||||
function editUserInfo(record) {
|
||||
openDrawer(true, { isUpdate: true, record, departDisabled: true, departPostDisabled: true });
|
||||
}
|
||||
|
||||
// 选择添加已有用户
|
||||
function selectAddUser() {
|
||||
userSelectModalRef.value.rowSelection.selectedRowKeys = [];
|
||||
selUserModal.openModal();
|
||||
}
|
||||
|
||||
// 选择用户成功
|
||||
async function onSelectUserOk(options, userIdList) {
|
||||
if (userIdList.length > 0) {
|
||||
try {
|
||||
setLoading(true);
|
||||
await linkDepartUserBatch(departId.value, userIdList);
|
||||
reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户抽屉表单成功回调
|
||||
*/
|
||||
function onUserDrawerSuccess({ isUpdate, values }) {
|
||||
isUpdate ? updateTableDataRecord(values.id, values) : reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '编辑', onClick: editUserInfo.bind(null, record) },
|
||||
{ label: '详情', onClick: showUserDetail.bind(null, record) },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@ -24,6 +24,14 @@ export enum Api {
|
||||
getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo',
|
||||
doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo',
|
||||
changeDepartChargePerson = '/sys/user/changeDepartChargePerson',
|
||||
//根据部门id获取岗位信息
|
||||
getPositionByDepartId = '/sys/sysDepart/getPositionByDepartId',
|
||||
//根据部门id获取岗位上下级关系
|
||||
getRankRelation = '/sys/sysDepart/getRankRelation',
|
||||
//异步获取部门和岗位
|
||||
queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync',
|
||||
//获取部门和岗位下的成员
|
||||
queryByOrgCodeForAddressList = '/sys/user/queryByOrgCodeForAddressList',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +39,11 @@ export enum Api {
|
||||
*/
|
||||
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params });
|
||||
|
||||
/**
|
||||
* 获取部门和岗位树列表
|
||||
*/
|
||||
export const queryDepartAndPostTreeSync = (params?) => defHttp.get({ url: Api.queryDepartAndPostTreeSync, params });
|
||||
|
||||
/**
|
||||
* 保存或者更新部门角色
|
||||
*/
|
||||
@ -120,3 +133,21 @@ export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ i
|
||||
* @param params
|
||||
*/
|
||||
export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params });
|
||||
|
||||
/**
|
||||
* 根据部门id获取岗位信息
|
||||
*/
|
||||
export const getPositionByDepartId = (params) => defHttp.get({ url: Api.getPositionByDepartId, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 根据部门id获取岗位上下级关系
|
||||
* @param params
|
||||
*/
|
||||
export const getRankRelation = (params) => defHttp.get({ url: Api.getRankRelation, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 根据部门或岗位编码获取通讯录成员
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
export const queryByOrgCodeForAddressList = (params) => defHttp.get({ url: Api.queryByOrgCodeForAddressList, params });
|
||||
@ -1,7 +1,16 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { getPositionByDepartId } from "./depart.api";
|
||||
import { useMessage } from "@/hooks/web/useMessage";
|
||||
import { BasicColumn } from "@/components/Table";
|
||||
import { getDepartPathNameByOrgCode } from '@/utils/common/compUtils';
|
||||
import { h, ref } from 'vue';
|
||||
|
||||
const { createMessage: $message } = useMessage();
|
||||
//部门名称
|
||||
const departNamePath = ref<Record<string, string>>({});
|
||||
|
||||
// 部门基础表单
|
||||
export function useBasicFormSchema() {
|
||||
export function useBasicFormSchema(treeData) {
|
||||
const basicFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'departName',
|
||||
@ -19,7 +28,21 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
treeData: [],
|
||||
placeholder: '无',
|
||||
treeCheckAble: true,
|
||||
multiple: true,
|
||||
dropdownStyle: { maxHeight: '200px', overflow: 'auto' },
|
||||
tagRender: (options) => {
|
||||
const { value, label, option } = options;
|
||||
if (departNamePath.value[value]) {
|
||||
return h(
|
||||
'span', { style: { marginLeft: '10px' } },
|
||||
departNamePath.value[value]
|
||||
);
|
||||
}
|
||||
getDepartPathNameByOrgCode('', label, option.id).then((data) => {
|
||||
departNamePath.value[value] = data;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -36,6 +59,34 @@ export function useBasicFormSchema() {
|
||||
component: 'RadioButtonGroup',
|
||||
componentProps: { options: [] },
|
||||
},
|
||||
{
|
||||
field: 'positionId',
|
||||
label: '职务级别',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: ({ formModel, formActionType }) => {
|
||||
return {
|
||||
dictCode: "sys_position,name,id, 1=1 order by post_level asc",
|
||||
getPopupContainer: ()=> document.body,
|
||||
onChange: (value) => {
|
||||
formModel.depPostParentId = "";
|
||||
return positionChange(value, formModel, treeData);
|
||||
},
|
||||
}
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory === '3'
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'depPostParentId',
|
||||
label: '上级岗位',
|
||||
component: 'TreeSelect',
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory === '3'
|
||||
},
|
||||
slot: 'depPostParentId',
|
||||
},
|
||||
{
|
||||
field: 'departOrder',
|
||||
label: '排序',
|
||||
@ -49,6 +100,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入电话',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'fax',
|
||||
@ -57,6 +111,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入传真',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'address',
|
||||
@ -65,6 +122,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入地址',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'memo',
|
||||
@ -73,6 +133,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
@ -90,7 +153,59 @@ export const orgCategoryOptions = {
|
||||
root: [{ value: '1', label: '公司' }],
|
||||
// 子级部门
|
||||
child: [
|
||||
{ value: '4', label: '子公司' },
|
||||
{ value: '2', label: '部门' },
|
||||
{ value: '3', label: '岗位' },
|
||||
],
|
||||
//部门岗位
|
||||
childDepartPost: [
|
||||
{ value: '2', label: '部门' },
|
||||
{ value: '3', label: '岗位' },
|
||||
],
|
||||
//岗位
|
||||
childPost: [
|
||||
{ value: '3', label: '岗位' },
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
*/
|
||||
export const userColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '手机',
|
||||
width: 150,
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '主岗位',
|
||||
dataIndex: 'mainDepPostId_dictText',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 职位改变事件
|
||||
* @param value
|
||||
* @param model
|
||||
* @param treeData
|
||||
*/
|
||||
export function positionChange(value, model, treeData) {
|
||||
if(value && model.parentId){
|
||||
getPositionByDepartId({ parentId: model.parentId, departId: model.id ? model.id:'', positionId: value }).then((res) =>{
|
||||
if(res.success){
|
||||
treeData.value = res.result;
|
||||
}else{
|
||||
treeData.value = [];
|
||||
$message.warning(res.message);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
treeData.value = [];
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<a-col :xl="10" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
|
||||
</a-col>
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<a-col :xl="14" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%;" :class="[`${prefixCls}`]">
|
||||
<a-tabs v-show="departData != null" defaultActiveKey="base-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
|
||||
@ -16,6 +16,16 @@
|
||||
<DepartRuleTab :data="departData" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="职级汇报关系" key="rank">
|
||||
<div style="padding: 0 20px 20px">
|
||||
<DepartRankRelation :data="departData" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="用户列表" key="user">
|
||||
<div style="padding: 0 20px 20px">
|
||||
<DepartUserList :data="departData" :key="reRender"></DepartUserList>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-show="departData == null" style="padding-top: 40px">
|
||||
<a-empty description="尚未选择部门" />
|
||||
@ -31,6 +41,8 @@
|
||||
import DepartLeftTree from './components/DepartLeftTree.vue';
|
||||
import DepartFormTab from './components/DepartFormTab.vue';
|
||||
import DepartRuleTab from './components/DepartRuleTab.vue';
|
||||
import DepartRankRelation from './components/DepartRankRelation.vue';
|
||||
import DepartUserList from './components/DepartUserList.vue';
|
||||
|
||||
const { prefixCls } = useDesign('depart-manage');
|
||||
provide('prefixCls', prefixCls);
|
||||
@ -41,10 +53,15 @@
|
||||
// 当前选中的部门信息
|
||||
const departData = ref({});
|
||||
const rootTreeData = ref<any[]>([]);
|
||||
const reRender = ref(-1);
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
console.log('onTreeSelect: ', data);
|
||||
if (reRender.value == -1) {
|
||||
// 重新渲染组件
|
||||
reRender.value = Math.random();
|
||||
}
|
||||
departData.value = data;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user