refactor: 更新用户页面组件和分页逻辑
- 优化 `index.tsx` 中的导入和表单字段 - 使用 `Table` 组件替换 `ListTable` 和 `AppPagination` - 添加密码确认字段及其验证规则 - 调整工具栏和表格布局以提高一致性dev_na
parent
900f092d5e
commit
ce4743c5ea
|
|
@ -1,4 +1,4 @@
|
|||
import { Avatar, Button, Card, Col, Drawer, Form, Input, Popconfirm, Row, Select, Space, Switch, Tag, TreeSelect, Typography, Upload, message } from "antd";
|
||||
import { Avatar, Button, Card, Col, Drawer, Form, Input, Popconfirm, Row, Select, Space, Switch, Table, Tag, TreeSelect, Typography, Upload, message } from "antd";
|
||||
import type { DefaultOptionType } from "antd/es/select";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
|
@ -7,9 +7,8 @@ import { createUser, deleteUser, getUserDetail, listOrgs, listRoles, listTenants
|
|||
import { useDict } from "@/hooks/useDict";
|
||||
import { usePermission } from "@/hooks/usePermission";
|
||||
import PageHeader from "@/components/shared/PageHeader";
|
||||
import ListTable from "@/components/shared/ListTable/ListTable";
|
||||
import AppPagination from "@/components/shared/AppPagination";
|
||||
import { LOGIN_NAME_PATTERN, sanitizeLoginName } from "@/utils/loginName";
|
||||
import { getStandardPagination } from "@/utils/pagination";
|
||||
import type { SysOrg, SysRole, SysTenant, SysUser } from "@/types";
|
||||
import "./index.less";
|
||||
|
||||
|
|
@ -102,6 +101,7 @@ export default function Users() {
|
|||
const activeTenantId = useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []);
|
||||
const selectedTenantId = Form.useWatch("tenantId", form);
|
||||
const memberships = (Form.useWatch("memberships", form) || []) as Membership[];
|
||||
const passwordValue = Form.useWatch("password", form);
|
||||
|
||||
const tenantMap = useMemo(() => {
|
||||
const map: Record<number, string> = {};
|
||||
|
|
@ -216,6 +216,7 @@ export default function Users() {
|
|||
...detail,
|
||||
roleIds: roleIds || [],
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
tenantId: (detail as any).tenantId || detail.memberships?.[0]?.tenantId,
|
||||
orgId: (detail as any).orgId || detail.memberships?.[0]?.orgId,
|
||||
memberships: detail.memberships || []
|
||||
|
|
@ -304,24 +305,24 @@ export default function Users() {
|
|||
},
|
||||
...(isPlatformMode
|
||||
? [{
|
||||
title: t("users.tenant"),
|
||||
key: "tenant",
|
||||
render: (_: any, record: SysUser) => {
|
||||
if (record.memberships && record.memberships.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{record.memberships.slice(0, 2).map((membership: any) => (
|
||||
<Tag key={membership.tenantId} color="blue" style={{ margin: 0, padding: "0 4px", fontSize: 11 }}>
|
||||
{tenantMap[membership.tenantId] || `Tenant ${membership.tenantId}`}
|
||||
</Tag>
|
||||
))}
|
||||
{record.memberships.length > 2 && <Text type="secondary" style={{ fontSize: 11 }}>+{record.memberships.length - 2} more</Text>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <Text type="secondary">{t("usersExt.noTenant")}</Text>;
|
||||
title: t("users.tenant"),
|
||||
key: "tenant",
|
||||
render: (_: any, record: SysUser) => {
|
||||
if (record.memberships && record.memberships.length > 0) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{record.memberships.slice(0, 2).map((membership: any) => (
|
||||
<Tag key={membership.tenantId} color="blue" style={{ margin: 0, padding: "0 4px", fontSize: 11 }}>
|
||||
{tenantMap[membership.tenantId] || `Tenant ${membership.tenantId}`}
|
||||
</Tag>
|
||||
))}
|
||||
{record.memberships.length > 2 && <Text type="secondary" style={{ fontSize: 11 }}>+{record.memberships.length - 2} more</Text>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}]
|
||||
return <Text type="secondary">{t("usersExt.noTenant")}</Text>;
|
||||
}
|
||||
}]
|
||||
: []),
|
||||
{
|
||||
title: t("users.orgNode"),
|
||||
|
|
@ -385,10 +386,10 @@ export default function Users() {
|
|||
<div className="users-table-toolbar">
|
||||
<Space size="middle" wrap className="app-page__toolbar" style={{ justifyContent: "space-between", width: "100%" }}>
|
||||
<Space size="middle" wrap className="app-page__toolbar">
|
||||
{isPlatformMode && <Select placeholder={t("users.tenantFilter")} style={{ width: 200 }} allowClear value={filterTenantId} onChange={setFilterTenantId} options={tenants.map((tenant) => ({ label: tenant.tenantName, value: tenant.id }))} suffixIcon={<ShopOutlined aria-hidden="true" />} />}
|
||||
<Input placeholder={t("users.searchPlaceholder")} prefix={<SearchOutlined aria-hidden="true" />} className="users-search-input" style={{ width: 300 }} value={searchText} onChange={(event) => { setSearchText(event.target.value); setCurrent(1); }} allowClear aria-label={t("common.search")} />
|
||||
<Button type="primary" icon={<SearchOutlined aria-hidden="true" />} onClick={handleSearch}>{t("common.search")}</Button>
|
||||
<Button onClick={handleResetSearch}>{t("common.reset")}</Button>
|
||||
{isPlatformMode && <Select placeholder={t("users.tenantFilter")} style={{ width: 200 }} allowClear value={filterTenantId} onChange={setFilterTenantId} options={tenants.map((tenant) => ({ label: tenant.tenantName, value: tenant.id }))} suffixIcon={<ShopOutlined aria-hidden="true" />} />}
|
||||
<Input placeholder={t("users.searchPlaceholder")} prefix={<SearchOutlined aria-hidden="true" />} className="users-search-input" style={{ width: 300 }} value={searchText} onChange={(event) => { setSearchText(event.target.value); setCurrent(1); }} allowClear aria-label={t("common.search")} />
|
||||
<Button type="primary" icon={<SearchOutlined aria-hidden="true" />} onClick={handleSearch}>{t("common.search")}</Button>
|
||||
<Button onClick={handleResetSearch}>{t("common.reset")}</Button>
|
||||
</Space>
|
||||
{can("sys:user:create") && <Button type="primary" icon={<PlusOutlined aria-hidden="true" />} onClick={openCreate}>{t("common.create")}</Button>}
|
||||
</Space>
|
||||
|
|
@ -396,18 +397,9 @@ export default function Users() {
|
|||
</Card>
|
||||
|
||||
<Card className="app-page__content-card flex-1 flex flex-col overflow-hidden" styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}>
|
||||
<div className="app-page__table-wrap" style={{ flex: 1, minHeight: 0, overflow: "auto", padding: "0 24px" }}>
|
||||
<ListTable rowKey="userId" columns={columns} dataSource={filteredData} loading={loading} scroll={{ y: "calc(100vh - 420px)" }} pagination={false} />
|
||||
<div className="flex-1 min-h-0 h-full">
|
||||
<Table rowKey="userId" columns={columns} dataSource={filteredData} loading={loading} size="middle" scroll={{ y: "calc(100vh - 420px)" }} pagination={getStandardPagination(filteredData.length, current, pageSize, (page, size) => { setCurrent(page); setPageSize(size); })} />
|
||||
</div>
|
||||
<AppPagination
|
||||
current={current}
|
||||
pageSize={pageSize}
|
||||
total={filteredData.length}
|
||||
onChange={(page, size) => {
|
||||
setCurrent(page);
|
||||
setPageSize(size);
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Drawer title={<div className="user-drawer-title"><UserOutlined className="mr-2" aria-hidden="true" />{editing ? t("users.drawerTitleEdit") : t("users.drawerTitleCreate")}</div>} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={520} destroyOnClose footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>{t("common.cancel")}</Button><Button type="primary" loading={saving} onClick={submit}>{t("common.save")}</Button></div>}>
|
||||
|
|
@ -425,7 +417,30 @@ export default function Users() {
|
|||
<Upload accept="image/*" showUploadList={false} beforeUpload={handleAvatarUpload}>
|
||||
<Button icon={<UploadOutlined />} loading={avatarUploading} style={{ marginBottom: 16 }}>{t("profile.uploadAvatar")}</Button>
|
||||
</Upload>
|
||||
<Form.Item label={t("users.password")} name="password" rules={[{ required: !editing, message: t("users.password") }]}><Input.Password placeholder={editing ? t("usersExt.passwordKeepPlaceholder") : t("usersExt.passwordInitPlaceholder")} /></Form.Item>
|
||||
<Form.Item label={t("users.password")} name="password" rules={[{ required: !editing, message: t("users.password") }]}><Input.Password placeholder={editing ? t("usersExt.passwordKeepPlaceholder") : t("usersExt.passwordInitPlaceholder")} autoComplete="new-password" /></Form.Item>
|
||||
{passwordValue && (
|
||||
<Form.Item
|
||||
label={t("usersExt.confirmPassword")}
|
||||
name="confirmPassword"
|
||||
dependencies={["password"]}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
required: !editing || !!getFieldValue("password"),
|
||||
message: t("usersExt.confirmPassword"),
|
||||
}),
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue("password") === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("profile.passwordsDoNotMatch")));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder={t("usersExt.confirmPasswordPlaceholder")} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label={t("users.roles")} name="roleIds" rules={[{ required: true, message: t("users.roles") }]}><Select mode="multiple" placeholder={t("users.roles")} options={roleOptions} optionFilterProp={isPlatformMode ? "searchText" : "label"} /></Form.Item>
|
||||
{!isPlatformMode && <Form.Item label={t("users.orgNode")} name="orgId"><TreeSelect placeholder={t("usersExt.selectOrgPlaceholder")} allowClear treeData={orgTreeData} /></Form.Item>}
|
||||
<Row gutter={16}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue