调整数据授权管理的前端优化
parent
aa632ec6ec
commit
09098a180c
|
|
@ -1,5 +1,6 @@
|
||||||
.user-data-scope-page {
|
.user-data-scope-page {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-data-scope-overview {
|
.user-data-scope-overview {
|
||||||
|
|
@ -38,7 +39,45 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-data-scope-table-card {
|
.user-data-scope-table-card {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-data-scope-table {
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-spin-nested-loading,
|
||||||
|
.ant-spin-container {
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-container {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-pagination {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 12px 16px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import dayjs from "dayjs";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined, TeamOutlined } from "@ant-design/icons";
|
import { DeleteOutlined, EditOutlined, PlusOutlined, SearchOutlined, TeamOutlined } from "@ant-design/icons";
|
||||||
import PageHeader from "@/components/shared/PageHeader";
|
import PageHeader from "@/components/shared/PageHeader";
|
||||||
import { deleteUserDataScopeGrant, listTenants, listUserDataScopeGrants, listUserDataScopeUsers, saveUserDataScopeAssignment } from "@/api";
|
import { deleteUserDataScopeGrant, fetchUsersByRoleId, listRoles, listTenants, listUserDataScopeGrants, listUserDataScopeUsers, saveUserDataScopeAssignment } from "@/api";
|
||||||
import { usePermission } from "@/hooks/usePermission";
|
import { usePermission } from "@/hooks/usePermission";
|
||||||
import { getStandardPagination } from "@/utils/pagination";
|
import { getStandardPagination } from "@/utils/pagination";
|
||||||
import type { SysTenant, UserDataScopeGrant, UserDataScopeUser } from "@/types";
|
import type { SysRole, SysTenant, UserDataScopeGrant, UserDataScopeUser } from "@/types";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
@ -141,7 +141,9 @@ export default function UserDataScopePage() {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [roleUserLoading, setRoleUserLoading] = useState(false);
|
||||||
const [users, setUsers] = useState<UserDataScopeUser[]>([]);
|
const [users, setUsers] = useState<UserDataScopeUser[]>([]);
|
||||||
|
const [roles, setRoles] = useState<SysRole[]>([]);
|
||||||
const [grants, setGrants] = useState<UserDataScopeGrant[]>([]);
|
const [grants, setGrants] = useState<UserDataScopeGrant[]>([]);
|
||||||
const [tenants, setTenants] = useState<SysTenant[]>([]);
|
const [tenants, setTenants] = useState<SysTenant[]>([]);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
|
@ -170,6 +172,15 @@ export default function UserDataScopePage() {
|
||||||
setUsers(list || []);
|
setUsers(list || []);
|
||||||
}, [selectedTenantId]);
|
}, [selectedTenantId]);
|
||||||
|
|
||||||
|
const loadRoles = useCallback(async () => {
|
||||||
|
if (!selectedTenantId) {
|
||||||
|
setRoles([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const list = await listRoles(selectedTenantId);
|
||||||
|
setRoles(list || []);
|
||||||
|
}, [selectedTenantId]);
|
||||||
|
|
||||||
const loadGrants = useCallback(async () => {
|
const loadGrants = useCallback(async () => {
|
||||||
if (!selectedTenantId) {
|
if (!selectedTenantId) {
|
||||||
setGrants([]);
|
setGrants([]);
|
||||||
|
|
@ -182,11 +193,11 @@ export default function UserDataScopePage() {
|
||||||
const loadPageData = useCallback(async () => {
|
const loadPageData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await Promise.all([loadTenants(), loadUsers(), loadGrants()]);
|
await Promise.all([loadTenants(), loadUsers(), loadRoles(), loadGrants()]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [loadTenants, loadUsers, loadGrants]);
|
}, [loadTenants, loadUsers, loadRoles, loadGrants]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPageData();
|
loadPageData();
|
||||||
|
|
@ -244,12 +255,28 @@ export default function UserDataScopePage() {
|
||||||
[selectedDrawerViewerId, userOptions]
|
[selectedDrawerViewerId, userOptions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const roleOptions = useMemo(() => roles.map((role) => {
|
||||||
|
const label = role.roleName || role.roleCode || String(role.roleId);
|
||||||
|
return {
|
||||||
|
label: (
|
||||||
|
<Space direction="vertical" size={0}>
|
||||||
|
<span>{label}</span>
|
||||||
|
{role.roleCode && role.roleCode !== label && <Text type="secondary" style={{ fontSize: 12 }}>{role.roleCode}</Text>}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
displayLabel: label,
|
||||||
|
value: role.roleId,
|
||||||
|
searchText: `${role.roleName || ""} ${role.roleCode || ""}`
|
||||||
|
};
|
||||||
|
}), [roles]);
|
||||||
|
|
||||||
const openCreate = () => {
|
const openCreate = () => {
|
||||||
setEditingGrantGroup(null);
|
setEditingGrantGroup(null);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
resourceTypes: ["OPPORTUNITY"],
|
resourceTypes: ["OPPORTUNITY"],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
roleIds: [],
|
||||||
ownerUserIds: []
|
ownerUserIds: []
|
||||||
});
|
});
|
||||||
setDrawerOpen(true);
|
setDrawerOpen(true);
|
||||||
|
|
@ -261,6 +288,7 @@ export default function UserDataScopePage() {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
viewerUserId: record.viewerUserId,
|
viewerUserId: record.viewerUserId,
|
||||||
resourceTypes: record.resourceTypes,
|
resourceTypes: record.resourceTypes,
|
||||||
|
roleIds: [],
|
||||||
ownerUserIds: record.ownerUsers.map((owner) => owner.userId),
|
ownerUserIds: record.ownerUsers.map((owner) => owner.userId),
|
||||||
enabled: record.enabled,
|
enabled: record.enabled,
|
||||||
expireAt: record.expireAt ? dayjs(record.expireAt) : undefined,
|
expireAt: record.expireAt ? dayjs(record.expireAt) : undefined,
|
||||||
|
|
@ -274,6 +302,27 @@ export default function UserDataScopePage() {
|
||||||
setEditingGrantGroup(null);
|
setEditingGrantGroup(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRoleSelectChange = async (roleIds: number[]) => {
|
||||||
|
form.setFieldValue("roleIds", roleIds);
|
||||||
|
if (!roleIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedViewerUserId = form.getFieldValue("viewerUserId");
|
||||||
|
const selectedOwnerIds = new Set<number>(form.getFieldValue("ownerUserIds") || []);
|
||||||
|
setRoleUserLoading(true);
|
||||||
|
try {
|
||||||
|
const roleUsers = await Promise.all(roleIds.map((roleId) => fetchUsersByRoleId(roleId)));
|
||||||
|
roleUsers.flat().forEach((user) => {
|
||||||
|
if (user?.userId && user.userId !== selectedViewerUserId) {
|
||||||
|
selectedOwnerIds.add(user.userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
form.setFieldValue("ownerUserIds", Array.from(selectedOwnerIds));
|
||||||
|
} finally {
|
||||||
|
setRoleUserLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!selectedTenantId) {
|
if (!selectedTenantId) {
|
||||||
message.warning("请先选择租户");
|
message.warning("请先选择租户");
|
||||||
|
|
@ -469,12 +518,13 @@ export default function UserDataScopePage() {
|
||||||
|
|
||||||
<Card className="app-page__content-card user-data-scope-table-card" styles={{ body: { padding: 0 } }}>
|
<Card className="app-page__content-card user-data-scope-table-card" styles={{ body: { padding: 0 } }}>
|
||||||
<Table
|
<Table
|
||||||
|
className="user-data-scope-table"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={pagedGrantGroups}
|
dataSource={pagedGrantGroups}
|
||||||
size="middle"
|
size="middle"
|
||||||
scroll={{ y: "calc(100vh - 390px)", x: 980 }}
|
scroll={{ y: "max(260px, calc(100vh - 470px))", x: 980 }}
|
||||||
pagination={getStandardPagination(
|
pagination={getStandardPagination(
|
||||||
filteredGrantGroups.length,
|
filteredGrantGroups.length,
|
||||||
query.current,
|
query.current,
|
||||||
|
|
@ -506,6 +556,20 @@ export default function UserDataScopePage() {
|
||||||
<Form.Item label="资源范围" name="resourceTypes" rules={[{ required: true, message: "请选择资源范围" }]}>
|
<Form.Item label="资源范围" name="resourceTypes" rules={[{ required: true, message: "请选择资源范围" }]}>
|
||||||
<Select mode="multiple" allowClear options={resourceOptions} maxTagCount="responsive" />
|
<Select mode="multiple" allowClear options={resourceOptions} maxTagCount="responsive" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label="按角色添加" name="roleIds">
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
showSearch
|
||||||
|
allowClear
|
||||||
|
placeholder="选择角色后自动加入该角色下人员"
|
||||||
|
options={roleOptions}
|
||||||
|
loading={roleUserLoading}
|
||||||
|
optionFilterProp="searchText"
|
||||||
|
optionLabelProp="displayLabel"
|
||||||
|
maxTagCount="responsive"
|
||||||
|
onChange={handleRoleSelectChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label="可见人员" name="ownerUserIds" rules={[{ required: true, message: "请选择可见人员" }]}>
|
<Form.Item label="可见人员" name="ownerUserIds" rules={[{ required: true, message: "请选择可见人员" }]}>
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue