mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2026-04-06 21:54:26 +08:00
v3.9.0【优化】typescript版本;【优化】App端消息;【优化】弹出层z-index;
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
* 当前所选部门的子部门 人员管理右上半部分
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="child-dept-container">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item v-for="(item, index) in props.breadcrumb" :key="index">
|
||||
{{ item }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
|
||||
<a-list class="department-list" :data-source="props.selectedDepartmentChildren">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<div class="department-item" @click="selectTree(item.departmentId)">
|
||||
{{ item.name }}
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import emitter from '../../department-mitt';
|
||||
|
||||
const props = defineProps({
|
||||
breadcrumb: Array,
|
||||
selectedDepartmentChildren: Array,
|
||||
});
|
||||
|
||||
function selectTree(id) {
|
||||
emitter.emit('selectTree', id);
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-list-item) {
|
||||
padding: 6px 0px;
|
||||
}
|
||||
.child-dept-container {
|
||||
.department-list-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.department-list {
|
||||
height: 170px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.department-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,245 @@
|
||||
<!--
|
||||
* 部门树形结构
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="tree-container">
|
||||
<a-row class="smart-margin-bottom10">
|
||||
<a-input v-model:value.trim="keywords" placeholder="请输入部门名称" />
|
||||
</a-row>
|
||||
<a-tree
|
||||
v-if="!_.isEmpty(departmentTreeData)"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
class="tree"
|
||||
:treeData="departmentTreeData"
|
||||
:fieldNames="{ title: 'name', key: 'departmentId', value: 'departmentId' }"
|
||||
style="width: 100%; overflow-x: auto"
|
||||
:style="[!height ? '' : { height: `${height}px`, overflowY: 'auto' }]"
|
||||
:checkable="props.checkable"
|
||||
:checkStrictly="props.checkStrictly"
|
||||
:selectable="!props.checkable"
|
||||
:defaultExpandAll="true"
|
||||
@select="treeSelectChange"
|
||||
>
|
||||
<template #title="item">
|
||||
<div>{{ item.name }}</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
<div class="no-data" v-else>暂无结果</div>
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { departmentApi } from '/@/api/system/department-api';
|
||||
import departmentEmitter from '../../department-mitt';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
|
||||
const DEPARTMENT_PARENT_ID = 0;
|
||||
|
||||
// ----------------------- 组件参数 ---------------------
|
||||
|
||||
const props = defineProps({
|
||||
// 是否可以选中
|
||||
checkable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 父子节点选中状态不再关联
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 树高度 超出出滚动条
|
||||
height: Number,
|
||||
// 显示菜单
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------- 部门树的展示 ---------------------
|
||||
const topDepartmentId = ref();
|
||||
// 所有部门列表
|
||||
const departmentList = ref([]);
|
||||
// 部门树形数据
|
||||
const departmentTreeData = ref([]);
|
||||
// 存放部门id和部门,用于查找
|
||||
const idInfoMap = ref(new Map());
|
||||
|
||||
onMounted(() => {
|
||||
queryDepartmentTree();
|
||||
});
|
||||
|
||||
// 刷新
|
||||
async function refresh() {
|
||||
await queryDepartmentTree();
|
||||
if (currentSelectedDepartmentId.value) {
|
||||
selectTree(currentSelectedDepartmentId.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询部门列表并构建 部门树
|
||||
async function queryDepartmentTree() {
|
||||
let res = await departmentApi.queryAllDepartment();
|
||||
let data = res.data;
|
||||
departmentList.value = data;
|
||||
departmentTreeData.value = buildDepartmentTree(data, DEPARTMENT_PARENT_ID);
|
||||
|
||||
data.forEach((e) => {
|
||||
idInfoMap.value.set(e.departmentId, e);
|
||||
});
|
||||
|
||||
// 默认显示 最顶级ID为列表中返回的第一条数据的ID
|
||||
if (!_.isEmpty(departmentTreeData.value) && departmentTreeData.value.length > 0) {
|
||||
topDepartmentId.value = departmentTreeData.value[0].departmentId;
|
||||
}
|
||||
|
||||
selectTree(departmentTreeData.value[0].departmentId);
|
||||
}
|
||||
|
||||
// 构建部门树
|
||||
function buildDepartmentTree(data, parentId) {
|
||||
let children = data.filter((e) => e.parentId === parentId) || [];
|
||||
children.forEach((e) => {
|
||||
e.children = buildDepartmentTree(data, e.departmentId);
|
||||
});
|
||||
updateDepartmentPreIdAndNextId(children);
|
||||
return children;
|
||||
}
|
||||
|
||||
// 更新树的前置id和后置id
|
||||
function updateDepartmentPreIdAndNextId(data) {
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
if (index === 0) {
|
||||
data[index].nextId = data.length > 1 ? data[1].departmentId : undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index === data.length - 1) {
|
||||
data[index].preId = data[index - 1].departmentId;
|
||||
data[index].nextId = undefined;
|
||||
continue;
|
||||
}
|
||||
|
||||
data[index].preId = data[index - 1].departmentId;
|
||||
data[index].nextId = data[index + 1].departmentId;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 树的选中 ---------------------
|
||||
const selectedKeys = ref([]);
|
||||
const checkedKeys = ref([]);
|
||||
const breadcrumb = ref([]);
|
||||
const currentSelectedDepartmentId = ref();
|
||||
const selectedDepartmentChildren = ref([]);
|
||||
|
||||
departmentEmitter.on('selectTree', selectTree);
|
||||
|
||||
function selectTree(id) {
|
||||
selectedKeys.value = [id];
|
||||
treeSelectChange(selectedKeys.value);
|
||||
}
|
||||
|
||||
function treeSelectChange(idList) {
|
||||
if (_.isEmpty(idList)) {
|
||||
breadcrumb.value = [];
|
||||
selectedDepartmentChildren.value = [];
|
||||
return;
|
||||
}
|
||||
let id = idList[0];
|
||||
selectedDepartmentChildren.value = departmentList.value.filter((e) => e.parentId == id);
|
||||
let filterDepartmentList = [];
|
||||
recursionFilterDepartment(filterDepartmentList, id, true);
|
||||
breadcrumb.value = filterDepartmentList.map((e) => e.name);
|
||||
}
|
||||
|
||||
// ----------------------- 筛选 ---------------------
|
||||
const keywords = ref('');
|
||||
watch(
|
||||
() => keywords.value,
|
||||
() => {
|
||||
onSearch();
|
||||
}
|
||||
);
|
||||
|
||||
// 筛选
|
||||
function onSearch() {
|
||||
if (!keywords.value) {
|
||||
departmentTreeData.value = buildDepartmentTree(departmentList.value, DEPARTMENT_PARENT_ID);
|
||||
return;
|
||||
}
|
||||
let originData = departmentList.value.concat();
|
||||
if (!originData) {
|
||||
return;
|
||||
}
|
||||
// 筛选出名称符合的部门
|
||||
let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
|
||||
let filterDepartmentList = [];
|
||||
// 循环筛选出的部门 构建部门树
|
||||
filterDepartment.forEach((e) => {
|
||||
recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
|
||||
});
|
||||
|
||||
departmentTreeData.value = buildDepartmentTree(filterDepartmentList, DEPARTMENT_PARENT_ID);
|
||||
}
|
||||
|
||||
// 根据ID递归筛选部门
|
||||
function recursionFilterDepartment(resList, id, unshift) {
|
||||
let info = idInfoMap.value.get(id);
|
||||
if (!info || resList.some((e) => e.departmentId == id)) {
|
||||
return;
|
||||
}
|
||||
if (unshift) {
|
||||
resList.unshift(info);
|
||||
} else {
|
||||
resList.push(info);
|
||||
}
|
||||
if (info.parentId && info.parentId != 0) {
|
||||
recursionFilterDepartment(resList, info.parentId, unshift);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
departmentEmitter.all.clear();
|
||||
});
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
queryDepartmentTree,
|
||||
selectedDepartmentChildren,
|
||||
breadcrumb,
|
||||
selectedKeys,
|
||||
checkedKeys,
|
||||
keywords,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.tree-container {
|
||||
height: 100%;
|
||||
.tree {
|
||||
height: 618px;
|
||||
margin-top: 10px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sort-flag-row {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sort-span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.no-data {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
* 部门 员工 弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:open="visible" title="调整部门" :footer="null" destroyOnClose>
|
||||
<DepartmentTree ref="departmentTree" :height="400" :showMenu="false" />
|
||||
<div class="footer">
|
||||
<a-button style="margin-right: 8px" @click="closeModal">取消</a-button>
|
||||
<a-button type="primary" @click="handleOk">提交</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { ref } from 'vue';
|
||||
import DepartmentTree from '../department-tree/index.vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// ----------------------- 显示/隐藏 ------------------------
|
||||
|
||||
const departmentTree = ref();
|
||||
const visible = ref(false);
|
||||
const employeeIdList = ref([]);
|
||||
|
||||
//显示
|
||||
async function showModal(selectEmployeeId) {
|
||||
employeeIdList.value = selectEmployeeId;
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
//隐藏
|
||||
function closeModal() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// ----------------------- form操作 ---------------------------------
|
||||
async function handleOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
if (_.isEmpty(employeeIdList.value)) {
|
||||
message.warning('请选择要调整的员工');
|
||||
return;
|
||||
}
|
||||
if (_.isEmpty(departmentTree.value.selectedKeys)) {
|
||||
message.warning('请选择要调整的部门');
|
||||
return;
|
||||
}
|
||||
let departmentId = departmentTree.value.selectedKeys[0];
|
||||
let params = {
|
||||
employeeIdList: employeeIdList.value,
|
||||
departmentId: departmentId,
|
||||
};
|
||||
await employeeApi.batchUpdateDepartmentEmployee(params);
|
||||
message.success('操作成功');
|
||||
emit('refresh');
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
text-align: right;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,236 @@
|
||||
<!--
|
||||
* 员工 表单 弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-drawer
|
||||
:title="form.employeeId ? '编辑' : '添加'"
|
||||
:width="600"
|
||||
:open="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
@close="onClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-alert message="超管需要直接在数据库表 t_employee修改哦" type="error" closable />
|
||||
<br />
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<a-form-item label="姓名" name="actualName">
|
||||
<a-input v-model:value.trim="form.actualName" placeholder="请输入姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value.trim="form.phone" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" name="departmentId">
|
||||
<DepartmentTreeSelect ref="departmentTreeSelect" width="100%" :init="false" v-model:value="form.departmentId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="登录名" name="loginName">
|
||||
<a-input v-model:value.trim="form.loginName" placeholder="请输入登录名" />
|
||||
<p class="hint">初始密码默认为:随机</p>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value.trim="form.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="gender">
|
||||
<smart-enum-select style="width: 100%" v-model:value="form.gender" placeholder="请选择性别" enum-name="GENDER_ENUM" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="disabledFlag">
|
||||
<a-select v-model:value="form.disabledFlag" placeholder="请选择状态">
|
||||
<a-select-option :value="0">启用</a-select-option>
|
||||
<a-select-option :value="1">禁用</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="职务" name="positionId">
|
||||
<PositionSelect v-model:value="form.positionId" placeholder="请选择职务" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="角色" name="roleIdList">
|
||||
<a-select mode="multiple" v-model:value="form.roleIdList" optionFilterProp="title" placeholder="请选择角色">
|
||||
<a-select-option v-for="item in roleList" :key="item.roleId" :title="item.roleName">{{ item.roleName }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="footer">
|
||||
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
|
||||
<a-button type="primary" style="margin-right: 8px" @click="onSubmit(false)">保存</a-button>
|
||||
<a-button v-if="!form.employeeId" type="primary" @click="onSubmit(true)">保存并继续添加</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { roleApi } from '/@/api/system/role-api';
|
||||
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
|
||||
import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';
|
||||
import PositionSelect from '/@/components/system/position-select/index.vue';
|
||||
import { GENDER_ENUM } from '/@/constants/common-const';
|
||||
import { regular } from '/@/constants/regular-const';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
const departmentTreeSelect = ref();
|
||||
// emit
|
||||
const emit = defineEmits(['refresh', 'show-account']);
|
||||
|
||||
// ----------------------- 显示/隐藏 ---------------------
|
||||
|
||||
const visible = ref(false); // 是否展示抽屉
|
||||
// 隐藏
|
||||
function onClose() {
|
||||
reset();
|
||||
visible.value = false;
|
||||
}
|
||||
// 显示
|
||||
async function showDrawer(rowData) {
|
||||
Object.assign(form, formDefault);
|
||||
if (rowData && !_.isEmpty(rowData)) {
|
||||
Object.assign(form, rowData);
|
||||
}
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
queryAllRole();
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------- 表单显示 ---------------------
|
||||
|
||||
const roleList = ref([]); //角色列表
|
||||
async function queryAllRole() {
|
||||
let res = await roleApi.queryAll();
|
||||
roleList.value = res.data;
|
||||
}
|
||||
|
||||
const formRef = ref(); // 组件ref
|
||||
const formDefault = {
|
||||
employeeId: undefined,
|
||||
actualName: undefined,
|
||||
departmentId: undefined,
|
||||
disabledFlag: 0,
|
||||
leaveFlag: 0,
|
||||
gender: GENDER_ENUM.MAN.value,
|
||||
loginName: undefined,
|
||||
phone: undefined,
|
||||
roleIdList: undefined,
|
||||
positionId: undefined,
|
||||
};
|
||||
|
||||
let form = reactive(_.cloneDeep(formDefault));
|
||||
function reset() {
|
||||
Object.assign(form, formDefault);
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
|
||||
// ----------------------- 表单提交 ---------------------
|
||||
// 表单规则
|
||||
const rules = {
|
||||
actualName: [
|
||||
{ required: true, message: '姓名不能为空' },
|
||||
{ max: 30, message: '姓名不能大于30个字符', trigger: 'blur' },
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空' },
|
||||
{ pattern: regular.phone, message: '请输入正确的手机号码', trigger: 'blur' },
|
||||
],
|
||||
loginName: [
|
||||
{ required: true, message: '登录账号不能为空' },
|
||||
{ max: 30, message: '登录账号不能大于30个字符', trigger: 'blur' },
|
||||
],
|
||||
gender: [{ required: true, message: '性别不能为空' }],
|
||||
departmentId: [{ required: true, message: '部门不能为空' }],
|
||||
disabledFlag: [{ required: true, message: '状态不能为空' }],
|
||||
leaveFlag: [{ required: true, message: '在职状态不能为空' }],
|
||||
email: [{ required: true, message: '请输入邮箱' }],
|
||||
};
|
||||
|
||||
// 校验表单
|
||||
function validateForm(formRef) {
|
||||
return new Promise((resolve) => {
|
||||
formRef
|
||||
.validate()
|
||||
.then(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 提交数据
|
||||
async function onSubmit(keepAdding) {
|
||||
let validateFormRes = await validateForm(formRef.value);
|
||||
if (!validateFormRes) {
|
||||
message.error('参数验证错误,请仔细填写表单数据!');
|
||||
return;
|
||||
}
|
||||
SmartLoading.show();
|
||||
if (form.employeeId) {
|
||||
await updateEmployee(keepAdding);
|
||||
} else {
|
||||
await addEmployee(keepAdding);
|
||||
}
|
||||
}
|
||||
|
||||
async function addEmployee(keepAdding) {
|
||||
try {
|
||||
let { data } = await employeeApi.addEmployee(form);
|
||||
message.success('添加成功');
|
||||
emit('show-account', form.loginName, data);
|
||||
if (keepAdding) {
|
||||
reset();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
async function updateEmployee(keepAdding) {
|
||||
try {
|
||||
let result = await employeeApi.updateEmployee(form);
|
||||
message.success('更新成功');
|
||||
if (keepAdding) {
|
||||
reset();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------- 以下是暴露的方法内容 ----------------------------
|
||||
defineExpose({
|
||||
showDrawer,
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
padding: 10px 16px;
|
||||
background: #fff;
|
||||
text-align: right;
|
||||
z-index: 1;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 5px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,416 @@
|
||||
<!--
|
||||
* 员工 列表
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-card class="employee-container">
|
||||
<div class="header">
|
||||
<a-typography-title :level="5">部门人员</a-typography-title>
|
||||
<div class="query-operate">
|
||||
<a-radio-group v-model:value="params.disabledFlag" style="margin: 8px; flex-shrink: 0" @change="queryEmployeeByKeyword(false)">
|
||||
<a-radio-button :value="undefined">全部</a-radio-button>
|
||||
<a-radio-button :value="false">启用</a-radio-button>
|
||||
<a-radio-button :value="true">禁用</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-input-search v-model:value.trim="params.keyword" placeholder="姓名/手机号/登录账号" @search="queryEmployeeByKeyword(true)">
|
||||
<template #enterButton>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
查询
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<a-button @click="reset" class="smart-margin-left10">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a-button class="btn" type="primary" @click="showDrawer" v-privilege="'system:employee:add'">添加成员</a-button>
|
||||
<a-button class="btn" @click="updateEmployeeDepartment" v-privilege="'system:employee:department:update'">调整部门</a-button>
|
||||
<a-button class="btn" @click="batchDelete" v-privilege="'system:employee:delete'">批量删除</a-button>
|
||||
|
||||
<span class="smart-table-column-operate">
|
||||
<TableOperator v-model="columns" :tableId="TABLE_ID_CONST.SYSTEM.EMPLOYEE" :refresh="queryEmployee" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="false"
|
||||
:loading="tableLoading"
|
||||
:scroll="{ x: 1500 }"
|
||||
row-key="employeeId"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ text, record, index, column }">
|
||||
<template v-if="column.dataIndex === 'administratorFlag'">
|
||||
<a-tag color="error" v-if="text">超管</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'disabledFlag'">
|
||||
<a-tag :color="text ? 'error' : 'processing'">{{ text ? '禁用' : '启用' }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'gender'">
|
||||
<span>{{ $smartEnumPlugin.getDescByValue('GENDER_ENUM', text) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.dataIndex === 'operate'">
|
||||
<div class="smart-table-operate">
|
||||
<a-button v-privilege="'system:employee:update'" type="link" size="small" @click="showDrawer(record)">编辑</a-button>
|
||||
<a-button
|
||||
v-privilege="'system:employee:password:reset'"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="resetPassword(record.employeeId, record.loginName)"
|
||||
>重置密码</a-button
|
||||
>
|
||||
<a-button v-privilege="'system:employee:disabled'" type="link" @click="updateDisabled(record.employeeId, record.disabledFlag)">{{
|
||||
record.disabledFlag ? '启用' : '禁用'
|
||||
}}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="smart-query-table-page">
|
||||
<a-pagination
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
show-less-items
|
||||
:pageSizeOptions="PAGE_SIZE_OPTIONS"
|
||||
:defaultPageSize="params.pageSize"
|
||||
v-model:current="params.pageNum"
|
||||
v-model:pageSize="params.pageSize"
|
||||
:total="total"
|
||||
@change="queryEmployee"
|
||||
@showSizeChange="queryEmployee"
|
||||
:show-total="showTableTotal"
|
||||
/>
|
||||
</div>
|
||||
<EmployeeFormModal ref="employeeFormModal" @refresh="queryEmployee" @show-account="showAccount" />
|
||||
<EmployeeDepartmentFormModal ref="employeeDepartmentFormModal" @refresh="queryEmployee" />
|
||||
<EmployeePasswordDialog ref="employeePasswordDialog" />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import _ from 'lodash';
|
||||
import { computed, createVNode, reactive, ref, watch } from 'vue';
|
||||
import { employeeApi } from '/@/api/system/employee-api';
|
||||
import { PAGE_SIZE } from '/@/constants/common-const';
|
||||
import { SmartLoading } from '/@/components/framework/smart-loading';
|
||||
import EmployeeFormModal from '../employee-form-modal/index.vue';
|
||||
import EmployeeDepartmentFormModal from '../employee-department-form-modal/index.vue';
|
||||
import EmployeePasswordDialog from '../employee-password-dialog/index.vue';
|
||||
import { PAGE_SIZE_OPTIONS, showTableTotal } from '/@/constants/common-const';
|
||||
import { smartSentry } from '/@/lib/smart-sentry';
|
||||
import TableOperator from '/@/components/support/table-operator/index.vue';
|
||||
import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
|
||||
|
||||
// ----------------------- 以下是字段定义 emits props ---------------------
|
||||
|
||||
const props = defineProps({
|
||||
departmentId: Number,
|
||||
breadcrumb: Array,
|
||||
});
|
||||
|
||||
//-------------回显账号密码信息----------
|
||||
let employeePasswordDialog = ref();
|
||||
function showAccount(accountName, passWord) {
|
||||
employeePasswordDialog.value.showModal(accountName, passWord);
|
||||
}
|
||||
|
||||
// ----------------------- 表格/列表/ 搜索 ---------------------
|
||||
//字段
|
||||
const columns = ref([
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'actualName',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'loginName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 85,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '超管',
|
||||
dataIndex: 'administratorFlag',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'disabledFlag',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '职务',
|
||||
dataIndex: 'positionName',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roleNameList',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departmentName',
|
||||
ellipsis: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operate',
|
||||
width: 140,
|
||||
},
|
||||
]);
|
||||
const tableData = ref();
|
||||
|
||||
let defaultParams = {
|
||||
departmentId: undefined,
|
||||
disabledFlag: false,
|
||||
keyword: undefined,
|
||||
searchCount: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: PAGE_SIZE,
|
||||
sortItemList: undefined,
|
||||
};
|
||||
const params = reactive({ ...defaultParams });
|
||||
const total = ref(0);
|
||||
|
||||
// 搜索重置
|
||||
function reset() {
|
||||
Object.assign(params, defaultParams);
|
||||
queryEmployee();
|
||||
}
|
||||
|
||||
const tableLoading = ref(false);
|
||||
// 查询
|
||||
async function queryEmployee() {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
params.departmentId = props.departmentId;
|
||||
let res = await employeeApi.queryEmployee(params);
|
||||
for (const item of res.data.list) {
|
||||
item.roleNameList = _.join(item.roleNameList, ',');
|
||||
}
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
// 清除选中
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据关键字 查询
|
||||
async function queryEmployeeByKeyword(allDepartment) {
|
||||
tableLoading.value = true;
|
||||
try {
|
||||
params.pageNum = 1;
|
||||
params.departmentId = allDepartment ? undefined : props.departmentId;
|
||||
let res = await employeeApi.queryEmployee(params);
|
||||
for (const item of res.data.list) {
|
||||
item.roleNameList = _.join(item.roleNameList, ',');
|
||||
}
|
||||
tableData.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
// 清除选中
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.departmentId,
|
||||
() => {
|
||||
if (props.departmentId !== params.departmentId) {
|
||||
params.pageNum = 1;
|
||||
queryEmployee();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// ----------------------- 多选操作 ---------------------
|
||||
|
||||
let selectedRowKeys = ref([]);
|
||||
let selectedRows = ref([]);
|
||||
// 是否有选中:用于 批量操作按钮的禁用
|
||||
const hasSelected = computed(() => selectedRowKeys.value.length > 0);
|
||||
|
||||
function onSelectChange(keyArray, selectRows) {
|
||||
selectedRowKeys.value = keyArray;
|
||||
selectedRows.value = selectRows;
|
||||
}
|
||||
|
||||
// 批量删除员工
|
||||
function batchDelete() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择要删除的员工');
|
||||
return;
|
||||
}
|
||||
const actualNameArray = selectedRows.value.map((e) => e.actualName);
|
||||
const employeeIdArray = selectedRows.value.map((e) => e.employeeId);
|
||||
Modal.confirm({
|
||||
title: '确定要删除如下员工吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: _.join(actualNameArray, ','),
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.batchDeleteEmployee(employeeIdArray);
|
||||
message.success('删除成功');
|
||||
queryEmployee();
|
||||
selectedRowKeys.value = [];
|
||||
selectedRows.value = [];
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// 批量更新员工部门
|
||||
const employeeDepartmentFormModal = ref();
|
||||
|
||||
function updateEmployeeDepartment() {
|
||||
if (!hasSelected.value) {
|
||||
message.warning('请选择要调整部门的员工');
|
||||
return;
|
||||
}
|
||||
const employeeIdArray = selectedRows.value.map((e) => e.employeeId);
|
||||
employeeDepartmentFormModal.value.showModal(employeeIdArray);
|
||||
}
|
||||
|
||||
// ----------------------- 添加、修改、禁用、重置密码 ------------------------------------
|
||||
|
||||
const employeeFormModal = ref(); //组件
|
||||
|
||||
// 展示编辑弹窗
|
||||
function showDrawer(rowData) {
|
||||
let params = {};
|
||||
if (rowData) {
|
||||
params = _.cloneDeep(rowData);
|
||||
params.disabledFlag = params.disabledFlag ? 1 : 0;
|
||||
} else if (props.departmentId) {
|
||||
params.departmentId = props.departmentId;
|
||||
}
|
||||
employeeFormModal.value.showDrawer(params);
|
||||
}
|
||||
|
||||
// 重置密码
|
||||
function resetPassword(id, name) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: '确定要重置密码吗?',
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
let { data: passWord } = await employeeApi.resetPassword(id);
|
||||
message.success('重置成功');
|
||||
employeePasswordDialog.value.showModal(name, passWord);
|
||||
queryEmployee();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
|
||||
// 禁用 / 启用
|
||||
function updateDisabled(id, disabledFlag) {
|
||||
Modal.confirm({
|
||||
title: '提醒',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: `确定要${disabledFlag ? '启用' : '禁用'}吗?`,
|
||||
okText: '确定',
|
||||
okType: 'danger',
|
||||
async onOk() {
|
||||
SmartLoading.show();
|
||||
try {
|
||||
await employeeApi.updateDisabled(id);
|
||||
message.success(`${disabledFlag ? '启用' : '禁用'}成功`);
|
||||
queryEmployee();
|
||||
} catch (error) {
|
||||
smartSentry.captureError(error);
|
||||
} finally {
|
||||
SmartLoading.hide();
|
||||
}
|
||||
},
|
||||
cancelText: '取消',
|
||||
onCancel() {},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.employee-container {
|
||||
height: 100%;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.query-operate {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin: 10px 0;
|
||||
.btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
* 员工 修改密码的 显示密码弹窗
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<a-modal v-model:open="visible" :zIndex="9999" :width="500" title="提示" :closable="false" :maskClosable="false">
|
||||
<!-- -->
|
||||
<ul>
|
||||
<li>登录名: {{ showLoginName }}</li>
|
||||
<li>密码: {{ showLoginPassword }}</li>
|
||||
</ul>
|
||||
<template #footer>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="account-copy"
|
||||
:data-clipboard-text="`登录名:${showLoginName}
|
||||
密码:${showLoginPassword}`"
|
||||
size="middle"
|
||||
@click="copy"
|
||||
>复制密码并关闭</a-button
|
||||
>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import Clipboard from 'clipboard';
|
||||
import { ref } from 'vue';
|
||||
|
||||
let visible = ref(false); // 是否展示抽屉
|
||||
let showLoginName = ref(''); //登录名
|
||||
let showLoginPassword = ref(''); //登录密码
|
||||
|
||||
function copy() {
|
||||
handleCopy();
|
||||
visible.value = false;
|
||||
}
|
||||
function showModal(loginName, loginPassword) {
|
||||
visible.value = true;
|
||||
showLoginName.value = loginName;
|
||||
showLoginPassword.value = loginPassword;
|
||||
}
|
||||
function handleCopy() {
|
||||
let clipboard = new Clipboard('.account-copy');
|
||||
clipboard.on('success', (e) => {
|
||||
message.info('复制成功');
|
||||
console.log('复制成功');
|
||||
// 释放内存
|
||||
clipboard.destroy();
|
||||
});
|
||||
clipboard.on('error', (e) => {
|
||||
// 不支持复制
|
||||
message.error('浏览器不支持复制,请您手动选择复制');
|
||||
// 释放内存
|
||||
clipboard.destroy();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
showModal,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
padding-left: 32%;
|
||||
li {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
>
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 部门event bus
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-07-12 23:32:48
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
*/
|
||||
import mitt from 'mitt';
|
||||
export default mitt();
|
||||
@@ -0,0 +1,70 @@
|
||||
<!--
|
||||
* 组织架构
|
||||
*
|
||||
* @Author: 1024创新实验室-主任:卓大
|
||||
* @Date: 2022-08-08 20:46:18
|
||||
* @Wechat: zhuda1024
|
||||
* @Email: lab1024@163.com
|
||||
* @Copyright 1024创新实验室 ( https://1024lab.net ),Since 2012
|
||||
-->
|
||||
<template>
|
||||
<div class="height100">
|
||||
<a-row :gutter="16" class="height100">
|
||||
<a-col :span="6">
|
||||
<DepartmentTree ref="departmentTree" />
|
||||
</a-col>
|
||||
|
||||
<a-col :span="18" class="height100">
|
||||
<div class="employee-box height100">
|
||||
<EmployeeList style="flex-grow: 2.5" class="employee" :departmentId="selectedDepartmentId" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import _ from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
import DepartmentTree from './components/department-tree/index.vue';
|
||||
import EmployeeList from './components/employee-list/index.vue';
|
||||
|
||||
const departmentTree = ref();
|
||||
|
||||
// 部门 面包屑
|
||||
const breadcrumb = computed(() => {
|
||||
if (departmentTree.value) {
|
||||
return departmentTree.value.breadcrumb;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// 当前选中部门的孩子
|
||||
const selectedDepartmentChildren = computed(() => {
|
||||
if (departmentTree.value) {
|
||||
return departmentTree.value.selectedDepartmentChildren;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// 当前选中的部门id
|
||||
const selectedDepartmentId = computed(() => {
|
||||
if (departmentTree.value) {
|
||||
let selectedKeys = departmentTree.value.selectedKeys;
|
||||
return _.isEmpty(selectedKeys) ? null : selectedKeys[0];
|
||||
}
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.height100 {
|
||||
height: 100%;
|
||||
}
|
||||
.employee-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.employee {
|
||||
flex-grow: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user