first commit
This commit is contained in:
@@ -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>
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
|
||||
<BasicForm @register="registerForm" />
|
||||
</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';
|
||||
|
||||
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 [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().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;
|
||||
// 隐藏不需要展示的字段
|
||||
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 = {};
|
||||
}
|
||||
// 赋默认值
|
||||
record = Object.assign(
|
||||
{
|
||||
departOrder: 0,
|
||||
orgCategory: categoryOptions[0].value,
|
||||
},
|
||||
record
|
||||
);
|
||||
model.value = record;
|
||||
await setFieldsValue({ ...record });
|
||||
});
|
||||
|
||||
// 提交事件
|
||||
async function handleOk() {
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
let values = await validate();
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<BasicForm @register="registerForm" />
|
||||
<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 } from '../depart.data';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
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 [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().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;
|
||||
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);
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//刷新列表
|
||||
emit('success');
|
||||
Object.assign(model.value, values);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
|
||||
@prefix-cls: ~'@{namespace}-j-depart-form-content';
|
||||
/*begin 兼容暗夜模式*/
|
||||
.@{prefix-cls} {
|
||||
background: @component-background;
|
||||
border-top: 1px solid @border-color-base;
|
||||
}
|
||||
/*end 兼容暗夜模式*/
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
</style>
|
||||
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<div class="j-table-operator" style="width: 100%">
|
||||
<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-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>
|
||||
<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>
|
||||
</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"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, dataRef }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<Popconfirm
|
||||
:open="visibleTreeKey === treeKey"
|
||||
title="确定要删除吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
placement="rightTop"
|
||||
@confirm="onDelete(dataRef)"
|
||||
@openChange="onVisibleChange"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
</Popconfirm>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="">
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, unref } 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 { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select', 'rootTreeData']);
|
||||
const { createMessage } = useMessage();
|
||||
const { handleImportXls, handleExportXls } = useMethods();
|
||||
|
||||
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('');
|
||||
|
||||
// 注册 modal
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 加载顶级部门信息
|
||||
async function loadRootTreeData() {
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
const result = await queryDepartTreeSync();
|
||||
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 queryDepartTreeSync({
|
||||
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;
|
||||
}
|
||||
const record = { parentId: data.id };
|
||||
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 });
|
||||
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() {
|
||||
//update-begin---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
|
||||
let params = {}
|
||||
if(checkedKeys.value && checkedKeys.value.length > 0) {
|
||||
params['selections'] = checkedKeys.value.join(',')
|
||||
}
|
||||
handleExportXls('部门信息', Api.exportXlsUrl,params);
|
||||
//update-end---author:wangshuai---date:2024-07-05---for:【TV360X-1671】部门管理不支持选中的记录导出---
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadRootTreeData,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,273 @@
|
||||
<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) => {
|
||||
// update-begin--author:liaozhiyang---date:20240704---for:【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;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
}
|
||||
});
|
||||
watch(departId, () => loadDepartPermission(), { immediate: true });
|
||||
|
||||
async function loadData(options: any = {}) {
|
||||
try {
|
||||
loading.value = true;
|
||||
let { treeList, ids } = await queryRoleTreeList();
|
||||
//update-begin---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
treeData.value = translateTitle(treeList);
|
||||
//update-end---author:wangshuai---date:2024-04-08---for:【issues/1169】部门管理功能中的【部门权限】中未翻译 t('') 多语言---
|
||||
// update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
allTreeKeys.value = ids;
|
||||
options.success?.(ids);
|
||||
// update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
} 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) {
|
||||
// update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
if (flag) {
|
||||
expandedKeys.value = allTreeKeys.value;
|
||||
saveLocalOperation('expand', 'openAll');
|
||||
} else {
|
||||
expandedKeys.value = [];
|
||||
saveLocalOperation('expand', 'closeAll');
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
}
|
||||
|
||||
// 切换全选
|
||||
async function toggleCheckALL(flag) {
|
||||
// update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
if (flag) {
|
||||
checkedKeys.value = allTreeKeys.value;
|
||||
} else {
|
||||
checkedKeys.value = [];
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
}
|
||||
|
||||
// 切换层级关联(独立)
|
||||
const toggleRelationAll = (flag) => {
|
||||
// update-begin--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
checkStrictly.value = flag;
|
||||
if (flag) {
|
||||
saveLocalOperation('level', 'standAlone');
|
||||
} else {
|
||||
saveLocalOperation('level', 'relation');
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240704---for:【TV360X-1689】同步系统角色改法加上缓存层级关联等功能
|
||||
};
|
||||
/**
|
||||
* 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>
|
||||
Reference in New Issue
Block a user