第一次提交

This commit is contained in:
2026-04-03 09:56:14 +08:00
commit 60e2c8debd
3598 changed files with 746659 additions and 0 deletions

View 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>

View File

@@ -0,0 +1,78 @@
<template>
<BasicDrawer title="数据规则/按钮权限配置" :width="365" @close="onClose" @register="registerDrawer">
<a-spin :spinning="loading">
<a-tabs defaultActiveKey="1">
<a-tab-pane tab="数据规则" key="1">
<a-checkbox-group v-model:value="dataRuleChecked" v-if="dataRuleList.length > 0">
<a-row>
<a-col :span="24" v-for="(item, index) in dataRuleList" :key="'dr' + index">
<a-checkbox :value="item.id">{{ item.ruleName }}</a-checkbox>
</a-col>
<a-col :span="24">
<div style="width: 100%; margin-top: 15px">
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="saveDataRuleForRole">
<span>点击保存</span>
</a-button>
</div>
</a-col>
</a-row>
</a-checkbox-group>
<a-empty v-else description="无配置信息" />
</a-tab-pane>
</a-tabs>
</a-spin>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { queryDepartDataRule, saveDepartDataRule } from '../depart.api';
defineEmits(['register']);
const loading = ref<boolean>(false);
const departId = ref('');
const functionId = ref('');
const dataRuleList = ref<Array<any>>([]);
const dataRuleChecked = ref<Array<any>>([]);
// 注册抽屉组件
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
departId.value = unref(data.departId);
functionId.value = unref(data.functionId);
loadData();
});
async function loadData() {
try {
loading.value = true;
const { datarule, drChecked } = await queryDepartDataRule(functionId, departId);
dataRuleList.value = datarule;
if (drChecked) {
dataRuleChecked.value = drChecked.split(',');
}
} finally {
loading.value = false;
}
}
function saveDataRuleForRole() {
let params = {
departId: departId.value,
permissionId: functionId.value,
dataRuleIds: dataRuleChecked.value.join(','),
};
saveDepartDataRule(params);
}
function onClose() {
doReset();
}
function doReset() {
functionId.value = '';
dataRuleList.value = [];
dataRuleChecked.value = [];
}
</script>

View File

@@ -0,0 +1,154 @@
<template>
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
<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>
<script lang="ts" setup>
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { BasicModal, useModalInner } from '/@/components/Modal';
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({
rootTreeData: { type: Array, default: () => [] },
});
const prefixCls = inject('prefixCls');
// 当前是否是更新模式
const isUpdate = ref<boolean>(false);
// 当前的弹窗数据
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(treeData).basicFormSchema,
showActionButtonGroup: false,
});
// 注册弹窗
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
isUpdate.value = unref(data?.isUpdate);
// 当前是否为添加子级
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([
{
field: 'parentId',
show: isChild,
componentProps: {
// 如果是添加子部门,就禁用该字段
disabled: isChild,
treeData: props.rootTreeData,
},
},
{
field: 'orgCode',
show: false,
},
{
field: 'orgCategory',
componentProps: { options: categoryOptions },
},
]);
let record = unref(data?.record);
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: company?categoryOptions[1].value:categoryOptions[0].value,
},
record
);
model.value = record;
await setFieldsValue({ ...record });
});
// 提交事件
async function handleOk() {
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);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
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>

View File

@@ -0,0 +1,177 @@
<template>
<a-spin :spinning="loading">
<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>
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button>
</div>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { saveOrUpdateDepart } from '../depart.api';
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');
const emit = defineEmits(['success']);
const props = defineProps({
data: { type: Object, default: () => ({}) },
rootTreeData: { type: Array, default: () => [] },
});
const loading = ref<boolean>(false);
// 当前是否是更新模式
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(treeData).basicFormSchema,
showActionButtonGroup: false,
});
const categoryOptions = computed(() => {
if (!!props?.data?.parentId) {
return orgCategoryOptions.child;
} else {
return orgCategoryOptions.root;
}
});
onMounted(() => {
// 禁用字段
updateSchema([
{ field: 'parentId', componentProps: { disabled: true } },
{ field: 'orgCode', componentProps: { disabled: true } },
]);
// data 变化,重填表单
watch(
() => props.data,
async () => {
let record = unref(props.data);
if (typeof record !== 'object') {
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 });
},
{ deep: true, immediate: true }
);
// 更新 父部门 选项
watch(
() => props.rootTreeData,
async () => {
updateSchema([
{
field: 'parentId',
componentProps: { treeData: props.rootTreeData },
},
]);
},
{ deep: true, immediate: true }
);
// 监听并更改 orgCategory options
watch(
categoryOptions,
async () => {
updateSchema([
{
field: 'orgCategory',
componentProps: { options: categoryOptions.value },
},
]);
},
{ immediate: true }
);
});
// 重置表单
async function onReset() {
await resetFields();
await setFieldsValue({ ...model.value });
}
// 提交事件
async function onSubmit() {
try {
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);
//刷新列表
emit('success');
Object.assign(model.value, values);
} finally {
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">
@prefix-cls: ~'@{namespace}-j-depart-form-content';
/*begin 兼容暗夜模式*/
.@{prefix-cls} {
background: @component-background;
border-top: 1px solid @border-color-base;
}
/*end 兼容暗夜模式*/
</style>
<style lang="less" scoped>
:deep(.ant-select-selector .ant-select-selection-item){
svg{
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,481 @@
<template>
<a-card :bordered="false" style="height: 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" 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" v-if="!isTenantDepart">导出</a-button>
<template v-if="checkedKeys.length > 0">
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="onDeleteBatch">
<icon icon="ant-design:delete-outlined" />
<span>删除</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作 </span>
<icon icon="akar-icons:chevron-down" />
</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>
<template v-if="checkedKeys.length > 0">
<span>已选中 {{ checkedKeys.length }} 条记录</span>
<a-divider type="vertical" />
<a @click="checkedKeys = []">清空</a>
</template>
<template v-else>
<span>未选中任何数据</span>
</template>
</template>
</a-alert>
<a-spin :spinning="loading">
<a-input-search placeholder="按部门名称搜索…" style="margin-bottom: 10px" @search="onSearch" />
<!--组织机构树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
checkable
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@check="onCheck"
@select="onSelect"
draggable
@drop="onDrop"
@dragstart="onDragStart"
style="overflow-y: auto;height: calc(100vh - 330px);"
>
<template #title="{ key: treeKey, title, dataRef, data }">
<a-dropdown :trigger="['contextmenu']">
<Popconfirm
:open="visibleTreeKey === treeKey"
title="确定要删除吗?"
ok-text="确定"
cancel-text="取消"
placement="rightTop"
@confirm="onDelete(dataRef)"
@openChange="onVisibleChange"
>
<TreeIcon :orgCategory="dataRef.orgCategory" :title="title"></TreeIcon>
</Popconfirm>
<template #overlay>
<a-menu @click="">
<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>
</a-menu>
</template>
</a-dropdown>
</template>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</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, defineEmits, h } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useMethods } from '/@/hooks/system/useMethods';
import { Api, deleteBatchDepart, queryDepartAndPostTreeSync, updateChangeDepart } from '../depart.api';
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
import { Modal, 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);
// 部门树列表数据
const treeData = ref<any[]>([]);
// 当前选中的项
const checkedKeys = ref<any[]>([]);
// 当前展开的项
const expandedKeys = ref<any[]>([]);
// 当前选中的项
const selectedKeys = ref<any[]>([]);
// 树组件重新加载
const treeReloading = ref<boolean>(false);
// 树父子是否关联
const checkStrictly = ref<boolean>(true);
// 当前选中的部门
const currentDepart = ref<any>(null);
// 控制确认删除提示框是否显示
const visibleTreeKey = ref<any>(null);
// 搜索关键字
const searchKeyword = ref('');
// 提示弹窗是否显示
const tipShow = ref<boolean>(false);
// 注册 modal
const [registerModal, { openModal }] = useModal();
// 加载顶级部门信息
async function loadRootTreeData() {
try {
loading.value = true;
treeData.value = [];
const result = await queryDepartAndPostTreeSync();
if (Array.isArray(result)) {
treeData.value = result;
}
if (expandedKeys.value.length === 0) {
autoExpandParentNode();
} else {
if (selectedKeys.value.length === 0) {
let item = treeData.value[0];
if (item) {
// 默认选中第一个
setSelectedKey(item.id, item);
}
} else {
emit('select', currentDepart.value);
}
}
emit('rootTreeData', treeData.value);
} finally {
loading.value = false;
}
}
loadRootTreeData();
// 加载子级部门信息
async function loadChildrenTreeData(treeNode) {
try {
const result = await queryDepartAndPostTreeSync({
pid: treeNode.dataRef.id,
});
if (result.length == 0) {
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result;
if (expandedKeys.value.length > 0) {
// 判断获取的子级是否有当前展开的项
let subKeys: any[] = [];
for (let key of expandedKeys.value) {
if (result.findIndex((item) => item.id === key) !== -1) {
subKeys.push(key);
}
}
if (subKeys.length > 0) {
expandedKeys.value = [...expandedKeys.value];
}
}
}
treeData.value = [...treeData.value];
emit('rootTreeData', treeData.value);
} catch (e) {
console.error(e);
}
return Promise.resolve();
}
// 自动展开父节点,只展开一级
function autoExpandParentNode() {
let item = treeData.value[0];
if (item) {
if (!item.isLeaf) {
expandedKeys.value = [item.key];
}
// 默认选中第一个
setSelectedKey(item.id, item);
reloadTree();
} else {
emit('select', null);
}
}
// 重新加载树组件,防止无法默认展开数据
async function reloadTree() {
await nextTick();
treeReloading.value = true;
await nextTick();
treeReloading.value = false;
}
/**
* 设置当前选中的行
*/
function setSelectedKey(key: string, data?: object) {
selectedKeys.value = [key];
if (data) {
currentDepart.value = data;
emit('select', data);
}
}
// 添加一级部门
function onAddDepart() {
openModal(true, { isUpdate: false, isChild: false });
}
// 添加子级部门
function onAddChildDepart(data = currentDepart.value) {
if (data == null) {
createMessage.warning('请先选择一个部门');
return;
}
if(data.orgCategory === '3'){
createMessage.warning('岗位下无法添加子级!');
return;
}
const record = { parentId: data.id, orgCategory: data.orgCategory };
openModal(true, { isUpdate: false, isChild: true, record });
}
// 搜索事件
async function onSearch(value: string) {
if (value) {
try {
loading.value = true;
treeData.value = [];
let result = await searchByKeywords({ keyWord: value, orgCategory: "1,2,3,4" });
if (Array.isArray(result)) {
treeData.value = result;
}
autoExpandParentNode();
} finally {
loading.value = false;
}
} else {
loadRootTreeData();
}
searchKeyword.value = value;
}
// 树复选框选择事件
function onCheck(e) {
if (Array.isArray(e)) {
checkedKeys.value = e;
} else {
checkedKeys.value = e.checked;
}
}
// 树选择事件
function onSelect(selKeys, event) {
console.log('select: ', selKeys, event);
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
setSelectedKey(selKeys[0], event.selectedNodes[0]);
} else {
// 这样可以防止用户取消选择
setSelectedKey(selectedKeys.value[0]);
}
}
/**
* 根据 ids 删除部门
* @param idListRef array
* @param confirm 是否显示确认提示框
*/
async function doDeleteDepart(idListRef, confirm = true) {
const idList = unref(idListRef);
if (idList.length > 0) {
try {
loading.value = true;
await deleteBatchDepart({ ids: idList.join(',') }, confirm);
await loadRootTreeData();
} finally {
loading.value = false;
}
}
}
// 删除单个部门
async function onDelete(data) {
if (data) {
onVisibleChange(false);
doDeleteDepart([data.id], false);
}
}
// 批量删除部门
async function onDeleteBatch() {
try {
await doDeleteDepart(checkedKeys);
checkedKeys.value = [];
} finally {
}
}
function onVisibleChange(visible) {
if (!visible) {
visibleTreeKey.value = null;
}
}
function onImportXls(d) {
handleImportXls(d, Api.importExcelUrl, () => {
loadRootTreeData();
});
}
function onExportXls() {
// 代码逻辑说明: 【TV360X-1671】部门管理不支持选中的记录导出---
let params = {}
if(checkedKeys.value && checkedKeys.value.length > 0) {
params['selections'] = checkedKeys.value.join(',')
}
handleExportXls('部门信息', Api.exportXlsUrl,params);
}
/**
* 拖拽开始时,只关闭被拖拽的当前节点
*
* @param info
*/
function onDragStart(info: any) {
const dragKey = info.node?.key;
if (!dragKey){
return;
}
// 只关闭被拖拽的当前节点,不关闭其子节点
if (expandedKeys.value.includes(dragKey)) {
expandedKeys.value = expandedKeys.value.filter(key => key !== dragKey);
}
}
/**
* 拖拽结束
* @param info
*/
function onDrop (info){
const dropKey = info.node.key;
const dragKey = info.dragNode.key;
const dropPos = info.node.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const dropTitle = info.node.title;
const dragTitle = info.dragNode.title;
//禁止拖拽到子节点
if (isDescendant(info.dragNode, info.node.key)) {
createMessage.warning('不能拖拽到自身后代');
return;
}
if(dropKey === dragKey){
createMessage.warning('不能自身拖拽到自身');
return;
}
let pos = "中";
if(dropPosition === -1){
pos = "上方";
}else if (dropPosition === 1){
pos = "下方";
}
let text = "将【" + dragTitle + "】移动到【" + dropTitle + "】" + pos + "";
Modal.confirm({
title: '确认移动',
content: h('div', {}, [
h('p', { style: { marginBottom: '12px', fontSize: '14px' } }, text),
h('p', {
style: {
color: '#ff4d4f',
fontSize: '13px',
margin: '0'
}
}, '移动后:机构编码会改变,历史业务数据保留原机构编码,此操作不可撤销!')
]),
okText: '确认',
cancelText: '取消',
onOk: () => {
updateChangeDepart({ dragId: dragKey, dropId: dropKey, dropPosition: dropPosition, sort: info.dropPosition }).then(res=>{
if(res.success){
createMessage.success('部门顺序调整成功');
//重新加载树
treeData.value = [];
selectedKeys.value = [];
loadRootTreeData();
} else {
createMessage.error(res.message);
}
}).catch(e=>{
createMessage.error(e.message);
})
}
})
}
/**
* 判断目标节点是否在拖拽节点的子树中(避免循环引用)
*
* @param dragNode
* @param targetKey
*/
function isDescendant(dragNode, targetKey) {
const stack = [...(dragNode.children ?? [])];
while (stack.length) {
const node = stack.pop()!;
if (node.key === targetKey){
return true;
}
if (node.children){
stack.push(...node.children);
}
}
return false;
}
defineExpose({
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>

View File

@@ -0,0 +1,92 @@
<template>
<a-spin :spinning="loading">
<template v-if="treeData && treeData.length > 0">
<div style="margin-top: 10px;margin-bottom: 10px;display: flex">
<a-button preIcon="ant-design:down-outlined" @click="expandAll(true)" type="primary">展开全部</a-button>
<a-button preIcon="ant-design:up-outlined" @click="expandAll(false)" type="primary" style="margin-left: 10px">折叠全部</a-button>
</div>
<BasicTree
:expandedKeys="expandedKeys"
:fieldNames="{ children: 'children', title: 'title', key: 'value' }"
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[]>([]);
//选中的key
const expandedKeys = ref<any[]>([]);
//所有的部门id
const departIds = 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;
departIds.value = getParentDepartmentIds(res.result);
}
});
} finally {
loading.value = false;
}
}
/**
* 折叠全部
*
* @param expandAll
*/
async function expandAll(expandAll) {
if (!expandAll) {
expandedKeys.value = [];
} else {
expandedKeys.value = departIds.value;
}
}
/**
* 获取存在子级的部门id
* @param departments
*/
function getParentDepartmentIds(departments) {
const ids: any = [];
departments.forEach((dept) => {
// 检查是否有 children 数组且不为空
if (dept.children && Array.isArray(dept.children) && dept.children.length > 0) {
ids.push(dept.id);
// 递归检查子部门是否也有子级
ids.push(...getParentDepartmentIds(dept.children));
}
});
return ids;
}
</script>
<style lang="less" scoped>
.depart-rule-tree :deep(.scrollbar__bar) {
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,267 @@
<template>
<a-spin :spinning="loading">
<template v-if="treeData.length > 0">
<BasicTree
ref="basicTree"
class="depart-rule-tree"
checkable
:treeData="treeData"
:checkedKeys="checkedKeys"
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
:checkStrictly="true"
style="height: 500px; overflow: auto"
@check="onCheck"
@expand="onExpand"
@select="onSelect"
>
<template #title="{ slotTitle, ruleFlag }">
<span>{{ slotTitle }}</span>
<Icon v-if="ruleFlag" icon="ant-design:align-left-outlined" style="margin-left: 5px; color: red" />
</template>
</BasicTree>
</template>
<a-empty v-else description="无可配置部门权限" />
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]">
<a-dropdown :trigger="['click']" placement="top">
<template #overlay>
<a-menu>
<a-menu-item key="3" @click="toggleCheckALL(true)">{{ t('component.tree.selectAll') }}</a-menu-item>
<a-menu-item key="4" @click="toggleCheckALL(false)">{{ t('component.tree.unSelectAll') }}</a-menu-item>
<a-menu-item key="5" @click="toggleExpandAll(true)">{{ t('component.tree.expandAll') }}</a-menu-item>
<a-menu-item key="6" @click="toggleExpandAll(false)">{{ t('component.tree.unExpandAll') }}</a-menu-item>
<a-menu-item key="7" @click="toggleRelationAll(false)">{{ t('component.tree.checkStrictly') }}</a-menu-item>
<a-menu-item key="8" @click="toggleRelationAll(true)">{{ t('component.tree.checkUnStrictly') }}</a-menu-item>
</a-menu>
</template>
<a-button style="float: left">
树操作
<Icon icon="ant-design:up-outlined" />
</a-button>
</a-dropdown>
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button>
</div>
</div>
</a-spin>
<DepartDataRuleDrawer @register="registerDataRuleDrawer" />
</template>
<script lang="ts" setup>
import { watch, computed, inject, ref, nextTick } from 'vue';
import { useDrawer } from '/@/components/Drawer';
import { BasicTree } from '/@/components/Tree/index';
import DepartDataRuleDrawer from './DepartDataRuleDrawer.vue';
import { queryRoleTreeList, queryDepartPermission, saveDepartPermission } from '../depart.api';
import { useDesign } from '/@/hooks/web/useDesign';
import { translateTitle } from '/@/utils/common/compUtils';
import { DEPART_MANGE_AUTH_CONFIG_KEY } from '/@/enums/cacheEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { prefixCls } = useDesign('j-depart-form-content');
const props = defineProps({
data: { type: Object, default: () => ({}) },
});
// 当前选中的部门ID可能会为空代表未选择部门
const departId = computed(() => props.data?.id);
const basicTree = ref();
const loading = ref<boolean>(false);
//树的全部节点信息
const allTreeKeys = ref([]);
const treeData = ref<any[]>([]);
const expandedKeys = ref<Array<any>>([]);
const selectedKeys = ref<Array<any>>([]);
const checkedKeys = ref<Array<any>>([]);
const lastCheckedKeys = ref<Array<any>>([]);
const checkStrictly = ref(false);
const { t } = useI18n();
// 注册数据规则授权弹窗抽屉
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
// onCreated
loadData({
success: (ids) => {
// 代码逻辑说明: 【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
const localData = localStorage.getItem(DEPART_MANGE_AUTH_CONFIG_KEY);
if (localData) {
const obj = JSON.parse(localData);
obj.level && toggleRelationAll(obj.level == 'relation' ? false : true);
obj.expand && toggleExpandAll(obj.expand == 'openAll' ? true :false);
} else {
// expandedKeys.value = ids;
}
}
});
watch(departId, () => loadDepartPermission(), { immediate: true });
async function loadData(options: any = {}) {
try {
loading.value = true;
let { treeList, ids } = await queryRoleTreeList();
// 代码逻辑说明: 【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
treeData.value = translateTitle(treeList);
// 代码逻辑说明: 【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
allTreeKeys.value = ids;
options.success?.(ids);
} finally {
loading.value = false;
}
}
async function loadDepartPermission() {
if (departId.value) {
try {
loading.value = true;
let keys = await queryDepartPermission({ departId: departId.value });
checkedKeys.value = keys;
lastCheckedKeys.value = [...keys];
} finally {
loading.value = false;
}
}
}
async function onSubmit() {
try {
loading.value = true;
await saveDepartPermission({
departId: departId.value,
permissionIds: checkedKeys.value.join(','),
lastpermissionIds: lastCheckedKeys.value.join(','),
});
await loadData();
await loadDepartPermission();
} finally {
loading.value = false;
}
}
/**
* 点击选中
* 2024-07-04
* liaozhiyang
*/
function onCheck(o, e) {
// checkStrictly: true=>层级独立false=>层级关联.
if (checkStrictly.value) {
checkedKeys.value = o.checked ? o.checked : o;
} else {
const keys = getNodeAllKey(e.node, 'children', 'key');
if (e.checked) {
// 反复操作下可能会有重复的keys得用new Set去重下
checkedKeys.value = [...new Set([...checkedKeys.value, ...keys])];
} else {
const result = removeMatchingItems(checkedKeys.value, keys);
checkedKeys.value = result;
}
}
}
/**
* 2024-07-04
* liaozhiyang
* 删除相匹配数组的项
*/
function removeMatchingItems(arr1, arr2) {
// 使用哈希表记录 arr2 中的元素
const hashTable = {};
for (const item of arr2) {
hashTable[item] = true;
}
// 使用 filter 方法遍历第一个数组,过滤出不在哈希表中存在的项
return arr1.filter((item) => !hashTable[item]);
}
/**
* 2024-07-04
* liaozhiyang
* 获取当前节点及以下所有子孙级的key
*/
function getNodeAllKey(node: any, children: any, key: string) {
const result: any = [];
result.push(node[key]);
const recursion = (data) => {
data.forEach((item: any) => {
result.push(item[key]);
if (item[children]?.length) {
recursion(item[children]);
}
});
};
node[children]?.length && recursion(node[children]);
return result;
}
// tree展开事件
function onExpand($expandedKeys) {
expandedKeys.value = $expandedKeys;
}
// tree选中事件
function onSelect($selectedKeys, { selectedNodes }) {
if (selectedNodes[0]?.ruleFlag) {
let functionId = $selectedKeys[0];
dataRuleDrawer.openDrawer(true, { departId, functionId });
}
selectedKeys.value = [];
}
// 切换父子关联
async function toggleCheckStrictly(flag) {
checkStrictly.value = flag;
await nextTick();
checkedKeys.value = basicTree.value.getCheckedKeys();
}
// 切换展开收起
async function toggleExpandAll(flag) {
// 代码逻辑说明: 【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
if (flag) {
expandedKeys.value = allTreeKeys.value;
saveLocalOperation('expand', 'openAll');
} else {
expandedKeys.value = [];
saveLocalOperation('expand', 'closeAll');
}
}
// 切换全选
async function toggleCheckALL(flag) {
// 代码逻辑说明: 【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
if (flag) {
checkedKeys.value = allTreeKeys.value;
} else {
checkedKeys.value = [];
}
}
// 切换层级关联(独立)
const toggleRelationAll = (flag) => {
// 代码逻辑说明: 【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
checkStrictly.value = flag;
if (flag) {
saveLocalOperation('level', 'standAlone');
} else {
saveLocalOperation('level', 'relation');
}
};
/**
* 2024-07-04
* liaozhiyang
* 缓存
* */
const saveLocalOperation = (key, value) => {
const localData = localStorage.getItem(DEPART_MANGE_AUTH_CONFIG_KEY);
const obj = localData ? JSON.parse(localData) : {};
obj[key] = value;
localStorage.setItem(DEPART_MANGE_AUTH_CONFIG_KEY, JSON.stringify(obj))
};
</script>
<style lang="less" scoped>
// 【VUEN-188】解决滚动条不灵敏的问题
.depart-rule-tree :deep(.scrollbar__bar) {
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,182 @@
<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 { queryDepartPostByOrgCode } 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: queryDepartPostByOrgCode,
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) {
record.activitiSync = record.activitiSync? Number(record.activitiSync) : 1;
openDrawer(true, {
record,
isUpdate: true,
showFooter: false,
});
}
// 编辑用户信息
function editUserInfo(record) {
record.activitiSync = record.activitiSync? Number(record.activitiSync) : 1;
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>

View File

@@ -0,0 +1,44 @@
<!-- 部门负责人页面 -->
<template>
<!--引用表格-->
<BasicTable @register="registerTable"></BasicTable>
</template>
<script setup lang="ts" name="DepartmentHeadList">
import { BasicTable } from '@/components/Table';
import { useListPage } from '@/hooks/system/useListPage';
import { userColumns } from '@/views/system/depart/depart.data';
import { getDepartmentHead } from '../depart.api';
import { computed, watch } from 'vue';
const props = defineProps({
data: { require: true, type: Object },
});
// 当前部门id
const departId = computed(() => props.data?.id);
// 列表页面公共参数、方法
const { tableContext } = useListPage({
tableProps: {
api: getDepartmentHead,
columns: userColumns,
canResize: false,
rowKey: 'id',
tableSetting: { cacheKey: 'depart_head_list' },
// 请求之前对参数做处理
beforeFetch(params) {
return Object.assign(params, { departId: departId.value });
},
showActionColumn: false,
immediate: !!departId.value,
},
});
// 注册 ListTable
const [registerTable, { reload }] = tableContext;
watch(
() => props.data,
() => reload()
);
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,171 @@
import { unref } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
export enum Api {
queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync',
save = '/sys/sysDepart/add',
edit = '/sys/sysDepart/edit',
delete = '/sys/sysDepart/delete',
deleteBatch = '/sys/sysDepart/deleteBatch',
exportXlsUrl = '/sys/sysDepart/exportXls',
importExcelUrl = '/sys/sysDepart/importExcel',
roleQueryTreeList = '/sys/role/queryTreeList',
queryDepartPermission = '/sys/permission/queryDepartPermission',
saveDepartPermission = '/sys/permission/saveDepartPermission',
dataRule = '/sys/sysDepartPermission/datarule',
getCurrentUserDeparts = '/sys/user/getCurrentUserDeparts',
selectDepart = '/sys/selectDepart',
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',
//获取部门和岗位下的成员
queryDepartPostByOrgCode = '/sys/user/queryDepartPostByOrgCode',
//更新拖拽部门后的位置
updateChangeDepart = '/sys/sysDepart/updateChangeDepart',
//获取负责部门
getDepartmentHead = '/sys/sysDepart/getDepartmentHead',
}
/**
* 获取部门树列表
*/
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params });
/**
* 获取部门和岗位树列表
*/
export const queryDepartAndPostTreeSync = (params?) => defHttp.get({ url: Api.queryDepartAndPostTreeSync, params });
/**
* 保存或者更新部门角色
*/
export const saveOrUpdateDepart = (params, isUpdate) => {
if (isUpdate) {
return defHttp.put({ url: Api.edit, params });
} else {
return defHttp.post({ url: Api.save, params });
}
};
/**
* 批量删除部门角色
*/
export const deleteBatchDepart = (params, confirm = false) => {
return new Promise((resolve, reject) => {
const doDelete = () => {
resolve(defHttp.delete({ url: Api.deleteBatch, params }, { joinParamsToUrl: true }));
};
if (confirm) {
createConfirm({
iconType: 'warning',
title: '删除',
content: '确定要删除吗?',
onOk: () => doDelete(),
onCancel: () => reject(),
});
} else {
doDelete();
}
});
};
/**
* 获取权限树列表
*/
export const queryRoleTreeList = (params?) => defHttp.get({ url: Api.roleQueryTreeList, params });
/**
* 查询部门权限
*/
export const queryDepartPermission = (params?) => defHttp.get({ url: Api.queryDepartPermission, params });
/**
* 保存部门权限
*/
export const saveDepartPermission = (params) => defHttp.post({ url: Api.saveDepartPermission, params });
/**
* 查询部门数据权限列表
*/
export const queryDepartDataRule = (functionId, departId, params?) => {
let url = `${Api.dataRule}/${unref(functionId)}/${unref(departId)}`;
return defHttp.get({ url, params });
};
/**
* 保存部门数据权限
*/
export const saveDepartDataRule = (params) => defHttp.post({ url: Api.dataRule, params });
/**
* 获取登录用户部门信息
*/
export const getUserDeparts = (params?) => defHttp.get({ url: Api.getCurrentUserDeparts, params });
/**
* 切换选择部门
*/
export const selectDepart = (params?) => defHttp.put({ url: Api.selectDepart, params });
/**
* 编辑部门前获取部门相关信息
* @param id
*/
export const getUpdateDepartInfo = (id) => defHttp.get({ url: Api.getUpdateDepartInfo, params: {id} });
/**
* 编辑部门
* @param params
*/
export const doUpdateDepartInfo = (params) => defHttp.put({ url: Api.doUpdateDepartInfo, params });
/**
* 删除部门
* @param id
*/
export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ id } }, { joinParamsToUrl: true });
/**
* 设置负责人 取消负责人
* @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 queryDepartPostByOrgCode = (params) => defHttp.get({ url: Api.queryDepartPostByOrgCode, params });
/**
* 更新拖拽部门后的位置
*
* @param params
*/
export const updateChangeDepart = (params) => defHttp.put({ url: Api.updateChangeDepart, params },{ isTransformResponse: false });
/**
* 获取负责部门
*
* @param params
*/
export const getDepartmentHead = (params) => defHttp.get({ url: Api.getDepartmentHead, params });

View File

@@ -0,0 +1,247 @@
import { FormSchema } from '/@/components/Form';
import { getPositionByDepartId } from "./depart.api";
import { useMessage } from "@/hooks/web/useMessage";
import { BasicColumn } from "@/components/Table";
import {
getDepartName,
getDepartPathName,
getDepartPathNameByOrgCode,
getMultiDepartPathName
} from '@/utils/common/compUtils';
import { h, ref } from 'vue';
const { createMessage: $message } = useMessage();
//部门名称
const departNamePath = ref<Record<string, string>>({});
// 部门基础表单
export function useBasicFormSchema(treeData) {
const basicFormSchema: FormSchema[] = [
{
field: 'departName',
label: '机构名称',
component: 'Input',
componentProps: {
placeholder: '请输入机构/部门名称',
},
rules: [{ required: true, message: '机构名称不能为空' }],
},
{
field: 'departNameAbbr',
label: '机构简称',
component: 'Input',
componentProps: {
placeholder: '请输入机构/部门简称',
}
},
{
field: 'parentId',
label: '上级部门',
component: 'TreeSelect',
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;
});
},
},
},
{
field: 'orgCode',
label: '机构编码',
component: 'Input',
componentProps: {
placeholder: '请输入机构编码',
},
},
{
field: 'orgCategory',
label: '机构类型',
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: '排序',
component: 'InputNumber',
componentProps: {},
},
{
field: 'mobile',
label: '电话',
component: 'Input',
componentProps: {
placeholder: '请输入电话',
},
ifShow:({ values })=>{
return values.orgCategory !== '3'
},
},
{
field: 'fax',
label: '传真',
component: 'Input',
componentProps: {
placeholder: '请输入传真',
},
ifShow:({ values })=>{
return values.orgCategory !== '3'
},
},
{
field: 'address',
label: '地址',
component: 'Input',
componentProps: {
placeholder: '请输入地址',
},
ifShow:({ values })=>{
return values.orgCategory !== '3'
},
},
{
field: 'memo',
label: '备注',
component: 'InputTextArea',
componentProps: {
placeholder: '请输入备注',
},
ifShow:({ values })=>{
return values.orgCategory !== '3'
},
},
{
field: 'id',
label: 'ID',
component: 'Input',
show: false,
},
];
return { basicFormSchema };
}
// 机构类型选项
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',
customRender:( { record, text })=>{
if(record.izHideContact && record.izHideContact === '1'){
return '/';
}
return text;
}
},
{
title: '主岗位',
dataIndex: 'mainDepPostId',
customRender: ({ record, text })=>{
if(!text){
return '';
}
return getDepartName(getDepartPathName(record.mainDepPostId_dictText,text,false));
},
width: 200,
},
{
title: '兼职岗位',
dataIndex: 'otherDepPostId',
customRender: ({ record, text })=>{
if(!text){
return '';
}
return getDepartName(getMultiDepartPathName(record.otherDepPostId_dictText,text));
},
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 = [];
}
}

View File

@@ -0,0 +1,14 @@
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-depart-manage';
.@{prefix-cls} {
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
background: @component-background;
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
&--box {
.ant-tabs-nav {
padding: 0 20px;
}
}
}

View File

@@ -0,0 +1,84 @@
<template>
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
<a-col :xl="10" :lg="24" :md="24" style="margin-bottom: 10px">
<DepartLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
</a-col>
<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">
<div style="padding: 20px">
<DepartFormTab :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" />
</div>
</a-tab-pane>
<a-tab-pane tab="部门权限" key="role-info">
<div style="padding: 0 20px 20px">
<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-tab-pane tab="部门负责人" key="departmentHead">
<DepartmentHeadList :data="departData"></DepartmentHeadList>
</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="system-depart">
import { provide, ref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
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';
import DepartmentHeadList from './components/DepartmentHeadList.vue';
const { prefixCls } = useDesign('depart-manage');
provide('prefixCls', prefixCls);
// 给子组件定义一个ref变量
const leftTree = ref();
// 当前选中的部门信息
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;
}
// 左侧树rootTreeData触发
function onRootTreeData(data) {
rootTreeData.value = data;
}
function onSuccess() {
leftTree.value.loadRootTreeData();
}
</script>
<style lang="less">
@import './index.less';
</style>