前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题

This commit is contained in:
JEECG
2024-06-23 10:39:52 +08:00
parent bb918b742e
commit 0325e34dcb
1439 changed files with 171106 additions and 0 deletions

View File

@ -0,0 +1,40 @@
<template>
<Description @register="registerDesc" />
</template>
<script lang="ts" setup>
import { ref, inject, onMounted, watch } from 'vue';
import { queryIdTree } from '../depart.user.api';
import { useBaseInfoForm } from '../depart.user.data';
import { Description, useDescription } from '/@/components/Description/index';
const prefixCls = inject('prefixCls');
const props = defineProps({
data: { require: true, type: Object },
});
const treeData = ref([]);
const { descItems } = useBaseInfoForm(treeData);
const [registerDesc, { setDescProps }] = useDescription({
data: props.data,
schema: descItems,
column: 1,
labelStyle: {
width: '180px',
},
});
function setData(data) {
setDescProps({ data });
}
onMounted(() => {
watch(
() => props.data,
() => setData(props.data),
{ immediate: true }
);
});
// 动态查询 parentId 组件的 treeData
queryIdTree().then((data) => (treeData.value = data));
</script>

View File

@ -0,0 +1,161 @@
<template>
<BasicDrawer
title="部门角色权限配置"
:width="650"
:loading="loading"
showFooter
okText="保存并关闭"
@ok="onSubmit(true)"
@close="onClose"
@register="registerDrawer"
>
<div>
<a-spin :spinning="loading">
<template v-if="treeData.length > 0">
<BasicTree
title="所拥有的部门权限"
toolbar
checkable
:treeData="treeData"
:checkedKeys="checkedKeys"
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
:checkStrictly="checkStrictly"
:clickRowToExpand="false"
@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="无可配置部门权限" />
</a-spin>
</div>
<template #centerFooter>
<a-button type="primary" :loading="loading" ghost @click="onSubmit(false)">仅保存</a-button>
</template>
</BasicDrawer>
<DepartRoleDataRuleDrawer @register="registerDataRuleDrawer" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicTree } from '/@/components/Tree/index';
import { BasicDrawer, useDrawer, useDrawerInner } from '/@/components/Drawer';
import { useMessage } from '/@/hooks/web/useMessage';
import DepartRoleDataRuleDrawer from './DepartRoleDataRuleDrawer.vue';
import { queryTreeListForDeptRole, queryDeptRolePermission, saveDeptRolePermission } from '../depart.user.api';
import { translateTitle } from "@/utils/common/compUtils";
defineEmits(['register']);
const { createMessage } = useMessage();
const loading = ref(false);
const departId = ref('');
const roleId = ref('');
const treeData = ref<Array<any>>([]);
const checkedKeys = ref<Array<any>>([]);
const lastCheckedKeys = ref<Array<any>>([]);
const expandedKeys = ref<Array<any>>([]);
const selectedKeys = ref<Array<any>>([]);
const allTreeKeys = ref<Array<any>>([]);
const checkStrictly = ref(true);
// 注册抽屉组件
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
roleId.value = data.record.id;
departId.value = data.record.departId;
loadData();
});
// 注册数据规则授权弹窗抽屉
const [registerDataRuleDrawer, dataRuleDrawer] = useDrawer();
async function loadData() {
try {
loading.value = true;
// 用户角色授权功能,查询菜单权限树
const { ids, treeList } = await queryTreeListForDeptRole({ departId: departId.value });
if (ids.length > 0) {
allTreeKeys.value = ids;
expandedKeys.value = ids;
//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('') 多语言---
// 查询角色授权
checkedKeys.value = await queryDeptRolePermission({ roleId: roleId.value });
lastCheckedKeys.value = [checkedKeys.value];
} else {
reset();
}
} finally {
loading.value = false;
}
}
// 重置页面
function reset() {
treeData.value = [];
expandedKeys.value = [];
checkedKeys.value = [];
lastCheckedKeys.value = [];
loading.value = false;
}
// tree勾选复选框事件
function onCheck(event) {
if (checkStrictly.value) {
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, { roleId, departId, functionId });
}
selectedKeys.value = [];
}
function doClose() {
reset();
closeDrawer();
}
function onClose() {
reset();
}
async function onSubmit(exit) {
try {
loading.value = true;
let params = {
roleId: roleId.value,
permissionIds: checkedKeys.value.join(','),
lastpermissionIds: lastCheckedKeys.value.join(','),
};
await saveDeptRolePermission(params);
if (exit) {
doClose();
}
} finally {
loading.value = false;
if (!exit) {
loadData();
}
}
}
</script>

