forked from wangjiadong/comp
Compare commits
2 Commits
0820c60529
...
0c12dc5341
Author | SHA1 | Date |
---|---|---|
喻忠伟 | 0c12dc5341 | 4 months ago |
喻忠伟 | c930d4fd5e | 4 months ago |
21 changed files with 1197 additions and 32 deletions
@ -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,135 @@ |
||||
<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 { Api, queryDepartSync } from '../depart.api'; |
||||
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; |
||||
} |
||||
}); |
||||
const setFormValue = async() => { |
||||
const res = await queryDepartSync() |
||||
await setFieldsValue({ ...res }); |
||||
} |
||||
onMounted(() => { |
||||
setFormValue() |
||||
// 禁用字段 |
||||
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; |
||||
// console.log(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,332 @@ |
||||
<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 |
||||
:visible="visibleTreeKey === treeKey" |
||||
title="确定要删除吗?" |
||||
ok-text="确定" |
||||
cancel-text="取消" |
||||
placement="rightTop" |
||||
@confirm="onDelete(dataRef)" |
||||
@visibleChange="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, defineExpose } 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() { |
||||
handleExportXls('部门信息', Api.exportXlsUrl); |
||||
} |
||||
|
||||
defineExpose({ |
||||
loadRootTreeData, |
||||
}); |
||||
</script> |
@ -0,0 +1,170 @@ |
||||
<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="checkStrictly" |
||||
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)">全部勾选</a-menu-item> |
||||
<a-menu-item key="4" @click="toggleCheckALL(false)">取消全选</a-menu-item> |
||||
<a-menu-item key="5" @click="toggleExpandAll(true)">展开所有</a-menu-item> |
||||
<a-menu-item key="6" @click="toggleExpandAll(false)">收起所有</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'; |
||||
|
||||
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 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(true); |
||||
|
||||
// 注册数据规则授权弹窗抽屉 |
||||
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer(); |
||||
|
||||
// onCreated |
||||
loadData(); |
||||
watch(departId, () => loadDepartPermission(), { immediate: true }); |
||||
|
||||
async function loadData() { |
||||
try { |
||||
loading.value = true; |
||||
let { treeList } = await queryRoleTreeList(); |
||||
treeData.value = treeList; |
||||
await nextTick(); |
||||
toggleExpandAll(true); |
||||
} 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; |
||||
} |
||||
} |
||||
|
||||
// tree勾选复选框事件 |
||||
function onCheck(event) { |
||||
if (!Array.isArray(event)) { |
||||
checkedKeys.value = event.checked; |
||||
} else { |
||||
checkedKeys.value = event; |
||||
} |
||||
} |
||||
|
||||
// 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) { |
||||
basicTree.value.expandAll(flag); |
||||
await nextTick(); |
||||
expandedKeys.value = basicTree.value.getExpandedKeys(); |
||||
} |
||||
|
||||
// 切换全选 |
||||
async function toggleCheckALL(flag) { |
||||
basicTree.value.checkAll(flag); |
||||
await nextTick(); |
||||
checkedKeys.value = basicTree.value.getCheckedKeys(); |
||||
} |
||||
</script> |
||||
|
||||
<style lang="less" scoped> |
||||
// 【VUEN-188】解决滚动条不灵敏的问题 |
||||
.depart-rule-tree :deep(.scrollbar__bar) { |
||||
pointer-events: none; |
||||
} |
||||
</style> |
@ -0,0 +1,129 @@ |
||||
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', |
||||
queryDepartSync = '/sys/sysDepart/queryDepartSync', |
||||
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', |
||||
} |
||||
|
||||
/** |
||||
* 获取部门树列表 |
||||
*/ |
||||
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params }); |
||||
|
||||
/** |
||||
* 获取部门树列表 |
||||
*/ |
||||
export const queryDepartSync = (params?) => defHttp.get({ url: Api.queryDepartSync, 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 }); |
@ -0,0 +1,83 @@ |
||||
import { FormSchema } from '/@/components/Form'; |
||||
|
||||
// 部门基础表单
|
||||
export function useBasicFormSchema() { |
||||
const basicFormSchema: FormSchema[] = [ |
||||
{ |
||||
field: 'departName', |
||||
label: '部门名称', |
||||
component: 'Input', |
||||
componentProps: { |
||||
placeholder: '部门名称', |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'parentId', |
||||
label: '上级部门', |
||||
component: 'TreeSelect', |
||||
componentProps: { |
||||
treeData: [], |
||||
placeholder: '无', |
||||
dropdownStyle: { maxHeight: '200px', overflow: 'auto' }, |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'orgCode', |
||||
label: '部门编码', |
||||
component: 'Input', |
||||
componentProps: { |
||||
placeholder: '请输入部门编码', |
||||
}, |
||||
}, |
||||
/* { |
||||
field: 'orgCategory', |
||||
label: '部门类型', |
||||
component: 'RadioButtonGroup', |
||||
componentProps: { options: [] }, |
||||
},*/ |
||||
{ |
||||
field: 'mobile', |
||||
label: '电话', |
||||
component: 'Input', |
||||
componentProps: { |
||||
placeholder: '请输入电话', |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'fax', |
||||
label: '传真', |
||||
component: 'Input', |
||||
componentProps: { |
||||
placeholder: '请输入传真', |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'address', |
||||
label: '地址', |
||||
component: 'Input', |
||||
componentProps: { |
||||
placeholder: '请输入地址', |
||||
}, |
||||
}, |
||||
{ |
||||
field: 'memo', |
||||
label: '备注', |
||||
component: 'InputTextArea', |
||||
componentProps: { |
||||
placeholder: '请输入备注', |
||||
}, |
||||
}, |
||||
]; |
||||
return { basicFormSchema }; |
||||
} |
||||
|
||||
// 部门类型选项
|
||||
export const orgCategoryOptions = { |
||||
// 一级部门
|
||||
root: [{ value: '1', label: '公司' }], |
||||
// 子级部门
|
||||
child: [ |
||||
{ value: '2', label: '部门' }, |
||||
{ value: '3', label: '岗位' }, |
||||
], |
||||
}; |
@ -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; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
<template> |
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10"> |
||||
<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 :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" /> |
||||
</div> |
||||
</a-tab-pane> |
||||
</a-tabs> |
||||
</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'; |
||||
|
||||
const { prefixCls } = useDesign('depart-manage'); |
||||
provide('prefixCls', prefixCls); |
||||
|
||||
// 给子组件定义一个ref变量 |
||||
const leftTree = ref(); |
||||
|
||||
// 当前选中的部门信息 |
||||
const departData = ref({}); |
||||
const rootTreeData = ref<any[]>([]); |
||||
|
||||
// 左侧树选择后触发 |
||||
function onTreeSelect(data) { |
||||
console.log('onTreeSelect: ', data); |
||||
departData.value = data; |
||||
} |
||||
|
||||
// 左侧树rootTreeData触发 |
||||
function onRootTreeData(data) { |
||||
rootTreeData.value = data; |
||||
} |
||||
|
||||
function onSuccess() { |
||||
leftTree.value.loadRootTreeData(); |
||||
} |
||||
</script> |
||||
|
||||
<style lang="less"> |
||||
@import './index.less'; |
||||
</style> |
Loading…
Reference in new issue