cangku/frontend/src/pages/Settings/DepartmentManagement.jsx

311 lines
9.8 KiB
JavaScript
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.

import React, { useEffect, useMemo, useState } from 'react';
import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message } from 'antd';
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, AppstoreOutlined } from '@ant-design/icons';
import { SycmCard } from '../../components/Common';
import {
createDepartment,
deleteDepartment,
getDepartmentPagePermissions,
getDepartments,
updateDepartment,
updateDepartmentPagePermissions,
} from '../../api/admin';
const { Text } = Typography;
const DEPARTMENT_SOURCE_META = {
dingtalk: { color: 'blue', label: '钉钉同步' },
hr: { color: 'cyan', label: 'HR同步' },
local: { color: 'default', label: '本地' },
};
const DepartmentManagement = () => {
const [departments, setDepartments] = useState([]);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [permissionSaving, setPermissionSaving] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [permissionVisible, setPermissionVisible] = useState(false);
const [editingDepartment, setEditingDepartment] = useState(null);
const [permissionDepartment, setPermissionDepartment] = useState(null);
const [pagePermissions, setPagePermissions] = useState([]);
const [form] = Form.useForm();
const departmentMap = useMemo(() => {
return departments.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {});
}, [departments]);
const fetchDepartments = async () => {
try {
setLoading(true);
const data = await getDepartments();
setDepartments(Array.isArray(data) ? data : []);
} catch (error) {
message.error(error.response?.data?.error || '获取部门列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchDepartments();
}, []);
const openCreateModal = () => {
setEditingDepartment(null);
form.resetFields();
form.setFieldsValue({
sort: 0,
status: 'active',
});
setModalVisible(true);
};
const openEditModal = (record) => {
setEditingDepartment(record);
form.setFieldsValue({
name: record.name,
code: record.code,
parentId: record.parentId || undefined,
sort: record.sort,
status: record.status,
});
setModalVisible(true);
};
const handleSave = async () => {
try {
const values = await form.validateFields();
setSaving(true);
if (editingDepartment) {
await updateDepartment(editingDepartment.id, values);
message.success('部门信息已更新');
} else {
await createDepartment(values);
message.success('部门已创建');
}
setModalVisible(false);
fetchDepartments();
} catch (error) {
if (!error?.errorFields) {
message.error(error.response?.data?.error || '保存部门失败');
}
} finally {
setSaving(false);
}
};
const handleDelete = async (id) => {
try {
await deleteDepartment(id);
message.success('部门已删除');
fetchDepartments();
} catch (error) {
message.error(error.response?.data?.error || '删除部门失败');
}
};
const openPermissionModal = async (record) => {
try {
setPermissionDepartment(record);
const data = await getDepartmentPagePermissions(record.id);
setPagePermissions(Array.isArray(data.permissions) ? data.permissions : []);
setPermissionVisible(true);
} catch (error) {
message.error(error.response?.data?.error || '获取部门功能页面失败');
}
};
const handlePermissionToggle = (pageKey, allowed) => {
setPagePermissions((prev) =>
prev.map((item) => (item.pageKey === pageKey ? { ...item, allowed } : item))
);
};
const handleSavePermissions = async () => {
if (!permissionDepartment) {
return;
}
try {
setPermissionSaving(true);
await updateDepartmentPagePermissions(permissionDepartment.id, {
permissions: pagePermissions.map((item) => ({
pageKey: item.pageKey,
allowed: item.allowed,
})),
});
message.success('部门功能页面权限已更新');
setPermissionVisible(false);
} catch (error) {
message.error(error.response?.data?.error || '更新部门功能页面失败');
} finally {
setPermissionSaving(false);
}
};
const columns = [
{
title: '部门名称',
dataIndex: 'name',
width: 220,
},
{
title: '编码',
dataIndex: 'code',
width: 160,
},
{
title: '上级部门',
dataIndex: 'parentId',
width: 180,
render: (value) => (value && departmentMap[value] ? departmentMap[value].name : '顶级部门'),
},
{
title: '来源',
dataIndex: 'source',
width: 120,
render: (value) => {
const sourceMeta = DEPARTMENT_SOURCE_META[value] || { color: 'default', label: value || '未知' };
return <Tag color={sourceMeta.color}>{sourceMeta.label}</Tag>;
},
},
{
title: '状态',
dataIndex: 'status',
width: 120,
render: (value) => <Tag color={value === 'active' ? 'success' : 'default'}>{value === 'active' ? '启用' : '停用'}</Tag>,
},
{
title: '排序',
dataIndex: 'sort',
width: 90,
},
{
title: '操作',
key: 'actions',
width: 260,
render: (_, record) => {
return (
<Space>
<Button type="link" size="small" icon={<AppstoreOutlined />} onClick={() => openPermissionModal(record)}>
功能页面
</Button>
{record.source === 'local' && (
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEditModal(record)}>
编辑
</Button>
)}
{record.source === 'local' && (
<Popconfirm title="确定删除该部门吗?" onConfirm={() => handleDelete(record.id)}>
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
)}
{record.source !== 'local' && <Text type="secondary">同步部门</Text>}
</Space>
);
},
},
];
return (
<div className="settings-tab-page">
<SycmCard bodyStyle={{ padding: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 12 }}>
<Space wrap>
<Button icon={<ReloadOutlined />} onClick={fetchDepartments}>
刷新
</Button>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreateModal}>
新增部门
</Button>
</Space>
</div>
<Table rowKey="id" columns={columns} dataSource={departments} loading={loading} pagination={false} scroll={{ x: 920 }} />
</SycmCard>
<Modal
title={editingDepartment ? '编辑部门' : '新增部门'}
open={modalVisible}
onCancel={() => setModalVisible(false)}
onOk={handleSave}
confirmLoading={saving}
>
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item label="部门名称" name="name" rules={[{ required: true, message: '请输入部门名称' }]}>
<Input />
</Form.Item>
<Form.Item label="部门编码" name="code" rules={[{ required: true, message: '请输入部门编码' }]}>
<Input />
</Form.Item>
<Form.Item label="上级部门" name="parentId">
<Select
allowClear
placeholder="请选择上级部门"
options={departments
.filter((item) => item.source === 'local' && item.id !== editingDepartment?.id)
.map((item) => ({
value: item.id,
label: item.name,
}))}
/>
</Form.Item>
<Form.Item label="排序" name="sort">
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="状态" name="status">
<Select
options={[
{ value: 'active', label: '启用' },
{ value: 'inactive', label: '停用' },
]}
/>
</Form.Item>
</Form>
</Modal>
<Modal
title={permissionDepartment ? `${permissionDepartment.name} - 功能页面权限` : '功能页面权限'}
open={permissionVisible}
onCancel={() => setPermissionVisible(false)}
onOk={handleSavePermissions}
confirmLoading={permissionSaving}
width={640}
>
<div style={{ marginTop: 12 }}>
<Text type="secondary">开启后,该部门下的用户在侧边栏中可以看到对应功能页面;管理员角色不受部门侧限制。</Text>
<div style={{ marginTop: 20, display: 'grid', gap: 12 }}>
{pagePermissions.map((item) => (
<div
key={item.pageKey}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 14px',
borderRadius: 'var(--sycm-radius-lg)',
background: 'var(--sycm-bg-subtle)',
}}
>
<div>
<div style={{ fontWeight: 600 }}>{item.pageName}</div>
<Text type="secondary">{item.path}</Text>
</div>
<Switch checked={Boolean(item.allowed)} onChange={(value) => handlePermissionToggle(item.pageKey, value)} />
</div>
))}
</div>
</div>
</Modal>
</div>
);
};
export default DepartmentManagement;