View File

@ -0,0 +1,82 @@
<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-tab-pane tab="按钮权限" key="2">敬请期待!!!</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 { queryDepartRoleDataRule, saveDepartRoleDataRule } from '../depart.user.api';
defineEmits(['register']);
const loading = ref<boolean>(false);
const departId = ref('');
const functionId = ref('');
const roleId = ref('');
const dataRuleList = ref<Array<any>>([]);
const dataRuleChecked = ref<Array<any>>([]);
// 注册抽屉组件
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
roleId.value = unref(data.roleId);
departId.value = unref(data.departId);
functionId.value = unref(data.functionId);
loadData();
});
async function loadData() {
try {
loading.value = true;
const { datarule, drChecked } = await queryDepartRoleDataRule(functionId, departId, roleId);
dataRuleList.value = datarule;
if (drChecked) {
dataRuleChecked.value = drChecked.split(',');
}
} finally {
loading.value = false;
}
}
function saveDataRuleForRole() {
let params = {
permissionId: functionId.value,
roleId: roleId.value,
dataRuleIds: dataRuleChecked.value.join(','),
};
saveDepartRoleDataRule(params);
}
function onClose() {
doReset();
}
function doReset() {
functionId.value = '';
roleId.value = '';
dataRuleList.value = [];
dataRuleChecked.value = [];
}
</script>

View File

