feat: 组织架构

feature/task1.0.0__0514__ch
向文可 2 years ago
parent 3007f5fdf2
commit bc6da51424

@ -0,0 +1,63 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/uc/department/tree',
method: 'get',
params,
});
};
export const searchEmployee = (params) => {
return request({
url: '/uc/employee/department',
method: 'get',
params,
});
};
export const children = (params) => {
return request({
url: '/uc/department/child',
method: 'get',
params,
});
};
export const detail = (id) => {
return request({
url: '/uc/department/' + id,
method: 'get',
});
};
export const create = (data) => {
return request({
url: '/uc/department',
method: 'post',
data,
});
};
export const update = (data) => {
return request({
url: '/uc/department/' + data.id,
method: 'put',
data,
});
};
export const remove = (idList) => {
return request({
url: '/uc/department',
method: 'delete',
params: { idList },
});
};
export const addRole = (data) => {
return request({
url: '/uc/department/role',
method: 'put',
data,
});
};
export const delRole = (data) => {
return request({
url: '/uc/department/role',
method: 'delete',
data,
});
};

@ -13,6 +13,8 @@
.el-button {
&.el-button--text {
background-color: transparent;
padding: 0;
height: unset;
}
:deep(.el-icon) {
position: relative;

@ -1,16 +1,16 @@
<template>
<div class="layout-container layout-default">
<div class="layout-left">
<LayoutAside />
<layout-aside />
</div>
<div class="layout-center">
<LayoutMenu />
<layout-menu />
</div>
<div class="layout-right">
<LayoutHeader />
<LayoutTabs />
<LayoutMain />
<LayoutFooter />
<layout-header />
<layout-tabs />
<layout-main />
<layout-footer />
</div>
</div>
</template>
@ -22,8 +22,8 @@
import LayoutMain from './components/main.vue';
import LayoutMenu from './components/menu.vue';
import LayoutTabs from './components/tabs.vue';
const store = useStore();
store.dispatch('chat/connect');
// const store = useStore();
// store.dispatch('chat/connect');
</script>
<style lang="less" scoped>

@ -0,0 +1,23 @@
export default [
{
path: '/uc',
name: 'UserCenter',
component: () => import('@/layouts/default.vue'),
meta: {
title: '权限中台',
icon: 'admin-fill',
layout: true,
},
children: [
{
path: 'dept',
name: 'DeptManagement',
component: () => import('@/views/permission/dept/index.vue'),
meta: {
title: '组织架构',
icon: 'organization-chart',
},
},
],
},
];

@ -0,0 +1,90 @@
import * as api from '@/api/permission/dept.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
employeeCode: 'DeptEmployeeManagement',
roleCode: 'DeptRoleManagement',
condition1: {},
condition2: {},
list: [],
total: 0,
employeeList: [],
employeeTotal: 0,
opts: {
init: false,
},
});
const getters = {};
const mutations = {
setCondition1: (state, data) => (state.condition1 = data),
setCondition2: (state, data) => (state.condition2 = data),
setList: (state, data) => (state.list = data),
setTotal: (state, data) => (state.total = data),
setEmployeeList: (state, data) => (state.employeeList = data),
setEmployeeTotal: (state, data) => (state.employeeTotal = data),
setOpts: (state, data) => (state.opts = data),
};
const actions = {
search: async ({ state, commit }) => {
let res = await api.search(state.condition1);
commit('setList', res || []);
if (!res) {
ElMessage.error('查询失败');
}
return res;
},
searchEmployee: async ({ state, commit }) => {
let res = await api.searchEmployee(state.condition2);
commit('setEmployeeList', res || []);
if (!res) {
ElMessage.error('查询部门员工失败');
}
return res;
},
load: async ({ commit }) => {
commit('setOpts', {
init: true,
});
},
detail: async (context, id) => {
let res = await api.detail(id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
save: async ({ dispatch }, data) => {
let save = data.id ? api.update : api.create;
let res = await save(data);
if (res) {
ElMessage.success('保存成功');
dispatch('search');
} else {
ElMessage.error('保存失败');
}
return res;
},
remove: async ({ dispatch }, idList) => {
if (!idList.length) {
ElMessage.warning('请选择要删除的数据');
} else {
try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
let res = await api.remove(idList.join(','));
if (res) {
ElMessage.success('删除成功');
dispatch('search');
} else {
ElMessage.error('删除失败');
}
} catch (e) {
console.info('取消删除', e);
}
}
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -1,125 +1,238 @@
<template>
<div class="list-container">
<table-list
ref="refsTable"
v-loading="loading"
:code="code"
:config="config"
:data="list"
:operation="['create']"
sortable
title="分类"
:total="total"
@create="handleCreate()"
@row-click="handleExpand"
@row-sort="handleSort"
/>
<el-dialog v-model="formState.formVisible" title="转移商品" width="360px">
<el-form ref="refsForm" label-width="100px" :model="formState.form" :rules="formState.rules">
<el-form-item label="源分类" prop="sourceId">
<el-cascader
v-model="formState.form.sourceId"
:options="list"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'name',
value: 'id',
children: 'childList',
emitPath: false,
}"
/>
</el-form-item>
<el-form-item label="目标分类" prop="targetId">
<el-cascader
v-model="formState.form.targetId"
:options="list"
:props="{
checkStrictly: true,
expandTrigger: 'hover',
label: 'name',
value: 'id',
children: 'childList',
emitPath: false,
}"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formState.formVisible = false">取消</el-button>
<el-button :loading="formState.submitting" type="primary" @click="handleSave"></el-button>
</template>
</el-dialog>
<div class="aside">
<div class="aside-header">
<div class="aside-title">组织架构</div>
<div class="aside-action">
<el-button type="text" @click="state.condition1.hiddenDisable = !state.condition1.hiddenDisable">
{{ state.condition1.hiddenDisable ? '隐藏' : '显示' }}禁用部门
</el-button>
</div>
</div>
<div class="aside-body">
<el-tree
:data="list"
default-expand-all
:expand-on-click-node="false"
highlight-current
node-key="id"
:props="{
label: 'name',
value: 'id',
children: 'childDepartment',
disabled: (data) => !data.isEnable,
}"
@current-change="(data) => (state.condition2.departmentId = data.id)"
>
<template #default="{ data, node }">
<div class="flex">
<div class="name">
{{ data.name }}
</div>
<div v-if="node.level > 1" class="btns">
<el-button type="text" @click.stop="handleCreate(null, data)">
<el-icon name="Plus" />
</el-button>
<el-button type="text" @click.stop="handleCreate(data)">
<el-icon name="Edit" />
</el-button>
<el-switch v-model="data.isEnable" @click.stop="handleEnabled(data)" />
</div>
</div>
</template>
</el-tree>
<el-dialog
v-model="formState.formVisible"
:title="(formState.form.id ? '编辑' : '新增') + '部门'"
width="300px"
>
<el-form
ref="refsForm"
v-loading="formState.submitting"
label-width="100px"
:model="formState.form"
:rules="formState.rules"
>
<el-form-item label="上级组织" prop="parentId">
<el-cascader
v-model="formState.form.parentId"
:options="list"
:props="{
label: 'name',
value: 'id',
children: 'childDepartment',
disabled: (data) => !data.isEnable,
checkStrictly: true,
expandTrigger: 'hover',
emitPath: false,
}"
/>
</el-form-item>
<el-form-item label="组织名称" prop="name">
<el-input v-model="formState.form.name" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button :loading="formState.submitting" type="primary" @click="handleSave"></el-button>
</template>
</el-dialog>
</div>
</div>
<div class="content">
<table-list
ref="refsTable"
v-loading="loading2"
:code="employeeCode"
:config="employeeConfig"
:data="employeeList"
:operation="['search']"
:total="employeeTotal"
@search="handleSearchEmployee"
>
<template #search>
<el-form inline :model="state.condition2">
<el-form-item label="姓名" prop="employeeName">
<el-input v-model="state.condition2.employeeName" />
</el-form-item>
<el-form-item label="花名" prop="userName">
<el-input v-model="state.condition2.userName" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="state.condition2.phone" />
</el-form-item>
</el-form>
</template>
</table-list>
</div>
</div>
</template>
<script setup lang="jsx">
import ElButton from '@/components/extra/ElButton.vue';
import ElImage from '@/components/extra/ElImage.vue';
import ElSwitch from '@/components/extra/ElSwitch.vue';
import TableList from '@/components/TableList.vue';
const router = useRouter();
const store = useStore();
const { proxy } = getCurrentInstance();
const loading = ref(false);
const refsTable = ref(null);
const code = computed(() => store.state.category.code);
const list = computed(() => _.cloneDeep(store.state.category.list));
const total = computed(() => store.state.category.total);
const opts = computed(() => store.state.category.opts);
const opts = computed(() => store.state.dept.opts);
if (!unref(opts).init) {
store.dispatch('category/load');
store.dispatch('dept/load');
}
/* 查询订单 */
/* 查询树结构 */
const loading = ref(false);
const list = computed(() => _.cloneDeep(store.state.dept.list));
const state = reactive({
condition: {},
condition1: { hiddenDisable: false },
condition2: { employeeName: null, userName: null, phone: null, departmentId: null },
});
const handleSearch = async () => {
loading.value = true;
await store.dispatch('dept/search');
loading.value = false;
};
watch(
() => state.condition,
() => state.condition1,
(value) => {
store.commit('category/setCondition', _.cloneDeep(value));
store.commit('dept/setCondition1', _.cloneDeep(value));
handleSearch();
},
{ immediate: true, deep: true }
);
const handleSearch = async () => {
loading.value = true;
await store.dispatch('category/search');
loading.value = false;
};
onActivated(handleSearch);
const handleCreate = (row) => {
router.push({ name: 'CreateCategory', query: { pid: row?.id } });
};
const handleUpdate = (row) => {
router.push({ name: 'UpdateCategory', params: { id: row.id } });
};
const handleVisible = async (row) => {
loading.value = true;
await store.dispatch('category/save', row);
loading.value = false;
};
const handleRemove = async (rows) => {
await store.dispatch(
'category/remove',
rows.map((item) => item.id)
);
};
const handleSort = async (newSort, oldSort, e, arr) => {
arr = arr || unref(list);
let direction = (oldSort - newSort) / Math.abs(oldSort - newSort);
let currentSort = arr[newSort].sort;
oldSort = arr[newSort + direction].sort;
loading.value = true;
await store.dispatch('category/sort', { id: arr[newSort].id, currentSort, oldSort });
loading.value = false;
/* 查询员工 */
const loading2 = ref(false);
const refsTable = ref(null);
const employeeCode = computed(() => store.state.dept.employeeCode);
const employeeList = computed(() => _.cloneDeep(store.state.dept.employeeList));
const employeeTotal = computed(() => store.state.dept.employeeTotal);
const handleSearchEmployee = async () => {
if (state.condition2.departmentId) {
loading2.value = true;
await store.dispatch('dept/searchEmployee');
loading2.value = false;
} else {
proxy.$message.error('请先选择部门');
}
};
const handleExpand = (row) => {
unref(list).forEach((item) => {
unref(refsTable).toggleRowExpansion(item, false);
watch(
() => state.condition2,
(value) => {
let search = value?.departmentId && value?.departmentId !== store.state.dept.condition2.departmentId;
store.commit('dept/setCondition2', _.cloneDeep(value));
if (search) {
handleSearchEmployee();
}
},
{ immediate: true, deep: true }
);
const handleUpdateEmployee = (row) => {
router.push({
name: 'UpdateEmployee',
params: {
id: row.id,
},
});
unref(refsTable).toggleRowExpansion(row);
};
const handleTransferEmployee = () => {};
let employeeConfig = reactive({
page: false,
columns: [
{
label: '工号',
prop: 'id',
width: 100,
},
{
label: '姓名',
prop: 'employeeName',
minWidth: 160,
},
{
label: '花名',
prop: 'userName',
minWidth: 160,
},
{
label: '手机号',
prop: 'phone',
minWidth: 160,
},
{
label: '状态',
width: 160,
slots: {
default: ({ row }) => <ElSwitch v-model={row.isEnable} />,
},
},
{
label: '创建人',
prop: 'createUserName',
width: 180,
},
{
label: '创建时间',
prop: 'createTime',
width: 180,
},
{
label: '操作',
width: 300,
slots: {
default: ({ row }) => (
<div>
<ElButton type="text" onClick={() => handleUpdateEmployee(row)}>
编辑
</ElButton>
<ElButton type="text" onClick={() => handleTransferEmployee(row)}>
调动
</ElButton>
</div>
),
},
},
],
});
/* 表单 */
const refsForm = ref(null);
@ -127,128 +240,94 @@
formVisible: false,
submitting: false,
form: {
sourceId: null,
targetId: false,
id: null,
parentId: null,
name: null,
},
rules: {
sourceId: [{ required: true, message: '源分类不能为空' }],
targetId: [{ required: true, message: '目标分类不能为空' }],
name: [{ required: true, message: '请输入组织名称' }],
parentId: [{ required: true, message: '请选择组织类型' }],
},
});
//
const handleTransform = (row) => {
formState.form = {
sourceId: row.id,
targetId: null,
};
const handleCreate = (row, parent) => {
formState.formVisible = true;
Object.assign(
formState.form,
row || {
id: null,
parentId: null,
name: null,
isEnable: true,
}
);
if (parent) {
formState.form.parentId = parent.id;
}
};
const handleCancel = () => {
formState.formVisible = false;
};
const handleEnabled = (row) => {
store.dispatch('dept/save', row);
};
const handleSave = async () => {
formState.submitting = true;
try {
await proxy.$validate(refsForm);
let data = _.cloneDeep(formState.form);
await store.dispatch('category/transform', data);
await store.dispatch('dept/save', data);
formState.formVisible = false;
} catch (e) {
console.info('取消保存', e);
}
formState.submitting = false;
};
/* 列表配置 */
const handleConfig = (parent) => {
let res = reactive({
setting: !parent,
page: false,
columns: [
{
label: '分类名称',
prop: 'name',
minWidth: 300,
},
{
label: '图片',
minWidth: 300,
slots: {
default: ({ row }) => <ElImage src={row.picture} alt={row.name} height="64px" />,
},
},
{
label: '是否显示',
width: 120,
slots: {
default: ({ row }) => (
<ElSwitch v-model={row.isEnable} onChange={(visible) => handleVisible(row, visible)} />
),
},
},
{
label: '操作',
width: 300,
slots: {
default: ({ row }) => (
<div>
{!parent ? (
<ElButton type="text" onClick={() => handleCreate(row)}>
新增下级
</ElButton>
) : (
''
)}
<ElButton type="text" onClick={() => handleTransform(row)}>
转移商品
</ElButton>
<ElButton type="text" onClick={() => handleUpdate(row)}>
编辑
</ElButton>
<ElButton type="text" onClick={() => handleRemove([row])}>
删除
</ElButton>
</div>
),
},
},
],
});
if (!parent) {
res.columns.unshift({
type: 'expand',
width: 60,
slots: {
default: ({ row }) => (
<TableList
code={Date.now() + ''}
config={handleConfig(row)}
data={row.childList || []}
operation={[]}
sortable
onRowSort={(newSort, oldSort, e) => handleSort(newSort, oldSort, e, row.childList)}
/>
),
},
});
} else {
res.columns.unshift({
type: 'index',
width: 60,
});
}
return res;
};
const config = handleConfig(null);
</script>
<style lang="less" scoped>
:deep(.el-table__expanded-cell) {
.common-list {
width: 100%;
min-height: 300px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.list-container {
display: flex;
flex-direction: row;
.aside {
margin-right: @layout-space;
.aside-header {
min-width: max-content;
display: flex;
justify-content: space-between;
align-items: center;
.aside-title {
font-size: @layout-h3;
}
.aside-action {
margin-left: @layout-space;
}
}
.aside-body {
:deep(.el-tree) {
.flex {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.btns {
display: flex;
align-items: center;
margin-left: @layout-space-super;
.el-button {
position: relative;
top: 2px;
+ .el-button {
margin-left: @layout-space-small;
}
}
.el-switch {
height: 100%;
transform: scale(0.6);
}
}
}
}
}
}
</style>

@ -21,10 +21,10 @@ export default (configEnv) => {
proxy: {
'/api': {
// target: 'http://192.168.10.109:8090/', // 显雨
target: 'http://192.168.10.5:4500', // 高玉
// target: 'http://192.168.10.5:4500', // 高玉
// target: 'http://192.168.10.67:8090', // 罗战
// target: 'http://192.168.10.93:8090', // 周渺
// target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址
target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址
// target: 'https://you-gateway.mashibing.com', // 生产环境
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),

Loading…
Cancel
Save