feat: 对接系统配置中的组织架构90%
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑部门' : '新增部门'"
|
||||
unmount-on-close
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
auto-label-width
|
||||
layout="vertical"
|
||||
>
|
||||
<!-- 部门名称 -->
|
||||
<a-form-item :label="t('department-name')" field="name" required>
|
||||
<a-input v-model="form.name" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 所属部门 -->
|
||||
<a-form-item :label="t('belongto-department')" field="parent_id" required>
|
||||
<a-tree-select
|
||||
v-model="form.parent_id"
|
||||
:data="departmentListData"
|
||||
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||
placeholder="请选择所属部门"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 部门主管 -->
|
||||
<a-form-item :label="t('department-head')" field="user_id">
|
||||
<a-select
|
||||
v-model="form.user_id"
|
||||
:options="props.userListData"
|
||||
placeholder="请选择部门主管"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<a-form-item :label="t('remark')" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import {
|
||||
createDepartment,
|
||||
updateDepartment,
|
||||
DepartmentItem,
|
||||
UserList,
|
||||
} from '@/api/setting';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
// Props & Emits
|
||||
// interface LeaderOption {
|
||||
// name: string;
|
||||
// id: number;
|
||||
// }
|
||||
|
||||
interface DepartmentForm {
|
||||
id?: number;
|
||||
name: string;
|
||||
parent_id: number | null;
|
||||
user_id?: number;
|
||||
remark?: string;
|
||||
}
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
isEdit: boolean;
|
||||
record?: DepartmentItem | null;
|
||||
departmentListData: any[]; // 新增
|
||||
userListData: UserList;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'submitted'): void;
|
||||
}>();
|
||||
// 表单数据
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive<DepartmentForm>({
|
||||
name: '',
|
||||
parent_id: null,
|
||||
user_id: undefined,
|
||||
remark: '',
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
// 校验规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入部门名称' }],
|
||||
parent_id: [{ required: true, message: '请选择所属部门' }],
|
||||
};
|
||||
|
||||
// Watch 弹窗控制 & 初始化
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.record) {
|
||||
form.id = props.record?.id;
|
||||
form.name = props.record?.name;
|
||||
form.parent_id = props.record?.parentId ?? '';
|
||||
form.user_id = props.record?.UserId ?? '';
|
||||
form.remark = props.record?.remark ?? '';
|
||||
// Object.assign(form, props.record);
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
parent_id: null,
|
||||
user_id: undefined,
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 表单事件
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
const { code, message } = props.isEdit
|
||||
? await updateDepartment(form)
|
||||
: await createDepartment(form);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
handleCancel();
|
||||
// 通知父组件更新部门列表数据
|
||||
emit('submitted');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,3 +1,82 @@
|
||||
<template>
|
||||
<div>帅哥kilo</div>
|
||||
<div class="setting_user_list">
|
||||
<Breadcrumb :items="['menu.setting', 'menu.setting.system']" />
|
||||
<a-row :gutter="12">
|
||||
<a-col flex="310px">
|
||||
<a-card
|
||||
class="full_height"
|
||||
:bordered="false"
|
||||
:title="$t('menu.setting.system')"
|
||||
>
|
||||
<ul class="system_setting_list">
|
||||
<li
|
||||
v-for="item in systemSettingList"
|
||||
:key="item.id"
|
||||
:class="[
|
||||
'system_setting_item',
|
||||
{ active: currentActive === item.id },
|
||||
]"
|
||||
@click="handleChooseSystemItem(item.id)"
|
||||
>
|
||||
<component :is="item.icon" class="mr-2" />
|
||||
<p class="name">{{ item.name }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<organization v-if="currentActive === 4" />
|
||||
<div v-else>开发中{{ currentActive }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {
|
||||
IconSettings,
|
||||
IconSafe,
|
||||
IconPaste,
|
||||
IconFullscreen,
|
||||
IconTiktokColor,
|
||||
} from '@arco-design/web-vue/es/icon';
|
||||
import organization from './organization.vue';
|
||||
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
|
||||
const { t } = useI18n();
|
||||
const currentActive = ref(4);
|
||||
const systemSettingList = [
|
||||
{ id: 1, name: t('general-setting'), icon: IconSettings },
|
||||
{ id: 2, name: t('security-setting'), icon: IconSafe },
|
||||
{ id: 3, name: t('company-info'), icon: IconPaste },
|
||||
{ id: 4, name: t('organization'), icon: IconFullscreen },
|
||||
{ id: 5, name: t('appearance-setting'), icon: IconTiktokColor },
|
||||
];
|
||||
const handleChooseSystemItem = (id: number) => {
|
||||
currentActive.value = id;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting_user_list {
|
||||
.system_setting_list {
|
||||
.system_setting_item {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
|
||||
&.active {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<a-card class="full_height" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<p>
|
||||
<icon-drag-arrow />
|
||||
<span class="title" style="margin-left: 15px">{{
|
||||
$t('organization')
|
||||
}}</span>
|
||||
</p>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ $t('add-department') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-row :gutter="12">
|
||||
<a-col class="border" :span="12">
|
||||
<a-tree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:field-names="{ key: 'id', title: 'name' }"
|
||||
:data="departmentListData"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #icon>
|
||||
<IconStar />
|
||||
</template>
|
||||
<template #extra="nodeData">
|
||||
<a-row
|
||||
v-if="nodeData.isShowEditIcon"
|
||||
style="position: absolute; right: 0"
|
||||
:gutter="24"
|
||||
>
|
||||
<a-col :span="4" @click="handleEdit"><icon-edit /></a-col>
|
||||
<a-col :span="4">
|
||||
<a-popconfirm
|
||||
:ok-text="t('cancel')"
|
||||
:cancel-text="t('delete')"
|
||||
:ok-button-props="{ type: 'outline' }"
|
||||
:cancel-button-props="{ type: 'primary', status: 'danger' }"
|
||||
:content="
|
||||
t('please.confirm.delete.some', { name: t('department') })
|
||||
"
|
||||
type="error"
|
||||
@cancel="handleDelete(nodeData)"
|
||||
>
|
||||
<icon-delete />
|
||||
</a-popconfirm>
|
||||
</a-col>
|
||||
<a-col :span="4" @click="handleMoveUp(nodeData)"
|
||||
><icon-up
|
||||
/></a-col>
|
||||
<a-col :span="4" @click="handleMoveDown(nodeData)"
|
||||
><icon-down
|
||||
/></a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-descriptions
|
||||
v-if="departmentDetailInfo"
|
||||
:column="1"
|
||||
layout="horizontal"
|
||||
bordered
|
||||
>
|
||||
<a-descriptions-item :label="t('department-name')">
|
||||
{{ departmentDetailInfo.name }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('belongto-department')">
|
||||
{{ departmentDetailInfo.parentName || '-' }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('department-head')">
|
||||
{{ departmentDetailInfo.username || '-' }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('department-desc')">
|
||||
{{ departmentDetailInfo.remark }}</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<departmentFormModal
|
||||
v-model:visible="showModal"
|
||||
:department-list-data="departmentListData"
|
||||
:record="departmentDetailInfo"
|
||||
:user-list-data="allUserList"
|
||||
:is-edit="isEdit"
|
||||
@submitted="getDepartmentListData"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
||||
import {
|
||||
DepartmentItem,
|
||||
getAllUserList,
|
||||
UserList,
|
||||
departmentSetUp,
|
||||
departmentSetDown,
|
||||
delDepartment,
|
||||
} from '@/api/setting';
|
||||
import { debounce } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import departmentFormModal from './components/department-form-modal.vue';
|
||||
|
||||
const {
|
||||
departmentListData,
|
||||
allDepartmentKeys,
|
||||
getDepartmentListData,
|
||||
getDepartmentDetailData,
|
||||
} = useDepartmentList();
|
||||
const emit = defineEmits(['onSelectTree']);
|
||||
const expandedKeys = ref<(string | number)[]>([]);
|
||||
const { t } = useI18n();
|
||||
watch(
|
||||
allDepartmentKeys,
|
||||
(val) => {
|
||||
expandedKeys.value = val;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const showModal = ref<boolean>(false);
|
||||
const isEdit = ref<boolean>(false);
|
||||
const departmentDetailInfo = ref<DepartmentItem | null>(null);
|
||||
|
||||
const resetEditIcon = (nodes: any[]) => {
|
||||
nodes.forEach((node) => {
|
||||
node.isShowEditIcon = false;
|
||||
if (node.children && node.children.length > 0) {
|
||||
resetEditIcon(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
const currentNode = ref<any>(null);
|
||||
const onSelect = debounce(async (_: (string | number)[], event: any) => {
|
||||
const { node } = event;
|
||||
if (node.id === currentNode.value?.id) return;
|
||||
currentNode.value = node;
|
||||
// 递归重置所有节点的 isShowEditIcon
|
||||
resetEditIcon(departmentListData.value);
|
||||
// 只设置当前节点
|
||||
if (currentNode.value.level !== 1) {
|
||||
node.isShowEditIcon = true;
|
||||
}
|
||||
departmentDetailInfo.value = await getDepartmentDetailData(
|
||||
currentNode.value.id
|
||||
);
|
||||
}, 300);
|
||||
|
||||
// 所有用户数据
|
||||
const allUserList = ref<UserList>([]);
|
||||
const userKeyword = ref<string>('');
|
||||
|
||||
const getAllUserListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getAllUserList({
|
||||
keyword: userKeyword.value,
|
||||
});
|
||||
if (code === 200 && data?.length) {
|
||||
allUserList.value = data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取所以用户数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = false;
|
||||
};
|
||||
const handleEdit = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = true;
|
||||
};
|
||||
const handleDelete = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await delDepartment(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveUp = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetUp(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
} else {
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向上移动失败:', error);
|
||||
}
|
||||
};
|
||||
const handleMoveDown = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetDown(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
} else {
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向下移动失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getAllUserListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.border {
|
||||
border: 1px solid #f4f4f4;
|
||||
height: 100%;
|
||||
padding: 15px 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user