refactor(ui): 统一页面头部组件并集成字典数据
- 引入统一的 PageHeader 组件替换各页面自定义头部结构 - 集成 useDict 钩子实现状态标签的动态字典映射 - 更新设备、字典、日志、组织、权限、租户等页面的状态渲染逻辑 - 替换硬编码的选择框选项为字典数据驱动 - 优化日志页面的标签页结构支持动态字典配置 - 统一各页面标题区域的样式和布局结构master
parent
351e56a059
commit
1ae81909c2
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import StatCard from "../components/shared/StatCard/StatCard";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -65,13 +66,11 @@ export default function Dashboard() {
|
|||
|
||||
return (
|
||||
<div className="dashboard-page p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('dashboard.title')}</Title>
|
||||
<Text type="secondary">{t('dashboard.subtitle')}</Text>
|
||||
</div>
|
||||
<Button icon={<SyncOutlined aria-hidden="true" />} size="small">{t('common.refresh')}</Button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('dashboard.title')}
|
||||
subtitle={t('dashboard.subtitle')}
|
||||
extra={<Button icon={<SyncOutlined aria-hidden="true" />} size="small">{t('common.refresh')}</Button>}
|
||||
/>
|
||||
|
||||
<Row gutter={[24, 24]}>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { createDevice, deleteDevice, listDevices, updateDevice, listUsers } from "../api";
|
||||
import type { DeviceInfo, SysUser } from "../types";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -25,7 +26,7 @@ import {
|
|||
DesktopOutlined,
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import "./Devices.css";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -37,6 +38,9 @@ export default function Devices() {
|
|||
const [data, setData] = useState<DeviceInfo[]>([]);
|
||||
const [users, setUsers] = useState<SysUser[]>([]);
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
// Search state
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
|
|
@ -165,11 +169,14 @@ export default function Devices() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 100,
|
||||
render: (status: number) => (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{status === 1 ? "启用" : "禁用"}
|
||||
</Tag>
|
||||
),
|
||||
render: (status: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(status));
|
||||
return (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{item ? item.itemLabel : (status === 1 ? "启用" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('devices.updateTime'),
|
||||
|
|
@ -209,17 +216,15 @@ export default function Devices() {
|
|||
|
||||
return (
|
||||
<div className="devices-page p-6">
|
||||
<div className="devices-header flex justify-between items-end mb-6">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('devices.title')}</Title>
|
||||
<Text type="secondary">{t('devices.subtitle')}</Text>
|
||||
</div>
|
||||
{can("device:create") && (
|
||||
<PageHeader
|
||||
title={t('devices.title')}
|
||||
subtitle={t('devices.subtitle')}
|
||||
extra={can("device:create") && (
|
||||
<Button type="primary" icon={<PlusOutlined aria-hidden="true" />} onClick={openCreate}>
|
||||
{t('devices.drawerTitleCreate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
<Card className="devices-table-card shadow-sm">
|
||||
<div className="devices-table-toolbar mb-4">
|
||||
|
|
@ -283,7 +288,7 @@ export default function Devices() {
|
|||
<Input placeholder="例如:会议室 A 转录仪" />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{ value: 1, label: "正常启用" }, { value: 0, label: "禁用接入" }]} />
|
||||
<Select options={statusDict.map(i => ({ value: Number(i.itemValue), label: i.itemLabel }))} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Drawer>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ import {
|
|||
} from "../api";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, BookOutlined, ProfileOutlined } from "@ant-design/icons";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import type { SysDictItem, SysDictType } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
import "./Dictionaries.css";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
|
@ -44,6 +46,9 @@ export default function Dictionaries() {
|
|||
const [loadingTypes, setLoadingTypes] = useState(false);
|
||||
const [loadingItems, setLoadingItems] = useState(false);
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
// Type Drawer
|
||||
const [typeDrawerVisible, setTypeDrawerVisible] = useState(false);
|
||||
const [editingType, setEditingType] = useState<SysDictType | null>(null);
|
||||
|
|
@ -158,12 +163,19 @@ export default function Dictionaries() {
|
|||
|
||||
return (
|
||||
<div className="dictionaries-page p-6">
|
||||
<div className="dictionaries-header mb-6">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('dicts.title')}</Title>
|
||||
<Text type="secondary">{t('dicts.subtitle')}</Text>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('dicts.title')}
|
||||
subtitle={t('dicts.subtitle')}
|
||||
extra={can("sys_dict:type:create") && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined aria-hidden="true" />}
|
||||
onClick={handleAddType}
|
||||
>
|
||||
{t('common.create')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Row gutter={24} style={{ height: 'calc(100vh - 180px)' }}>
|
||||
<Col span={8} style={{ height: '100%' }}>
|
||||
|
|
@ -175,18 +187,6 @@ export default function Dictionaries() {
|
|||
</Space>
|
||||
}
|
||||
className="full-height-card shadow-sm"
|
||||
extra={
|
||||
can("sys_dict:type:create") && (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<PlusOutlined aria-hidden="true" />}
|
||||
onClick={handleAddType}
|
||||
>
|
||||
{t('common.create')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div style={{ height: 'calc(100% - 10px)', overflowY: 'auto' }}>
|
||||
<Table
|
||||
|
|
@ -299,11 +299,14 @@ export default function Dictionaries() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 100,
|
||||
render: (v) => (
|
||||
<Tag color={v === 1 ? "green" : "red"}>
|
||||
{v === 1 ? "启用" : "禁用"}
|
||||
</Tag>
|
||||
)
|
||||
render: (v) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(v));
|
||||
return (
|
||||
<Tag color={v === 1 ? "green" : "red"}>
|
||||
{item ? item.itemLabel : (v === 1 ? "启用" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -412,10 +415,7 @@ export default function Dictionaries() {
|
|||
</Form.Item>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "启用", value: 1 },
|
||||
{ label: "禁用", value: 0 }
|
||||
]}
|
||||
options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('common.remark')} name="remark">
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import { Card, Table, Tabs, Tag, Input, Space, Button, DatePicker, Select, Typog
|
|||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fetchLogs } from "../api";
|
||||
import { SearchOutlined, ReloadOutlined, InfoCircleOutlined, EyeOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { SearchOutlined, ReloadOutlined, InfoCircleOutlined, EyeOutlined, UserOutlined, FileTextOutlined } from "@ant-design/icons";
|
||||
import { SysLog, UserProfile } from "../types";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
const { Text, Title } = Typography;
|
||||
|
|
@ -26,6 +28,10 @@ export default function Logs() {
|
|||
sortOrder: "descend" as any
|
||||
});
|
||||
|
||||
// Dictionaries
|
||||
const { items: logTypeDict } = useDict("sys_log_type");
|
||||
const { items: logStatusDict } = useDict("sys_log_status");
|
||||
|
||||
// Get user profile to check platform admin
|
||||
const userProfile = useMemo(() => {
|
||||
const stored = sessionStorage.getItem("userProfile");
|
||||
|
|
@ -151,11 +157,14 @@ export default function Logs() {
|
|||
dataIndex: "status",
|
||||
key: "status",
|
||||
width: 90,
|
||||
render: (status: number) => (
|
||||
<Tag color={status === 1 ? "green" : "red"} className="m-0">
|
||||
{status === 1 ? "成功" : "失败"}
|
||||
</Tag>
|
||||
)
|
||||
render: (status: number) => {
|
||||
const item = logStatusDict.find(i => i.itemValue === String(status));
|
||||
return (
|
||||
<Tag color={status === 1 ? "green" : "red"} className="m-0">
|
||||
{item ? item.itemLabel : (status === 1 ? "成功" : "失败")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('logs.time'),
|
||||
|
|
@ -196,10 +205,10 @@ export default function Logs() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<Title level={4} className="mb-1">{t('logs.title')}</Title>
|
||||
<Text type="secondary">{t('logs.subtitle')}</Text>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('logs.title')}
|
||||
subtitle={t('logs.subtitle')}
|
||||
/>
|
||||
|
||||
<Card className="mb-4 shadow-sm">
|
||||
<Space wrap size="middle">
|
||||
|
|
@ -217,10 +226,7 @@ export default function Logs() {
|
|||
allowClear
|
||||
value={params.status}
|
||||
onChange={v => setParams({ ...params, status: v })}
|
||||
options={[
|
||||
{ label: "成功", value: 1 },
|
||||
{ label: "失败", value: 0 }
|
||||
]}
|
||||
options={logStatusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))}
|
||||
aria-label={t('common.status')}
|
||||
/>
|
||||
<RangePicker
|
||||
|
|
@ -251,14 +257,30 @@ export default function Logs() {
|
|||
|
||||
<Card className="shadow-sm" styles={{ body: { paddingTop: 0 } }}>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} size="large">
|
||||
<Tabs.TabPane
|
||||
tab={<span><InfoCircleOutlined aria-hidden="true" />{t('logs.opLog')}</span>}
|
||||
key="OPERATION"
|
||||
/>
|
||||
<Tabs.TabPane
|
||||
tab={<span><UserOutlined aria-hidden="true" />{t('logs.loginLog')}</span>}
|
||||
key="LOGIN"
|
||||
/>
|
||||
{logTypeDict.length > 0 ? (
|
||||
logTypeDict.map(item => (
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
{item.itemValue === 'OPERATION' ? <InfoCircleOutlined aria-hidden="true" /> : <UserOutlined aria-hidden="true" />}
|
||||
{item.itemLabel}
|
||||
</span>
|
||||
}
|
||||
key={item.itemValue}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
<Tabs.TabPane
|
||||
tab={<span><InfoCircleOutlined aria-hidden="true" />{t('logs.opLog')}</span>}
|
||||
key="OPERATION"
|
||||
/>
|
||||
<Tabs.TabPane
|
||||
tab={<span><UserOutlined aria-hidden="true" />{t('logs.loginLog')}</span>}
|
||||
key="LOGIN"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Tabs>
|
||||
|
||||
<Table
|
||||
|
|
@ -305,7 +327,7 @@ export default function Logs() {
|
|||
<Descriptions.Item label={t('logs.duration')}>{selectedLog.duration ? `${selectedLog.duration}ms` : "-"}</Descriptions.Item>
|
||||
<Descriptions.Item label={t('common.status')}>
|
||||
<Tag color={selectedLog.status === 1 ? "green" : "red"}>
|
||||
{selectedLog.status === 1 ? "成功" : "失败"}
|
||||
{logStatusDict.find(i => i.itemValue === String(selectedLog.status))?.itemLabel || (selectedLog.status === 1 ? "成功" : "失败")}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t('logs.time')} className="tabular-nums">{selectedLog.createdAt?.replace('T', ' ')}</Descriptions.Item>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { useEffect, useState, useMemo } from "react";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { createOrg, deleteOrg, listOrgs, updateOrg, listTenants } from "../api";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -29,6 +30,7 @@ import {
|
|||
ShopOutlined
|
||||
} from "@ant-design/icons";
|
||||
import type { SysOrg, SysTenant, OrgNode } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -59,6 +61,10 @@ function buildOrgTree(list: SysOrg[]): OrgNode[] {
|
|||
export default function Orgs() {
|
||||
const { t } = useTranslation();
|
||||
const { can } = usePermission();
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [data, setData] = useState<SysOrg[]>([]);
|
||||
|
|
@ -200,7 +206,14 @@ export default function Orgs() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 100,
|
||||
render: (s: number) => <Tag color={s === 1 ? "green" : "red"}>{s === 1 ? "启用" : "禁用"}</Tag>
|
||||
render: (s: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(s));
|
||||
return (
|
||||
<Tag color={s === 1 ? "green" : "red"}>
|
||||
{item ? item.itemLabel : (s === 1 ? "启用" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -226,17 +239,15 @@ export default function Orgs() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('orgs.title')}</Title>
|
||||
<Text type="secondary">{t('orgs.subtitle')}</Text>
|
||||
</div>
|
||||
{can("sys:org:create") && (
|
||||
<PageHeader
|
||||
title={t('orgs.title')}
|
||||
subtitle={t('orgs.subtitle')}
|
||||
extra={can("sys:org:create") && (
|
||||
<Button type="primary" icon={<PlusOutlined aria-hidden="true" />} onClick={() => openCreate()}>
|
||||
{t('orgs.createRoot')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
{isPlatformMode && (
|
||||
<Card className="shadow-sm mb-4">
|
||||
|
|
@ -332,7 +343,7 @@ export default function Orgs() {
|
|||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{ label: "启用", value: 1 }, { label: "禁用", value: 0 }]} />
|
||||
<Select options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { createPermission, deletePermission, listMyPermissions, updatePermission } from "../api";
|
||||
import type { SysPermission } from "../types";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
CheckSquareOutlined,
|
||||
InfoCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -75,6 +77,12 @@ export default function Permissions() {
|
|||
const { can } = usePermission();
|
||||
const level = Form.useWatch("level", form);
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
const { items: typeDict } = useDict("sys_permission_type");
|
||||
const { items: visibleDict } = useDict("sys_common_visibility");
|
||||
const { items: levelDict } = useDict("sys_permission_level");
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
|
|
@ -206,11 +214,14 @@ export default function Permissions() {
|
|||
title: t('permissions.permType'),
|
||||
dataIndex: "permType",
|
||||
width: 90,
|
||||
render: (type: string) => (
|
||||
<Tag color={type === 'menu' ? 'processing' : 'warning'}>
|
||||
{type === 'menu' ? '菜单' : '按钮'}
|
||||
</Tag>
|
||||
)
|
||||
render: (type: string) => {
|
||||
const item = typeDict.find(i => i.itemValue === type);
|
||||
return (
|
||||
<Tag color={type === 'menu' ? 'processing' : 'warning'}>
|
||||
{item ? item.itemLabel : type}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('permissions.sort'),
|
||||
|
|
@ -233,13 +244,19 @@ export default function Permissions() {
|
|||
title: t('permissions.visible'),
|
||||
dataIndex: "isVisible",
|
||||
width: 80,
|
||||
render: (v: number) => (v === 1 ? <Tag color="blue">可见</Tag> : <Tag>隐藏</Tag>)
|
||||
render: (v: number) => {
|
||||
const item = visibleDict.find(i => i.itemValue === String(v));
|
||||
return (v === 1 ? <Tag color="blue">{item?.itemLabel || '可见'}</Tag> : <Tag>{item?.itemLabel || '隐藏'}</Tag>);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 80,
|
||||
render: (v: number) => (v === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>)
|
||||
render: (v: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(v));
|
||||
return (v === 1 ? <Tag color="green">{item?.itemLabel || '启用'}</Tag> : <Tag color="red">{item?.itemLabel || '禁用'}</Tag>);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -284,12 +301,10 @@ export default function Permissions() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('permissions.title')}</Title>
|
||||
<Text type="secondary">{t('permissions.subtitle')}</Text>
|
||||
</div>
|
||||
{can("sys:permission:create") && (
|
||||
<PageHeader
|
||||
title={t('permissions.title')}
|
||||
subtitle={t('permissions.subtitle')}
|
||||
extra={can("sys:permission:create") && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined aria-hidden="true" />}
|
||||
|
|
@ -298,7 +313,7 @@ export default function Permissions() {
|
|||
{t('common.create')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
<Card className="mb-4 shadow-sm">
|
||||
<Space wrap size="middle">
|
||||
|
|
@ -324,10 +339,7 @@ export default function Permissions() {
|
|||
allowClear
|
||||
value={query.permType || undefined}
|
||||
onChange={(v) => setQuery({ ...query, permType: v || "" })}
|
||||
options={[
|
||||
{ value: "menu", label: "菜单" },
|
||||
{ value: "button", label: "按钮" }
|
||||
]}
|
||||
options={typeDict.map(i => ({ value: i.itemValue, label: i.itemLabel }))}
|
||||
style={{ width: 120 }}
|
||||
aria-label={t('permissions.permType')}
|
||||
/>
|
||||
|
|
@ -395,16 +407,18 @@ export default function Permissions() {
|
|||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('permissions.level')} name="level" rules={[{ required: true }]}>
|
||||
<Select aria-label={t('permissions.level')}>
|
||||
<Select.Option value={1}>一级入口</Select.Option>
|
||||
<Select.Option value={2}>二级子项</Select.Option>
|
||||
<Select.Option value={3}>三级按钮</Select.Option>
|
||||
</Select>
|
||||
<Select
|
||||
options={levelDict.map(i => ({ value: Number(i.itemValue), label: i.itemLabel }))}
|
||||
aria-label={t('permissions.level')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('permissions.permType')} name="permType" rules={[{ required: true }]}>
|
||||
<Select options={[{ value: "menu", label: "菜单" }, { value: "button", label: "按钮" }]} aria-label={t('permissions.permType')} />
|
||||
<Select
|
||||
options={typeDict.map(i => ({ value: i.itemValue, label: i.itemLabel }))}
|
||||
aria-label={t('permissions.permType')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
@ -484,12 +498,12 @@ export default function Permissions() {
|
|||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('permissions.isVisible')} name="isVisible" initialValue={1}>
|
||||
<Select options={[{ value: 1, label: "显示" }, { value: 0, label: "隐藏" }]} />
|
||||
<Select options={visibleDict.map(i => ({ value: Number(i.itemValue), label: i.itemLabel }))} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{ value: 1, label: "启用" }, { value: 0, label: "禁用" }]} />
|
||||
<Select options={statusDict.map(i => ({ value: Number(i.itemValue), label: i.itemLabel }))} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
FileTextOutlined
|
||||
} from "@ant-design/icons";
|
||||
import type { SysPlatformConfig } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -85,20 +86,20 @@ export default function PlatformSettings() {
|
|||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
<div className="flex justify-between items-end mb-6">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('platformSettings.title')}</Title>
|
||||
<Text type="secondary">{t('platformSettings.subtitle')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
loading={saving}
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('platformSettings.title')}
|
||||
subtitle={t('platformSettings.subtitle')}
|
||||
extra={(
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
loading={saving}
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { listPermissions, listRolePermissions, listRoles, saveRolePermissions } from "../api";
|
||||
import { SearchOutlined, SafetyCertificateOutlined, SaveOutlined, KeyOutlined, ClusterOutlined } from "@ant-design/icons";
|
||||
import type { SysPermission, SysRole } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -178,21 +179,21 @@ export default function RolePermissionBinding() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('rolePerm.title')}</Title>
|
||||
<Text type="secondary">{t('rolePerm.subtitle')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={!selectedRoleId || (selectedRole?.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
|
||||
>
|
||||
{saving ? t('common.loading') : t('rolePerm.savePolicy')}
|
||||
</Button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('rolePerm.title')}
|
||||
subtitle={t('rolePerm.subtitle')}
|
||||
extra={(
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={!selectedRoleId || (selectedRole?.roleCode === 'TENANT_ADMIN' && !isPlatformMode)}
|
||||
>
|
||||
{saving ? t('common.loading') : t('rolePerm.savePolicy')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Row gutter={24} style={{ height: 'calc(100vh - 180px)' }}>
|
||||
<Col xs={24} lg={10} style={{ height: '100%' }}>
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ import {
|
|||
unbindUserFromRole,
|
||||
listUsers
|
||||
} from "../api";
|
||||
import type {SysPermission, SysRole, SysTenant, SysUser} from "../types";
|
||||
import { SysPermission, SysRole, SysTenant, SysUser } from "../types";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
EditOutlined,
|
||||
PlusOutlined,
|
||||
|
|
@ -47,6 +48,7 @@ import {
|
|||
SaveOutlined,
|
||||
UserAddOutlined
|
||||
} from "@ant-design/icons";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
import "./Roles.css";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
|
@ -106,6 +108,9 @@ export default function Roles() {
|
|||
const [permissions, setPermissions] = useState<SysPermission[]>([]);
|
||||
const [selectedRole, setSelectedRole] = useState<SysRole | null>(null);
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
// Platform admin check
|
||||
const isPlatformMode = useMemo(() => {
|
||||
const profileStr = sessionStorage.getItem("userProfile");
|
||||
|
|
@ -364,22 +369,25 @@ export default function Roles() {
|
|||
|
||||
return (
|
||||
<div className="roles-page-v2 p-6">
|
||||
<PageHeader
|
||||
title={t('roles.title')}
|
||||
subtitle={t('roles.subtitle')}
|
||||
extra={can("sys:role:create") && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined aria-hidden="true" />}
|
||||
onClick={openCreate}
|
||||
>
|
||||
{t('common.create')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<Row gutter={24} style={{ height: 'calc(100vh - 180px)' }}>
|
||||
{/* Left: Role List */}
|
||||
<Col span={8} style={{ height: '100%' }}>
|
||||
<Card
|
||||
title={t('roles.title')}
|
||||
title="角色列表"
|
||||
className="full-height-card shadow-sm"
|
||||
extra={can("sys:role:create") && (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon={<PlusOutlined aria-hidden="true" />}
|
||||
onClick={openCreate}
|
||||
>
|
||||
{t('common.create')}
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<div className="mb-4 flex gap-2">
|
||||
{isPlatformMode && (
|
||||
|
|
@ -540,7 +548,14 @@ export default function Roles() {
|
|||
title: t('common.status'),
|
||||
dataIndex: 'status',
|
||||
width: 80,
|
||||
render: s => <Tag color={s === 1 ? 'green' : 'red'}>{s === 1 ? '正常' : '禁用'}</Tag>
|
||||
render: (s: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(s));
|
||||
return (
|
||||
<Tag color={s === 1 ? 'green' : 'red'}>
|
||||
{item ? item.itemLabel : (s === 1 ? '正常' : '禁用')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -648,7 +663,7 @@ export default function Roles() {
|
|||
<Input placeholder={t('roles.roleCode')} disabled={!!editing} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{label: '启用', value: 1}, {label: '禁用', value: 0}]} />
|
||||
<Select options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('common.remark')} name="remark">
|
||||
<Input.TextArea rows={3} />
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
deleteParam
|
||||
} from "../api";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -35,6 +36,7 @@ import {
|
|||
InfoCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import type { SysParamVO, SysParamQuery } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
import "./SysParams.css";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
|
@ -42,6 +44,10 @@ const { Title, Text } = Typography;
|
|||
export default function SysParams() {
|
||||
const { t } = useTranslation();
|
||||
const { can } = usePermission();
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
const { items: paramTypeDict } = useDict("sys_param_type");
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
|
@ -176,11 +182,14 @@ export default function SysParams() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 80,
|
||||
render: (status: number) => (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{status === 1 ? "正常" : "禁用"}
|
||||
</Tag>
|
||||
),
|
||||
render: (status: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(status));
|
||||
return (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{item ? item.itemLabel : (status === 1 ? "正常" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -208,17 +217,15 @@ export default function SysParams() {
|
|||
|
||||
return (
|
||||
<div className="sys-params-page p-6">
|
||||
<div className="sys-params-header mb-6">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('sysParams.title')}</Title>
|
||||
<Text type="secondary">{t('sysParams.subtitle')}</Text>
|
||||
</div>
|
||||
{can("sys_param:create") && (
|
||||
<PageHeader
|
||||
title={t('sysParams.title')}
|
||||
subtitle={t('sysParams.subtitle')}
|
||||
extra={can("sys_param:create") && (
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||
{t('common.create')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
<Card className="sys-params-table-card shadow-sm mb-4">
|
||||
<Form
|
||||
|
|
@ -239,12 +246,7 @@ export default function SysParams() {
|
|||
placeholder={t('sysParams.paramType')}
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
options={[
|
||||
{ label: 'String', value: 'String' },
|
||||
{ label: 'Number', value: 'Number' },
|
||||
{ label: 'Boolean', value: 'Boolean' },
|
||||
{ label: 'JSON', value: 'JSON' }
|
||||
]}
|
||||
options={paramTypeDict.map(i => ({ label: i.itemLabel, value: i.itemValue }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
|
|
@ -321,12 +323,7 @@ export default function SysParams() {
|
|||
rules={[{ required: true, message: t('sysParams.paramType') }]}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'String', value: 'String' },
|
||||
{ label: 'Number', value: 'Number' },
|
||||
{ label: 'Boolean', value: 'Boolean' },
|
||||
{ label: 'JSON', value: 'JSON' }
|
||||
]}
|
||||
options={paramTypeDict.map(i => ({ label: i.itemLabel, value: i.itemValue }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
@ -337,10 +334,7 @@ export default function SysParams() {
|
|||
initialValue={1}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 }
|
||||
]}
|
||||
options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { useEffect, useState } from "react";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { createTenant, deleteTenant, listTenants, updateTenant } from "../api";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -31,6 +32,7 @@ import {
|
|||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import type { SysTenant } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
|
@ -38,6 +40,10 @@ const { Title, Text } = Typography;
|
|||
export default function Tenants() {
|
||||
const { t } = useTranslation();
|
||||
const { can } = usePermission();
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [data, setData] = useState<SysTenant[]>([]);
|
||||
|
|
@ -165,11 +171,14 @@ export default function Tenants() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 100,
|
||||
render: (status: number) => (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{status === 1 ? "正常" : "禁用"}
|
||||
</Tag>
|
||||
),
|
||||
render: (status: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(status));
|
||||
return (
|
||||
<Tag color={status === 1 ? "green" : "red"}>
|
||||
{item ? item.itemLabel : (status === 1 ? "正常" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('tenants.expireTime'),
|
||||
|
|
@ -209,17 +218,15 @@ export default function Tenants() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('tenants.title')}</Title>
|
||||
<Text type="secondary">{t('tenants.subtitle')}</Text>
|
||||
</div>
|
||||
{can("sys_tenant:create") && (
|
||||
<PageHeader
|
||||
title={t('tenants.title')}
|
||||
subtitle={t('tenants.subtitle')}
|
||||
extra={can("sys_tenant:create") && (
|
||||
<Button type="primary" icon={<PlusOutlined aria-hidden="true" />} onClick={openCreate}>
|
||||
{t('tenants.drawerTitleCreate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
<Card className="shadow-sm mb-4">
|
||||
<Space wrap size="middle">
|
||||
|
|
@ -311,7 +318,7 @@ export default function Tenants() {
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{ label: "正常启用", value: 1 }, { label: "禁止访问", value: 0 }]} />
|
||||
<Select options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t('common.remark')} name="remark">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { listRoles, listUserRoles, listUsers, saveUserRoles } from "../api";
|
||||
import { SearchOutlined, UserOutlined, SaveOutlined, TeamOutlined } from "@ant-design/icons";
|
||||
import type { SysRole, SysUser } from "../types";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -107,21 +108,21 @@ export default function UserRoleBinding() {
|
|||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6 flex justify-between items-end">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('userRole.title')}</Title>
|
||||
<Text type="secondary">{t('userRole.subtitle')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={!selectedUserId}
|
||||
>
|
||||
{saving ? t('common.loading') : t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
<PageHeader
|
||||
title={t('userRole.title')}
|
||||
subtitle={t('userRole.subtitle')}
|
||||
extra={(
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined aria-hidden="true" />}
|
||||
onClick={handleSave}
|
||||
loading={saving}
|
||||
disabled={!selectedUserId}
|
||||
>
|
||||
{saving ? t('common.loading') : t('common.save')}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Row gutter={24} style={{ height: 'calc(100vh - 180px)' }}>
|
||||
<Col xs={24} lg={12} style={{ height: '100%' }}>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
listOrgs
|
||||
} from "../api";
|
||||
import { usePermission } from "../hooks/usePermission";
|
||||
import { useDict } from "../hooks/useDict";
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -41,8 +42,7 @@ import {
|
|||
ReloadOutlined,
|
||||
MinusCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import type { SysRole, SysUser, SysTenant, SysOrg } from "../types";
|
||||
import "./Users.css";
|
||||
import PageHeader from "../components/shared/PageHeader";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
|
|
@ -108,6 +108,9 @@ export default function Users() {
|
|||
const [roles, setRoles] = useState<SysRole[]>([]);
|
||||
const [tenants, setTenants] = useState<SysTenant[]>([]);
|
||||
const [orgs, setOrgs] = useState<SysOrg[]>([]);
|
||||
|
||||
// Dictionaries
|
||||
const { items: statusDict } = useDict("sys_common_status");
|
||||
|
||||
// Platform admin check
|
||||
const isPlatformMode = useMemo(() => {
|
||||
|
|
@ -373,11 +376,14 @@ export default function Users() {
|
|||
title: t('common.status'),
|
||||
dataIndex: "status",
|
||||
width: 80,
|
||||
render: (status: number) => (
|
||||
<Tag color={status === 1 ? "green" : "red"} className="m-0">
|
||||
{status === 1 ? "正常" : "禁用"}
|
||||
</Tag>
|
||||
),
|
||||
render: (status: number) => {
|
||||
const item = statusDict.find(i => i.itemValue === String(status));
|
||||
return (
|
||||
<Tag color={status === 1 ? "green" : "red"} className="m-0">
|
||||
{item ? item.itemLabel : (status === 1 ? "正常" : "禁用")}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.action'),
|
||||
|
|
@ -406,17 +412,15 @@ export default function Users() {
|
|||
|
||||
return (
|
||||
<div className="users-page p-6">
|
||||
<div className="users-header flex justify-between items-end mb-6">
|
||||
<div>
|
||||
<Title level={4} className="mb-1">{t('users.title')}</Title>
|
||||
<Text type="secondary">{t('users.subtitle')}</Text>
|
||||
</div>
|
||||
{can("sys:user:create") && (
|
||||
<PageHeader
|
||||
title={t('users.title')}
|
||||
subtitle={t('users.subtitle')}
|
||||
extra={can("sys:user:create") && (
|
||||
<Button type="primary" icon={<PlusOutlined aria-hidden="true" />} onClick={openCreate}>
|
||||
{t('users.drawerTitleCreate')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
/>
|
||||
|
||||
<Card className="users-table-card shadow-sm">
|
||||
<div className="users-table-toolbar mb-4">
|
||||
|
|
@ -536,7 +540,7 @@ export default function Users() {
|
|||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item label={t('common.status')} name="status" initialValue={1}>
|
||||
<Select options={[{ label: "启用", value: 1 }, { label: "禁用", value: 0 }]} />
|
||||
<Select options={statusDict.map(i => ({ label: i.itemLabel, value: Number(i.itemValue) }))} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{isPlatformMode && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue