update: replace table component

This commit is contained in:
tao.chen
2026-08-26 10:47:54 +08:00
parent 83190ea57e
commit 32d3309ecf
3 changed files with 259 additions and 166 deletions
@@ -8,6 +8,14 @@ import Icon from "../../components/common/Icon";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { Textarea } from "~/components/ui/textarea"; import { Textarea } from "~/components/ui/textarea";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { UserMultiSelect } from "./UserMultiSelect"; import { UserMultiSelect } from "./UserMultiSelect";
import { import {
formFieldClass, formFieldClass,
@@ -278,63 +286,88 @@ export function ProjectManagementPage({
</div> </div>
{!canManage && <div className="admin-readonly"></div>} {!canManage && <div className="admin-readonly"></div>}
<div className="employee-table"> <Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<div className="employee-table__head"> <colgroup>
<span></span> <col style={{ width: "32%" }} />
<span></span> <col style={{ width: "22%" }} />
<span></span> <col style={{ width: "15%" }} />
<span></span> <col style={{ width: "14%" }} />
<span></span> <col style={{ width: "17%" }} />
</div> </colgroup>
{projectLoading ? ( <TableHeader className="bg-[#f5f8fb] text-[#748598]">
<p className="admin-empty"></p> <TableRow className="hover:bg-transparent border-b border-[#edf1f5]">
) : projects.length === 0 ? ( <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
<p className="admin-empty"></p> <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
) : ( <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
projects <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
.filter((project) => { <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
const term = projectSearchTerm.toLowerCase().trim(); </TableRow>
if (!term) return true; </TableHeader>
return ( <TableBody>
project.workspace_name.toLowerCase().includes(term) || {projectLoading ? (
project.workspace_code.toLowerCase().includes(term) || <TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
(project.description && project.description.toLowerCase().includes(term)) <TableCell colSpan={5} className="p-0">
); <p className="admin-empty"></p>
}) </TableCell>
.map((project) => ( </TableRow>
<div className="employee-row" key={project.workspace_id}> ) : projects.length === 0 ? (
<span className="employee-name"> <TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<b className="avatar">{project.workspace_name.slice(0, 1)}</b> <TableCell colSpan={5} className="p-0">
<span> <p className="admin-empty"></p>
<strong>{project.workspace_name}</strong> </TableCell>
<small>{project.description ?? "无描述"}</small> </TableRow>
</span> ) : (
</span> projects
<code>{project.workspace_code}</code> .filter((project) => {
<span> const term = projectSearchTerm.toLowerCase().trim();
<span className={`status-pill is-${project.status}`}> if (!term) return true;
{project.status === "active" ? "正常" : project.status === "archived" ? "已归档" : "已删除"} return (
</span> project.workspace_name.toLowerCase().includes(term) ||
</span> project.workspace_code.toLowerCase().includes(term) ||
<span>{project.quota_bytes > 0 ? `${(project.quota_bytes / 1024 / 1024 / 1024).toFixed(1)} GB` : "无限制"}</span> (project.description && project.description.toLowerCase().includes(term))
<span className="employee-actions"> );
<button type="button" disabled={!canManage} onClick={() => openMembersDrawer(project)}></button> })
<button type="button" disabled={!canManage} onClick={() => openEditProject(project)}></button> .map((project) => (
{/* <button type="button" disabled={!canManage} onClick={() => openImportMemberDialog(project)}>导入成员</button> */} <TableRow key={project.workspace_id} className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<button <TableCell className="p-3 px-4 align-middle">
type="button" <span className="employee-name">
className="is-danger" <b className="avatar">{project.workspace_name.slice(0, 1)}</b>
disabled={!canManage || project.status === "disabled"} <span>
title={project.status === "disabled" ? "已删除的项目不能操作" : "删除项目"} <strong>{project.workspace_name}</strong>
onClick={() => void deleteProject(project)} <small>{project.description ?? "无描述"}</small>
> </span>
</span>
</button> </TableCell>
</span> <TableCell className="p-3 px-4 align-middle"><code>{project.workspace_code}</code></TableCell>
</div> <TableCell className="p-3 px-4 align-middle">
)) <span>
)} <span className={`status-pill is-${project.status}`}>
</div> {project.status === "active" ? "正常" : project.status === "archived" ? "已归档" : "已删除"}
</span>
</span>
</TableCell>
<TableCell className="p-3 px-4 align-middle"><span>{project.quota_bytes > 0 ? `${(project.quota_bytes / 1024 / 1024 / 1024).toFixed(1)} GB` : "无限制"}</span></TableCell>
<TableCell className="p-3 px-4 align-middle">
<span className="employee-actions">
<button type="button" disabled={!canManage} onClick={() => openMembersDrawer(project)}></button>
<button type="button" disabled={!canManage} onClick={() => openEditProject(project)}></button>
{/* <button type="button" disabled={!canManage} onClick={() => openImportMemberDialog(project)}>导入成员</button> */}
<button
type="button"
className="is-danger"
disabled={!canManage || project.status === "disabled"}
title={project.status === "disabled" ? "已删除的项目不能操作" : "删除项目"}
onClick={() => void deleteProject(project)}
>
</button>
</span>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
{projectDialogOpen && ( {projectDialogOpen && (
<div className={modalBackdropClass} role="presentation"> <div className={modalBackdropClass} role="presentation">
@@ -12,6 +12,14 @@ import { useApi, useAuth } from "../../context/AuthContext";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { PermissionEditDialog } from "./PermissionEditDialog"; import { PermissionEditDialog } from "./PermissionEditDialog";
import { RoleEditDialog } from "./RoleEditDialog"; import { RoleEditDialog } from "./RoleEditDialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import "../../styles/admin.css"; import "../../styles/admin.css";
@@ -195,64 +203,87 @@ export function RoleManagementPage({
)} )}
{loadError && <div className="admin-readonly">{loadError}</div>} {loadError && <div className="admin-readonly">{loadError}</div>}
<div className="role-table"> <Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<div className="role-table__head"> <colgroup>
<span></span><span></span><span></span><span></span><span></span> <col style={{ width: "20%" }} />
</div> <col style={{ width: "23%" }} />
{loading ? ( <col style={{ width: "14%" }} />
<p className="admin-empty"></p> <col style={{ width: "16%" }} />
) : filteredRoles.length === 0 ? ( <col style={{ width: "27%" }} />
<p className="admin-empty"> </colgroup>
{roles.length === 0 ? "暂无角色" : "没有匹配的角色"} <TableHeader className="bg-[#f5f8fb] text-[#748598]">
</p> <TableRow className="hover:bg-transparent border-b border-[#edf1f5]">
) : ( <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
filteredRoles.map((role) => ( <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
<div className="role-row" key={role.role_id}> <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
<code className="role-code">{role.role_code}</code> <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
<span className="role-name">{role.role_name}</span> <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
<span className={`builtin-badge${role.is_builtin ? "" : " is-custom"}`}> </TableRow>
{role.is_builtin ? "内置" : "自定义"} </TableHeader>
</span> <TableBody>
<span className="permission-count"> {loading ? (
{role.permission_codes.length} <TableRow className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
</span> <TableCell colSpan={5} className="p-0">
<span className="role-actions"> <p className="admin-empty"></p>
<Button </TableCell>
variant="outline" </TableRow>
size="xs" ) : filteredRoles.length === 0 ? (
type="button" <TableRow className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
disabled={!canManage} <TableCell colSpan={5} className="p-0">
onClick={() => openEdit(role)} <p className="admin-empty">
className="h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#4c6c88] hover:bg-[#f7fafc] disabled:cursor-not-allowed disabled:opacity-[0.45]" {roles.length === 0 ? "暂无角色" : "没有匹配的角色"}
> </p>
</TableCell>
</Button> </TableRow>
<Button ) : (
variant="outline" filteredRoles.map((role) => (
size="xs" <TableRow key={role.role_id} className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
type="button" <TableCell className="p-3 px-4 align-middle"><code className="role-code">{role.role_code}</code></TableCell>
disabled={!canManage} <TableCell className="p-3 px-4 align-middle"><span className="role-name">{role.role_name}</span></TableCell>
onClick={() => openPermissions(role)} <TableCell className="p-3 px-4 align-middle"><span className={`builtin-badge${role.is_builtin ? "" : " is-custom"}`}>
className="h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#4c6c88] hover:bg-[#f7fafc] disabled:cursor-not-allowed disabled:opacity-[0.45]" {role.is_builtin ? "内置" : "自定义"}
> </span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className="permission-count">
</Button> {role.permission_codes.length}
<Button </span></TableCell>
variant="destructive" <TableCell className="p-3 px-4 align-middle"><span className="role-actions">
size="xs" <Button
type="button" variant="outline"
disabled={!canManage || role.is_builtin} size="xs"
title={role.is_builtin ? "内置角色不可删除" : "删除角色"} type="button"
onClick={() => void removeRole(role)} disabled={!canManage}
className="h-6 gap-1 rounded-[4px] border-red-200 bg-white px-[9px] text-[11px] text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-[0.45]" onClick={() => openEdit(role)}
> className="h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#4c6c88] hover:bg-[#f7fafc] disabled:cursor-not-allowed disabled:opacity-[0.45]"
>
</Button>
</span> </Button>
</div> <Button
)) variant="outline"
)} size="xs"
</div> type="button"
disabled={!canManage}
onClick={() => openPermissions(role)}
className="h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#4c6c88] hover:bg-[#f7fafc] disabled:cursor-not-allowed disabled:opacity-[0.45]"
>
</Button>
<Button
variant="destructive"
size="xs"
type="button"
disabled={!canManage || role.is_builtin}
title={role.is_builtin ? "内置角色不可删除" : "删除角色"}
onClick={() => void removeRole(role)}
className="h-6 gap-1 rounded-[4px] border-red-200 bg-white px-[9px] text-[11px] text-red-700 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-[0.45]"
>
</Button>
</span></TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
{dialogOpen && ( {dialogOpen && (
<RoleEditDialog <RoleEditDialog
@@ -6,6 +6,14 @@ import { useApi, useAuth } from "../../context/AuthContext";
import Icon from "../../components/common/Icon"; import Icon from "../../components/common/Icon";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { import {
formFieldClass, formFieldClass,
formInputClass, formInputClass,
@@ -212,57 +220,78 @@ export function UserManagementPage({
</div> </div>
{!canManage && <div className="admin-readonly"></div>} {!canManage && <div className="admin-readonly"></div>}
<div className="employee-table"> <Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<div className="employee-table__head"><span></span><span></span><span></span><span></span><span></span></div> <colgroup>
{loading ? ( <col style={{ width: "32%" }} />
<p className="admin-empty"></p> <col style={{ width: "22%" }} />
) : ( <col style={{ width: "15%" }} />
employees <col style={{ width: "14%" }} />
.filter((employee) => { <col style={{ width: "17%" }} />
const term = userSearchTerm.toLowerCase().trim(); </colgroup>
if (!term) return true; <TableHeader className="bg-[#f5f8fb] text-[#748598]">
return ( <TableRow className="hover:bg-transparent border-b border-[#edf1f5]">
employee.display_name.toLowerCase().includes(term) || <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
employee.username.toLowerCase().includes(term) || <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
(employee.email && employee.email.toLowerCase().includes(term)) <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
); <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
}) <TableHead className="h-10 px-4 text-[10px] font-bold text-[#748598]"></TableHead>
.map((employee) => { </TableRow>
const isProtectedAdmin = employee.role_code === "admin"; </TableHeader>
return ( <TableBody>
<div className="employee-row" key={employee.user_id}> {loading ? (
<span className="employee-name"><b className="avatar">{employee.display_name.slice(0, 1)}</b><span><strong>{employee.display_name}</strong><small>{employee.email ?? "未设置邮箱"}</small></span></span> <TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<code>{employee.username}</code> <TableCell colSpan={5} className="p-0">
{employee.role_code ? (<span className={`role-pill is-${employee.role_code}`}>{employee.role_name}</span>) : (<span>-</span>)} <p className="admin-empty"></p>
<span className={`status-pill is-${employee.status}`}>{employee.status === "active" ? "正常" : employee.status === "disabled" ? "已停用" : "已锁定"}</span> </TableCell>
<span className="employee-actions"> </TableRow>
<Button ) : (
variant="outline" employees
size="xs" .filter((employee) => {
type="button" const term = userSearchTerm.toLowerCase().trim();
disabled={!canManage} if (!term) return true;
onClick={() => openEdit(employee)} return (
className={ROW_BUTTON_CLASS} employee.display_name.toLowerCase().includes(term) ||
> employee.username.toLowerCase().includes(term) ||
(employee.email && employee.email.toLowerCase().includes(term))
</Button> );
<Button })
variant="destructive" .map((employee) => {
size="xs" const isProtectedAdmin = employee.role_code === "admin";
type="button" return (
disabled={!canManage || isProtectedAdmin} <TableRow key={employee.user_id} className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
title={isProtectedAdmin ? "管理员账号不能删除" : "删除"} <TableCell className="p-3 px-4 align-middle"><span className="employee-name"><b className="avatar">{employee.display_name.slice(0, 1)}</b><span><strong>{employee.display_name}</strong><small>{employee.email ?? "未设置邮箱"}</small></span></span></TableCell>
onClick={() => void remove(employee)} <TableCell className="p-3 px-4 align-middle"><code>{employee.username}</code></TableCell>
className={ROW_DANGER_BUTTON_CLASS} <TableCell className="p-3 px-4 align-middle">{employee.role_code ? (<span className={`role-pill is-${employee.role_code}`}>{employee.role_name}</span>) : (<span>-</span>)}</TableCell>
> <TableCell className="p-3 px-4 align-middle"><span className={`status-pill is-${employee.status}`}>{employee.status === "active" ? "正常" : employee.status === "disabled" ? "已停用" : "已锁定"}</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className="employee-actions">
</Button> <Button
</span> variant="outline"
</div> size="xs"
); type="button"
}) disabled={!canManage}
)} onClick={() => openEdit(employee)}
</div> className={ROW_BUTTON_CLASS}
>
</Button>
<Button
variant="destructive"
size="xs"
type="button"
disabled={!canManage || isProtectedAdmin}
title={isProtectedAdmin ? "管理员账号不能删除" : "删除"}
onClick={() => void remove(employee)}
className={ROW_DANGER_BUTTON_CLASS}
>
</Button>
</span></TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
{dialogOpen && ( {dialogOpen && (
<div className={modalBackdropClass} role="presentation"> <div className={modalBackdropClass} role="presentation">