@ -0,0 +1,203 @@
<template>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="addDepartRole">添加部门角色</a-button>
<template v-if="selectedRowKeys.length > 0">
<a-divider type="vertical" />
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="onDeleteDepartRoleBatch">
<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>
</template>
<!-- 插槽行内操作按钮 -->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<!-- 添加部门弹窗 -->
<DepartRoleModal :departId="departId" @register="registerFormModal" @success="onFormModalSuccess" />
<DepartRoleAuthDrawer @register="registerAuthDrawer" />
</template>
<script lang="ts" setup>
import { inject, ref, unref, watch, computed, onMounted } 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 DepartRoleModal from './DepartRoleModal.vue';
import DepartRoleAuthDrawer from './DepartRoleAuthDrawer.vue';
import { deleteBatchDepartRole, departRoleList } from '../depart.user.api';
import { departRoleColumns, departRoleSearchFormSchema } from '../depart.user.data';
import { ColEx } from '/@/components/Form/src/types';
const prefixCls = inject('prefixCls');
const props = defineProps({
data: { require: true, type: Object },
});
defineEmits(['register']);
// 当前选中的部门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: departRoleList,
columns: departRoleColumns,
canResize: false,
formConfig: {
labelWidth: 100,
schemas: departRoleSearchFormSchema,
baseColProps: adaptiveColProps,
labelAlign: 'left',
labelCol: {
xs: 24,
sm: 24,
md: 24,
lg: 9,
xl: 7,
xxl: 6,
},
wrapperCol: {},
// 操作按钮配置
actionColOptions: {
...adaptiveColProps,
style: { textAlign: 'left' },
},
},
// 【issues/1064】列设置的 cacheKey
tableSetting: { cacheKey: 'depart_user_departInfo' },
// 请求之前对参数做处理
beforeFetch(params) {
params.deptId = departId.value;
},
// update-begin--author:liaozhiyang---date:20240517---for【TV360X-53】未选择部门的情况下部门角色全查出来了
immediate: !!departId.value,
// update-end--author:liaozhiyang---date:20240517---for【TV360X-53】未选择部门的情况下部门角色全查出来了
},
});
// 注册 ListTable
const [registerTable, { reload, setProps, setLoading, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
// 注册Form弹窗
const [registerFormModal, formModal] = useModal();
// 注册授权弹窗抽屉
const [registerAuthDrawer, authDrawer] = useDrawer();
// 监听 data 更改,重新加载数据
watch(
() => props.data,
() => reload()
);
onMounted(() => {
// update-begin--author:liaozhiyang---date:20240517---for【TV360X-53】未选择部门的情况下部门角色全查出来了
// reload();
// update-end--author:liaozhiyang---date:20240517---for【TV360X-53】未选择部门的情况下部门角色全查出来了
});
// 清空选择的行
function clearSelection() {
selectedRowKeys.value = [];
}
// 添加部门角色
function addDepartRole() {
formModal.openModal(true, {
isUpdate: false,
record: {},
});
}
// 编辑部门角色
function editDepartRole(record) {
formModal.openModal(true, {
isUpdate: true,
record: record,
});
}
// 授权部门角色
function permissionDepartRole(record) {
authDrawer.openDrawer(true, { record });
}
// 批量删除部门角色
async function deleteDepartRole(idList, confirm) {
if (!departId.value) {
createMessage.warning('请先选择一个部门');
} else {
setLoading(true);
let ids = unref(idList).join(',');
try {
await deleteBatchDepartRole({ ids }, confirm);
return reload();
} finally {
setLoading(false);
}
}
return Promise.reject();
}
// 批量删除部门角色事件
async function onDeleteDepartRoleBatch() {
try {
await deleteDepartRole(selectedRowKeys, true);
// 批量删除成功后清空选择
clearSelection();
} catch (e) {}
}
// 表单弹窗成功后的回调
function onFormModalSuccess({ isUpdate, values }) {
isUpdate ? updateTableDataRecord(values.id, values) : reload();
}
/**
* 操作栏
*/
function getTableAction(record): ActionItem[] {
return [{ label: '编辑', onClick: editDepartRole.bind(null, record) }];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record): ActionItem[] {
return [
{ label: '授权', onClick: permissionDepartRole.bind(null, record) },
{
label: '删除',
color: 'error',
popConfirm: {
title: '确认要删除吗',
confirm: deleteDepartRole.bind(null, [record.id], false),
},
},
];
}
</script>

View File

@ -0,0 +1,63 @@
<template>
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, inject, ref, unref } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
// noinspection ES6UnusedImports
import { BasicModal, useModalInner } from '/@/components/Modal';
import { saveOrUpdateDepartRole } from '../depart.user.api';
import { departRoleModalFormSchema } from '../depart.user.data';
const emit = defineEmits(['success', 'register']);
const props = defineProps({
// 当前部门ID
departId: { require: true, type: String },
});
const prefixCls = inject('prefixCls');
// 当前是否是更新模式
const isUpdate = ref<boolean>(true);
// 当前的弹窗数据
const model = ref<object>({});
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
//注册表单
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
schemas: departRoleModalFormSchema,
showActionButtonGroup: false,
});
// 注册弹窗
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
isUpdate.value = unref(data?.isUpdate);
// 无论新增还是编辑,都可以设置表单值
let record = unref(data?.record);
if (typeof record === 'object') {
model.value = record;
await setFieldsValue({ ...record });
}
});
//提交事件
async function handleOk() {
try {
setModalProps({ confirmLoading: true });
let values = await validate();
values.departId = unref(props.departId);
//提交表单
await saveOrUpdateDepartRole(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success', { isUpdate: unref(isUpdate), values });
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>

View File

@ -0,0 +1,91 @@
<template>
<BasicDrawer title="部门角色分配" :width="365" @close="onClose" @register="registerDrawer">
<a-spin :spinning="loading">
<template v-if="desformList.length > 0">
<a-checkbox-group v-model:value="designNameValue">
<a-row>
<a-col :span="24" v-for="item of desformList">
<a-checkbox :value="item.id">{{ item.roleName }}</a-checkbox>
</a-col>
</a-row>
</a-checkbox-group>
<div style="width: 100%; margin-top: 15px">
<a-button type="primary" :loading="loading" :size="'small'" preIcon="ant-design:save-filled" @click="onSubmit">
<span>点击保存</span>
</a-button>
</div>
</template>
<a-empty v-else description="无配置信息" />
</a-spin>
</BasicDrawer>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { queryDepartRoleByUserId, queryDepartRoleUserList, saveDepartRoleUser } from '../depart.user.api';
defineEmits(['register']);
const loading = ref<boolean>(false);
const userId = ref('');
const departId = ref('');
const oldRoleId = ref('');
const desformList = ref<Array<any>>([]);
const designNameValue = ref<Array<any>>([]);
// 注册抽屉组件
const [registerDrawer, { closeDrawer }] = useDrawerInner((data) => {
userId.value = unref(data.userId);
departId.value = unref(data.departId);
loadData();
});
async function loadData() {
try {
loading.value = true;
const params = {
departId: departId.value,
userId: userId.value,
};
// 查询 DepartRole
const [$desformList, $departRoleList] = await Promise.all([queryDepartRoleUserList(params), queryDepartRoleByUserId(params)]);
desformList.value = $desformList;
designNameValue.value = $departRoleList.map((item) => item.droleId);
oldRoleId.value = designNameValue.value.join(',');
} finally {
loading.value = false;
}
}
async function onSubmit() {
try {
loading.value = true;
await saveDepartRoleUser({
userId: userId.value,
newRoleId: designNameValue.value.join(','),
oldRoleId: oldRoleId.value,
});
doClose();
} finally {
loading.value = false;
}
}
function onClose() {
doReset();
}
function doClose() {
doReset();
closeDrawer();
}
function doReset() {
userId.value = '';
departId.value = '';
oldRoleId.value = '';
desformList.value = [];
designNameValue.value = [];
}
</script>

View File

@ -0,0 +1,149 @@
<template>
<div class="bg-white m-4 mr-0 overflow-hidden">
<a-spin :spinning="loading">
<template v-if="userIdentity === '2'">
<!--组织机构树-->
<BasicTree
v-if="!treeReloading"
title="部门列表"
toolbar
search
showLine
:checkStrictly="true"
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
@select="onSelect"
@expand="onExpand"
@search="onSearch"
/>
</template>
<a-empty v-else description="普通员工无此权限" />
</a-spin>
</div>
</template>
<script lang="ts" setup>
import { inject, nextTick, ref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { BasicTree } from '/@/components/Tree';
import { queryMyDepartTreeList, searchByKeywords } from '../depart.user.api';
const prefixCls = inject('prefixCls');
const emit = defineEmits(['select']);
const { createMessage } = useMessage();
let loading = ref<boolean>(false);
// 部门树列表数据
let treeData = ref<any[]>([]);
// 当前展开的项
let expandedKeys = ref<any[]>([]);
// 当前选中的项
let selectedKeys = ref<any[]>([]);
// 是否自动展开父级
let autoExpandParent = ref<boolean>(true);
// 用户身份
let userIdentity = ref<string>('2');
// 树组件重新加载
let treeReloading = ref<boolean>(false);
// 加载部门信息
function loadDepartTreeData() {
loading.value = true;
treeData.value = [];
queryMyDepartTreeList()
.then((res) => {
if (res.success) {
if (Array.isArray(res.result)) {
treeData.value = res.result;
userIdentity.value = res.message;
autoExpandParentNode();
}
} else {
createMessage.warning(res.message);
}
})
.finally(() => (loading.value = false));
}
loadDepartTreeData();
// 自动展开父节点,只展开一级
function autoExpandParentNode() {
let keys: Array<any> = [];
treeData.value.forEach((item, index) => {
if (item.children && item.children.length > 0) {
keys.push(item.key);
}
if (index === 0) {
// 默认选中第一个
setSelectedKey(item.id, item);
}
});
if (keys.length > 0) {
reloadTree();
expandedKeys.value = keys;
}
}
// 重新加载树组件,防止无法默认展开数据
async function reloadTree() {
await nextTick();
treeReloading.value = true;
await nextTick();
treeReloading.value = false;
}
/**
* 设置当前选中的行
*/
function setSelectedKey(key: string, data?: object) {
selectedKeys.value = [key];
if (data) {
emit('select', data);
}
}
// 搜索事件
function onSearch(value: string) {
if (value) {
loading.value = true;
searchByKeywords({ keyWord: value, myDeptSearch: '1' })
.then((result) => {
if (Array.isArray(result)) {
treeData.value = result;
} else {
createMessage.warning('未查询到部门信息');
treeData.value = [];
}
})
.finally(() => (loading.value = false));
} else {
loadDepartTreeData();
}
}
// 树选择事件
function onSelect(selKeys, event) {
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
setSelectedKey(selKeys[0], event.selectedNodes[0]);
} else {
// 这样可以防止用户取消选择
setSelectedKey(selectedKeys.value[0]);
}
}
// 树展开事件
function onExpand(keys) {
expandedKeys.value = keys;
autoExpandParent.value = false;
}
</script>
<style lang="less" scoped>
/*升级antd3后查询框与树贴的太近样式优化*/
:deep(.jeecg-tree-header) {
margin-bottom: 6px;
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser">添加已有用户</a-button>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser">新建用户</a-button>
<template v-if="selectedRowKeys.length > 0">
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="onUnlinkDepartUserBatch">
<icon icon="bx:bx-unlink" />
<span>取消关联</span>
</a-menu-item>
</a-menu>
</template>
<a-button>
<span>批量操作 </span>
<icon icon="akar-icons:chevron-down" />
</a-button>
</a-dropdown>
</template>
</template>
<!-- 插槽行内操作按钮 -->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
</BasicTable>
<UserDrawer @register="registerDrawer" @success="onUserDrawerSuccess" />
<DepartRoleUserAuthDrawer @register="registerUserAuthDrawer" />
<UserSelectModal rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
</template>
<script lang="ts" setup>
import { computed, inject, ref, unref, 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 DepartRoleUserAuthDrawer from './DepartRoleUserAuthDrawer.vue';
import { departUserList, linkDepartUserBatch, unlinkDepartUserBatch } from '../depart.user.api';
import { userInfoColumns, userInfoSearchFormSchema } from '../depart.user.data';
import { ColEx } from '/@/components/Form/src/types';
const prefixCls = inject('prefixCls');
const props = defineProps({
data: { require: true, type: Object },
});
// 当前选中的部门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: departUserList,
columns: userInfoColumns,
canResize: false,
formConfig: {
schemas: userInfoSearchFormSchema,
baseColProps: adaptiveColProps,
labelAlign: 'left',
labelCol: {
xs: 24,
sm: 24,
md: 24,
lg: 9,
xl: 7,
xxl: 5,
},
wrapperCol: {},
// 操作按钮配置
actionColOptions: {
...adaptiveColProps,
style: { textAlign: 'left' },
},
},
// 【issues/1064】列设置的 cacheKey
tableSetting: { cacheKey: 'depart_user_userInfo' },
// 请求之前对参数做处理
beforeFetch(params) {
params.depId = departId.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 = [];
}
// 查看部门角色
function showDepartRole(record) {
userAuthDrawer.openDrawer(true, { userId: record.id, departId });
}
// 创建用户
function createUser() {
if (!departId.value) {
createMessage.warning('请先选择一个部门');
} else {
openDrawer(true, {
isUpdate: false,
departDisabled: true,
// 初始化负责部门
nextDepartOptions: { value: props.data?.key, label: props.data?.title },
record: {
activitiSync: 1,
userIdentity: 1,
selecteddeparts: departId.value,
},
});
}
}
// 查看用户详情
function showUserDetail(record) {
openDrawer(true, {
record,
isUpdate: true,
departDisabled: true,
showFooter: false,
});
}
// 编辑用户信息
function editUserInfo(record) {
openDrawer(true, { isUpdate: true, record, departDisabled: true });
}
// 选择添加已有用户
function selectAddUser() {
selUserModal.openModal();
}
// 批量取消关联部门和用户之间的关系
async function unlinkDepartUser(idList, confirm) {
if (!departId.value) {
createMessage.warning('请先选择一个部门');
} else {
setLoading(true);
let userIds = unref(idList).join(',');
try {
await unlinkDepartUserBatch({ depId: departId.value, userIds }, confirm);
return reload();
} finally {
setLoading(false);
}
}
return Promise.reject();
}
// 批量取消关联事件
async function onUnlinkDepartUserBatch() {
try {
await unlinkDepartUser(selectedRowKeys, true);
// 批量删除成功后清空选择
clearSelection();
} catch (e) {}
}
// 选择用户成功
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) }];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record): ActionItem[] {
return [
{ label: '部门角色', onClick: showDepartRole.bind(null, record) },
{ label: '用户详情', onClick: showUserDetail.bind(null, record) },
{
label: '取消关联',
color: 'error',
popConfirm: {
title: '确认取消关联吗',
confirm: unlinkDepartUser.bind(null, [record.id], false),
},
},
];
}
</script>

View File

@ -0,0 +1,159 @@
import { unref } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
treeList = '/sys/sysDepart/queryMyDeptTreeList',
queryIdTree = '/sys/sysDepart/queryIdTree',
searchBy = '/sys/sysDepart/searchBy',
}
// 部门用户API
enum DepartUserApi {
list = '/sys/user/departUserList',
link = '/sys/user/editSysDepartWithUser',
unlink = '/sys/user/deleteUserInDepartBatch',
}
// 部门角色API
enum DepartRoleApi {
list = '/sys/sysDepartRole/list',
deleteBatch = '/sys/sysDepartRole/deleteBatch',
save = '/sys/sysDepartRole/add',
edit = '/sys/sysDepartRole/edit',
queryTreeListForDeptRole = '/sys/sysDepartPermission/queryTreeListForDeptRole',
queryDeptRolePermission = '/sys/sysDepartPermission/queryDeptRolePermission',
saveDeptRolePermission = '/sys/sysDepartPermission/saveDeptRolePermission',
dataRule = '/sys/sysDepartRole/datarule',
getDeptRoleList = '/sys/sysDepartRole/getDeptRoleList',
getDeptRoleByUserId = '/sys/sysDepartRole/getDeptRoleByUserId',
saveDeptRoleUser = '/sys/sysDepartRole/deptRoleUserAdd',
}
/**
* 获取部门树列表
*/
export const queryMyDepartTreeList = (params?) => defHttp.get({ url: Api.treeList, params }, { isTransformResponse: false });
/**
* 查询数据,以树结构形式加载所有部门的名称
*/
export const queryIdTree = (params?) => defHttp.get({ url: Api.queryIdTree, params });
/**
* 根据关键字搜索部门
*/
export const searchByKeywords = (params) => defHttp.get({ url: Api.searchBy, params });
/**
* 查询部门下的用户信息
*/
export const departUserList = (params) => defHttp.get({ url: DepartUserApi.list, params });
/**
* 批量添加部门和用户的关联关系
*
* @param departId 部门ID
* @param userIdList 用户ID列表
*/
export const linkDepartUserBatch = (departId: string, userIdList: string[]) =>
defHttp.post({ url: DepartUserApi.link, params: { depId: departId, userIdList } });
/**
* 批量取消部门和用户的关联关系
*/
export const unlinkDepartUserBatch = (params, confirm = false) => {
return new Promise((resolve, reject) => {
const doDelete = () => {
resolve(defHttp.delete({ url: DepartUserApi.unlink, params }, { joinParamsToUrl: true }));
};
if (confirm) {
createConfirm({
iconType: 'warning',
title: '取消关联',
content: '确定要取消关联吗?',
onOk: () => doDelete(),
onCancel: () => reject(),
});
} else {
doDelete();
}
});
};
/**
* 查询部门角色信息
*/
export const departRoleList = (params) => defHttp.get({ url: DepartRoleApi.list, params });
/**
* 保存或者更新部门角色
*/
export const saveOrUpdateDepartRole = (params, isUpdate) => {
if (isUpdate) {
return defHttp.put({ url: DepartRoleApi.edit, params });
} else {
return defHttp.post({ url: DepartRoleApi.save, params });
}
};
/**
* 批量删除部门角色
*/
export const deleteBatchDepartRole = (params, confirm = false) => {
return new Promise((resolve, reject) => {
const doDelete = () => {
resolve(defHttp.delete({ url: DepartRoleApi.deleteBatch, params }, { joinParamsToUrl: true }));
};
if (confirm) {
createConfirm({
iconType: 'warning',
title: '删除',
content: '确定要删除吗?',
onOk: () => doDelete(),
onCancel: () => reject(),
});
} else {
doDelete();
}
});
};
/**
* 用户角色授权功能,查询菜单权限树
*/
export const queryTreeListForDeptRole = (params) => defHttp.get({ url: DepartRoleApi.queryTreeListForDeptRole, params });
/**
* 查询角色授权
*/
export const queryDeptRolePermission = (params) => defHttp.get({ url: DepartRoleApi.queryDeptRolePermission, params });
/**
* 保存角色授权
*/
export const saveDeptRolePermission = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRolePermission, params });
/**
* 查询部门角色数据权限列表
*/
export const queryDepartRoleDataRule = (functionId, departId, roleId, params?) => {
let url = `${DepartRoleApi.dataRule}/${unref(functionId)}/${unref(departId)}/${unref(roleId)}`;
return defHttp.get({ url, params });
};
/**
* 保存部门角色数据权限
*/
export const saveDepartRoleDataRule = (params) => defHttp.post({ url: DepartRoleApi.dataRule, params });
/**
* 查询部门角色用户授权
*/
export const queryDepartRoleUserList = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleList, params });
/**
* 根据 userId 查询部门角色用户授权
*/
export const queryDepartRoleByUserId = (params) => defHttp.get({ url: DepartRoleApi.getDeptRoleByUserId, params });
/**
* 保存部门角色用户授权
*/
export const saveDepartRoleUser = (params) => defHttp.post({ url: DepartRoleApi.saveDeptRoleUser, params });

View File

@ -0,0 +1,195 @@
import { Ref } from 'vue';
import { duplicateCheckDelay } from '/@/views/system/user/user.api';
import { BasicColumn, FormSchema } from '/@/components/Table';
import { DescItem } from '/@/components/Description';
import { findTree } from '/@/utils/common/compUtils';
// 用户信息 columns
export const userInfoColumns: BasicColumn[] = [
{
title: '用户账号',
dataIndex: 'username',
width: 150,
},
{
title: '用户名称',
dataIndex: 'realname',
width: 180,
},
{
title: '部门',
dataIndex: 'orgCode',
width: 200,
},
{
title: '性别',
dataIndex: 'sex_dictText',
width: 80,
},
{
title: '电话',
dataIndex: 'phone',
width: 120,
},
];
// 用户信息查询条件表单
export const userInfoSearchFormSchema: FormSchema[] = [
{
field: 'username',
label: '用户账号',
component: 'Input',
},
];
// 部门角色 columns
export const departRoleColumns: BasicColumn[] = [
{
title: '部门角色名称',
dataIndex: 'roleName',
width: 100,
},
{
title: '部门角色编码',
dataIndex: 'roleCode',
width: 100,
},
{
title: '部门',
dataIndex: 'departId_dictText',
width: 100,
},
{
title: '备注',
dataIndex: 'description',
width: 100,
},
];
// 部门角色查询条件表单
export const departRoleSearchFormSchema: FormSchema[] = [
{
field: 'roleName',
label: '部门角色名称',
component: 'Input',
},
];
// 部门角色弹窗form表单
export const departRoleModalFormSchema: FormSchema[] = [
{
label: 'id',
field: 'id',
component: 'Input',
show: false,
},
{
field: 'roleName',
label: '部门角色名称',
component: 'Input',
rules: [
{ required: true, message: '部门角色名称不能为空!' },
{ min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' },
],
},
{
field: 'roleCode',
label: '部门角色编码',
component: 'Input',
dynamicDisabled: ({ values }) => {
return !!values.id;
},
dynamicRules: ({ model }) => {
return [
{ required: true, message: '部门角色编码不能为空!' },
{ min: 0, max: 64, message: '长度不能超过 64 个字符', trigger: 'blur' },
{
validator: (_, value) => {
if (/[\u4E00-\u9FA5]/g.test(value)) {
return Promise.reject('部门角色编码不可输入汉字!');
}
return new Promise((resolve, reject) => {
let params = {
tableName: 'sys_depart_role',
fieldName: 'role_code',
fieldVal: value,
dataId: model.id,
};
duplicateCheckDelay(params)
.then((res) => {
res.success ? resolve() : reject(res.message || '校验失败');
})
.catch((err) => {
reject(err.message || '验证失败');
});
});
},
},
];
},
},
{
field: 'description',
label: '描述',
component: 'Input',
rules: [{ min: 0, max: 126, message: '长度不能超过 126 个字符', trigger: 'blur' }],
},
];
// 基本信息form
export function useBaseInfoForm(treeData: Ref<any[]>) {
const descItems: DescItem[] = [
{
field: 'departName',
label: '机构名称',
},
{
field: 'parentId',
label: '上级部门',
render(val) {
if (val) {
let data = findTree(treeData.value, (item) => item.key == val);
return data?.title ?? val;
}
return val;
},
},
{
field: 'orgCode',
label: '机构编码',
},
{
field: 'orgCategory',
label: '机构类型',
render(val) {
if (val === '1') {
return '公司';
} else if (val === '2') {
return '部门';
} else if (val === '3') {
return '岗位';
}
return val;
},
},
{
field: 'departOrder',
label: '排序',
},
{
field: 'mobile',
label: '手机号',
},
{
field: 'address',
label: '地址',
},
{
field: 'memo',
label: '备注',
},
];
return { descItems };
}

View File

@ -0,0 +1,48 @@
@prefix-cls: ~'@{namespace}-depart-user';
.@{prefix-cls} {
&--tree-search {
width: 100%;
margin: 10px 0 20px;
}
&--base-info-form {
@media (min-width: 576px) {
.no-border {
border: 0;
box-shadow: none;
}
.ant-select.ant-select-disabled {
.ant-select-selector {
border: 0;
color: black;
background-color: transparent;
}
.ant-select-selector,
.ant-select-selection-item {
cursor: text !important;
user-select: initial !important;
}
.ant-select-selection-search,
.ant-select-arrow {
display: none;
}
}
}
}
}
// 夜间模式样式兼容
[data-theme='dark'] .@{prefix-cls} {
&--base-info-form {
.ant-select.ant-select-disabled {
.ant-select-selector {
color: #c9d1d9;
background-color: transparent;
}
}
}
}

View File

@ -0,0 +1,49 @@
<template>
<a-row :class="['p-4', `${prefixCls}--box`]" :gutter="10">
<a-col :xl="6" :lg="8" :md="10" :sm="24" style="flex: 1">
<a-card :bordered="false" style="height: 100%">
<DepartTree @select="onTreeSelect" />
</a-card>
</a-col>
<a-col :xl="18" :lg="16" :md="14" :sm="24" style="flex: 1">
<a-card :bordered="false" style="height: 100%">
<a-tabs defaultActiveKey="user-info">
<a-tab-pane tab="基本信息" key="base-info" forceRender>
<DepartBaseInfoTab :data="departData" />
</a-tab-pane>
<a-tab-pane tab="用户信息" key="user-info">
<DepartUserInfoTab :data="departData" />
</a-tab-pane>
<a-tab-pane tab="部门角色" key="role-info">
<DepartRoleInfoTab :data="departData" />
</a-tab-pane>
</a-tabs>
</a-card>
</a-col>
</a-row>
</template>
<script lang="ts" setup name="system-depart-user">
import { provide, ref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import DepartTree from './components/DepartTree.vue';
import DepartBaseInfoTab from './components/DepartBaseInfoTab.vue';
import DepartUserInfoTab from './components/DepartUserInfoTab.vue';
import DepartRoleInfoTab from './components/DepartRoleInfoTab.vue';
const { prefixCls } = useDesign('depart-user');
provide('prefixCls', prefixCls);
// 当前选中的部门信息
let departData = ref({});
// 左侧树选择后触发
function onTreeSelect(data) {
departData.value = data;
}
</script>
<style lang="less">
@import './index.less';
</style>