Files
im_manager/web/src/views/imOrg/index.vue
T

544 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="组织架构" />
</div>
<n-card :bordered="false" class="proCard">
<n-grid :x-gap="16" :y-gap="16" responsive="screen" cols="1 l:2">
<n-grid-item>
<BasicForm
ref="departmentSearchRef"
@register="registerDepartment"
@submit="reloadDepartments"
@reset="handleDepartmentReset"
@keyup.enter="reloadDepartments"
/>
<n-space class="table-toolbar">
<n-button
v-if="hasPermission(['/imOrg/departmentEdit'])"
type="primary"
@click="handleDepartmentAdd"
>
新增部门
</n-button>
<n-button
v-if="hasPermission(['/imOrg/departmentExport'])"
type="primary"
@click="handleDepartmentExport"
>
导出部门
</n-button>
</n-space>
<BasicTable
ref="departmentTableRef"
:columns="departmentColumns"
:request="loadDepartmentTable"
:row-key="(row) => row.departmentId"
:actionColumn="departmentActionColumn"
:scroll-x="departmentScrollX"
:resizeHeightOffset="-10000"
@update:checked-row-keys="handleDepartmentChecked"
/>
</n-grid-item>
<n-grid-item>
<BasicForm
ref="memberSearchRef"
@register="registerMember"
@submit="reloadMembers"
@reset="handleMemberReset"
@keyup.enter="reloadMembers"
/>
<n-space class="table-toolbar">
<n-button
v-if="hasPermission(['/imOrg/memberEdit'])"
type="primary"
:disabled="!selectedDepartment.departmentId"
@click="handleMemberAdd"
>
添加成员
</n-button>
<n-button
v-if="hasPermission(['/imOrg/memberExport'])"
type="primary"
:disabled="!selectedDepartment.departmentId"
@click="handleMemberExport"
>
导出成员
</n-button>
</n-space>
<BasicTable
ref="memberTableRef"
:columns="memberColumns"
:request="loadMemberTable"
:row-key="(row) => row.id"
:actionColumn="memberActionColumn"
:scroll-x="memberScrollX"
:resizeHeightOffset="-10000"
/>
</n-grid-item>
</n-grid>
</n-card>
<n-modal
v-model:show="departmentModalVisible"
preset="dialog"
:show-icon="false"
:title="departmentForm.id > 0 ? '编辑部门' : '新增部门'"
:style="{ width: dialogWidth }"
>
<n-form
ref="departmentFormRef"
:model="departmentForm"
:rules="departmentRules"
label-placement="left"
label-width="90"
>
<n-form-item label="部门名称" path="name">
<n-input v-model:value="departmentForm.name" placeholder="请输入部门名称" />
</n-form-item>
<n-form-item label="上级部门">
<n-select
v-model:value="departmentForm.parentId"
:options="parentDepartmentOptions"
filterable
placeholder="请选择上级部门"
/>
</n-form-item>
<n-form-item label="部门ID">
<n-input
v-model:value="departmentForm.departmentId"
:disabled="departmentForm.id > 0"
placeholder="留空自动生成"
/>
</n-form-item>
<n-form-item label="排序">
<n-input-number v-model:value="departmentForm.sortOrder" />
</n-form-item>
<n-form-item label="状态" path="state">
<n-select
v-model:value="departmentForm.state"
:options="departmentStateOptions"
placeholder="请选择状态"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="departmentModalVisible = false">取消</n-button>
<n-button type="primary" :loading="submitLoading" @click="submitDepartment"
>保存</n-button
>
</n-space>
</template>
</n-modal>
<n-modal
v-model:show="memberModalVisible"
preset="dialog"
:show-icon="false"
:title="memberForm.id > 0 ? '编辑成员' : '添加成员'"
:style="{ width: dialogWidth }"
>
<n-form
ref="memberFormRef"
:model="memberForm"
:rules="memberRules"
label-placement="left"
label-width="90"
>
<n-form-item label="部门" path="departmentId">
<n-select
v-model:value="memberForm.departmentId"
:options="departmentOptions"
filterable
placeholder="请选择部门"
/>
</n-form-item>
<n-form-item label="用户ID" path="userId">
<n-input
v-model:value="memberForm.userId"
:disabled="memberForm.id > 0"
placeholder="请输入用户ID"
/>
</n-form-item>
<n-form-item label="姓名">
<n-input v-model:value="memberForm.displayName" placeholder="请输入展示姓名" />
</n-form-item>
<n-form-item label="岗位">
<n-input v-model:value="memberForm.position" placeholder="请输入岗位" />
</n-form-item>
<n-form-item label="手机号">
<n-input v-model:value="memberForm.mobile" placeholder="请输入手机号" />
</n-form-item>
<n-form-item label="邮箱">
<n-input v-model:value="memberForm.email" placeholder="请输入邮箱" />
</n-form-item>
<n-form-item label="在职状态" path="employmentState">
<n-select
v-model:value="memberForm.employmentState"
:options="employmentStateOptions"
placeholder="请选择在职状态"
/>
</n-form-item>
<n-form-item label="可联系">
<n-switch v-model:value="memberContactable" />
</n-form-item>
<n-form-item label="排序">
<n-input-number v-model:value="memberForm.sortOrder" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="memberModalVisible = false">取消</n-button>
<n-button type="primary" :loading="submitLoading" @click="submitMember">保存</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { useDictStore } from '@/store/modules/dict';
import { BasicForm, useForm } from '@/components/Form/index';
import { BasicTable, TableAction } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { useUserStore } from '@/store/modules/user';
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
import {
DepartmentDelete,
DepartmentEdit,
DepartmentExport,
DepartmentList,
MemberDelete,
MemberEdit,
MemberExport,
MemberList,
} from '@/api/imOrg';
import {
departmentColumns,
departmentOptions,
departmentRules,
departmentSchemas,
DepartmentState,
loadOptions,
loadTenantOptions,
memberColumns,
memberRules,
memberSchemas,
MemberState,
newDepartmentState,
newMemberState,
tenantOptions,
} from './model';
const dict = useDictStore();
const dialog = useDialog();
const message = useMessage();
const { hasPermission } = usePermission();
const userStore = useUserStore();
const isPlatformAdmin = computed(() => userStore.isCompanyDept);
const departmentSearchRef = ref<any>({});
const memberSearchRef = ref<any>({});
const departmentTableRef = ref();
const memberTableRef = ref();
const departmentFormRef = ref();
const memberFormRef = ref();
const selectedDepartment = ref<DepartmentState>(newDepartmentState(null));
const departmentRows = ref<DepartmentState[]>([]);
const departmentForm = ref<DepartmentState>(newDepartmentState(null));
const memberForm = ref<MemberState>(newMemberState(null));
const departmentModalVisible = ref(false);
const memberModalVisible = ref(false);
const submitLoading = ref(false);
const dialogWidth = computed(() => adaModalWidth(640));
const tenantSchema = departmentSchemas.value.find((item) => item.field === 'tenantId');
if (tenantSchema) {
tenantSchema.ifShow = () => isPlatformAdmin.value;
}
const departmentStateOptions = dict.getOption('ImOrgDepartmentStateOptions');
const employmentStateOptions = dict.getOption('ImOrgEmploymentStateOptions');
const parentDepartmentOptions = computed(() => [
{ label: '根部门', value: 'root' },
...departmentOptions.value.filter((item) => item.value !== departmentForm.value.departmentId),
]);
const memberContactable = computed({
get: () => memberForm.value.contactable === 1,
set: (value: boolean) => {
memberForm.value.contactable = value ? 1 : 0;
},
});
const departmentActionColumn = reactive({
width: 180,
title: '操作',
key: 'action',
fixed: 'right',
render(record: DepartmentState) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '成员',
auth: ['/imOrg/memberList'],
onClick: () => selectDepartment(record),
},
{
label: '编辑',
auth: ['/imOrg/departmentEdit'],
onClick: () => handleDepartmentEdit(record),
},
{
label: '删除',
type: 'error',
auth: ['/imOrg/departmentDelete'],
ifShow: () => record.childCount === 0 && record.memberCount === 0,
onClick: () => handleDepartmentDelete(record),
},
],
});
},
});
const memberActionColumn = reactive({
width: 150,
title: '操作',
key: 'action',
fixed: 'right',
render(record: MemberState) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
auth: ['/imOrg/memberEdit'],
onClick: () => handleMemberEdit(record),
},
{
label: '移除',
type: 'error',
auth: ['/imOrg/memberDelete'],
onClick: () => handleMemberDelete(record),
},
],
});
},
});
const departmentScrollX = computed(() =>
adaTableScrollX(departmentColumns, departmentActionColumn.width)
);
const memberScrollX = computed(() => adaTableScrollX(memberColumns, memberActionColumn.width));
const [registerDepartment, { setFieldsValue: setDepartmentFields }] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:3 2xl:3' },
labelWidth: 90,
schemas: departmentSchemas,
});
const [registerMember, { setFieldsValue: setMemberFields }] = useForm({
gridProps: { cols: '1 s:1 m:2 l:2 xl:2 2xl:2' },
labelWidth: 90,
schemas: memberSchemas,
});
const currentTenantId = computed(() => departmentSearchRef.value?.formModel?.tenantId || 0);
const loadDepartmentTable = async (res) => {
const params = { ...departmentSearchRef.value?.formModel, ...res };
if (isPlatformAdmin.value && !params.tenantId)
return { list: [], page: 1, pageCount: 0, totalCount: 0 };
const data = await DepartmentList(params);
departmentRows.value = data?.list || [];
departmentOptions.value = departmentRows.value.map((item) => ({
label: `${item.name}(${item.departmentId})`,
value: item.departmentId,
}));
if (!selectedDepartment.value.departmentId && departmentRows.value.length > 0) {
selectedDepartment.value = newDepartmentState(departmentRows.value[0]);
reloadMembers();
}
return data;
};
const loadMemberTable = async (res) => {
if (!selectedDepartment.value.departmentId)
return { list: [], page: 1, pageCount: 0, totalCount: 0 };
const params = {
...memberSearchRef.value?.formModel,
...res,
tenantId: currentTenantId.value,
departmentId: selectedDepartment.value.departmentId,
};
return await MemberList(params);
};
function reloadDepartments() {
departmentTableRef.value?.reload();
}
function reloadMembers() {
memberTableRef.value?.reload();
}
function handleDepartmentExport() {
const params = { ...departmentSearchRef.value?.formModel };
if (isPlatformAdmin.value && !params.tenantId) {
message.warning('请先选择商户');
return;
}
DepartmentExport(params);
}
function handleMemberExport() {
if (!selectedDepartment.value.departmentId) {
message.warning('请先选择部门');
return;
}
MemberExport({
...memberSearchRef.value?.formModel,
tenantId: currentTenantId.value,
departmentId: selectedDepartment.value.departmentId,
});
}
async function handleDepartmentReset() {
const defaults: Recordable = {};
if (isPlatformAdmin.value && tenantOptions.value.length > 0)
defaults.tenantId = tenantOptions.value[0].value;
selectedDepartment.value = newDepartmentState(null);
await setDepartmentFields(defaults);
reloadDepartments();
}
async function handleMemberReset() {
await setMemberFields({});
reloadMembers();
}
function handleDepartmentChecked(keys) {
const key = keys?.[0];
const row = departmentRows.value.find((item) => item.departmentId === key);
if (row) selectDepartment(row);
}
function selectDepartment(record: DepartmentState) {
selectedDepartment.value = newDepartmentState(record);
reloadMembers();
}
function handleDepartmentAdd() {
departmentForm.value = newDepartmentState({
tenantId: currentTenantId.value,
parentId: selectedDepartment.value.departmentId || 'root',
state: 'active',
});
departmentModalVisible.value = true;
}
function handleDepartmentEdit(record: DepartmentState) {
departmentForm.value = newDepartmentState(record);
departmentModalVisible.value = true;
}
async function submitDepartment() {
submitLoading.value = true;
try {
await departmentFormRef.value?.validate();
await DepartmentEdit({ ...departmentForm.value, tenantId: currentTenantId.value });
message.success('操作成功');
departmentModalVisible.value = false;
selectedDepartment.value = newDepartmentState(null);
reloadDepartments();
} finally {
submitLoading.value = false;
}
}
function handleDepartmentDelete(record: DepartmentState) {
dialog.error({
title: '删除部门',
content: `确认删除部门 ${record.name}?`,
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
await DepartmentDelete({
tenantId: currentTenantId.value,
departmentId: record.departmentId,
});
message.success('操作成功');
selectedDepartment.value = newDepartmentState(null);
reloadDepartments();
reloadMembers();
},
});
}
function handleMemberAdd() {
memberForm.value = newMemberState({
tenantId: currentTenantId.value,
departmentId: selectedDepartment.value.departmentId,
employmentState: 'active',
contactable: 1,
});
memberModalVisible.value = true;
}
function handleMemberEdit(record: MemberState) {
memberForm.value = newMemberState(record);
memberModalVisible.value = true;
}
async function submitMember() {
submitLoading.value = true;
try {
await memberFormRef.value?.validate();
await MemberEdit({ ...memberForm.value, tenantId: currentTenantId.value });
message.success('操作成功');
memberModalVisible.value = false;
reloadDepartments();
reloadMembers();
} finally {
submitLoading.value = false;
}
}
function handleMemberDelete(record: MemberState) {
dialog.error({
title: '移除成员',
content: `确认将 ${record.displayName || record.userId} 从部门移除?`,
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
await MemberDelete({ tenantId: currentTenantId.value, id: record.id });
message.success('操作成功');
reloadDepartments();
reloadMembers();
},
});
}
onMounted(async () => {
loadOptions();
if (isPlatformAdmin.value) {
await loadTenantOptions();
if (tenantOptions.value.length > 0) {
await setDepartmentFields({ tenantId: tenantOptions.value[0].value });
} else {
message.warning('暂无可用商户');
}
}
reloadDepartments();
});
</script>
<style lang="less" scoped>
.table-toolbar {
margin-bottom: 12px;
}
</style>