updata:admin.css替用

This commit is contained in:
xiaozhu
2026-08-27 15:07:25 +08:00
parent 213490f8ac
commit 433c68998e
9 changed files with 226 additions and 76 deletions
@@ -1,6 +1,13 @@
import type { PlatformPermission } from "../../services/api";
import { Checkbox } from "~/components/ui/checkbox";
import {
adminEmptyClass,
permissionGroupClass,
permissionGroupTitleClass,
permissionGroupsClass,
permissionItemClassName,
} from "./adminUi";
/** 模块编码 → 展示名(用于权限分组标题)。 */
const MODULE_LABELS: Record<string, string> = {
@@ -10,7 +17,7 @@ const MODULE_LABELS: Record<string, string> = {
system: "系统管理",
};
/** 勾选框样式:复刻原生 checkbox 外观(白底、灰边、蓝色勾选),并对齐 `.permission-item input` 的 3px 顶部偏移。 */
/** 勾选框样式:复刻原生 checkbox 外观(白底、灰边、蓝色勾选),并对齐 permission item 的 3px 顶部偏移。 */
const CHECKBOX_CLASS =
"mt-[3px] size-[16px] rounded-[3px] border-[#cdd9e4] bg-white disabled:opacity-100 data-checked:border-[#1277d3] data-checked:bg-[#1277d3] data-checked:text-white";
@@ -41,16 +48,16 @@ export function PermissionCheckboxList({
);
if (permissions.length === 0) {
return <p className="admin-empty"></p>;
return <p className={adminEmptyClass}></p>;
}
const isAdmin = targetRoleCode === "admin";
return (
<div className="permission-groups">
<div className={permissionGroupsClass}>
{Object.entries(groups).map(([module, items]) => (
<div className="permission-group" key={module}>
<div className="permission-group__title">
<div className={permissionGroupClass} key={module}>
<div className={permissionGroupTitleClass}>
{MODULE_LABELS[module] ?? module}
</div>
{items.map((permission) => {
@@ -61,7 +68,7 @@ export function PermissionCheckboxList({
const checked = selected.includes(code);
return (
<label
className={`permission-item${locked ? " is-locked" : ""}`}
className={permissionItemClassName(locked)}
key={code}
>
<Checkbox
@@ -9,6 +9,7 @@ import {
import { Button } from "~/components/ui/button";
import { modalFormClass } from "../platform/modalUi";
import { PermissionCheckboxList } from "./PermissionCheckboxList";
import { roleErrorClass } from "./adminUi";
export function PermissionEditDialog({
open,
@@ -91,7 +92,7 @@ export function PermissionEditDialog({
>
<div className={modalFormClass}>
{target?.role_code === "admin" && (
<p className="role-error">admin system:view </p>
<p className={roleErrorClass}>admin system:view </p>
)}
{target ? (
<PermissionCheckboxList
@@ -17,8 +17,20 @@ import { AdminColgroup, USER_PROJECT_COL_WIDTHS } from "./AdminTable";
import { ProjectEditDialog } from "./ProjectEditDialog";
import { ImportMemberDialog } from "./ImportMemberDialog";
import { ProjectMembersDrawer } from "./ProjectMembersDrawer";
import "../../styles/admin.css";
import {
adminEmptyClass,
adminPageClass,
adminReadonlyClass,
adminToolbarClass,
adminToolbarInputClass,
adminToolbarSearchClass,
employeeActionsClass,
employeeAvatarClass,
employeeNameClass,
rowButtonClass,
rowDangerButtonClass,
statusPillClass,
} from "./adminUi";
const EMPTY_PROJECT_FORM = {
workspace_code: "",
@@ -242,14 +254,14 @@ export function ProjectManagementPage({
};
return (
<section className="admin-page">
<div className="admin-toolbar">
<div className="admin-toolbar__search">
<section className={adminPageClass}>
<div className={adminToolbarClass}>
<div className={adminToolbarSearchClass}>
<Search size={14} />
<input
type="text"
placeholder="请输入项目名称"
className="admin-toolbar__input"
className={adminToolbarInputClass}
value={projectSearchTerm}
onChange={(event) => setProjectSearchTerm(event.target.value)}
/>
@@ -267,7 +279,7 @@ export function ProjectManagementPage({
</Button>
</div>
{!canManage && <div className="admin-readonly"></div>}
{!canManage && <div className={adminReadonlyClass}></div>}
<Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<AdminColgroup widths={USER_PROJECT_COL_WIDTHS} />
<TableHeader className="bg-[#f5f8fb] text-[#748598]">
@@ -283,13 +295,13 @@ export function ProjectManagementPage({
{projectLoading ? (
<TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell colSpan={5} className="p-0">
<p className="admin-empty"></p>
<p className={adminEmptyClass}></p>
</TableCell>
</TableRow>
) : projects.length === 0 ? (
<TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell colSpan={5} className="p-0">
<p className="admin-empty"></p>
<p className={adminEmptyClass}></p>
</TableCell>
</TableRow>
) : (
@@ -306,8 +318,8 @@ export function ProjectManagementPage({
.map((project) => (
<TableRow key={project.workspace_id} className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell className="p-3 px-4 align-middle">
<span className="employee-name">
<b className="avatar">{project.workspace_name.slice(0, 1)}</b>
<span className={employeeNameClass}>
<b className={employeeAvatarClass}>{project.workspace_name.slice(0, 1)}</b>
<span>
<strong>{project.workspace_name}</strong>
<small>{project.description ?? "无描述"}</small>
@@ -317,26 +329,45 @@ export function ProjectManagementPage({
<TableCell className="p-3 px-4 align-middle"><code>{project.workspace_code}</code></TableCell>
<TableCell className="p-3 px-4 align-middle">
<span>
<span className={`status-pill is-${project.status}`}>
<span className={statusPillClass(project.status)}>
{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
<span className={employeeActionsClass}>
<Button
variant="outline"
size="xs"
type="button"
disabled={!canManage}
onClick={() => openMembersDrawer(project)}
className={rowButtonClass}
>
</Button>
<Button
variant="outline"
size="xs"
type="button"
disabled={!canManage}
onClick={() => openEditProject(project)}
className={rowButtonClass}
>
</Button>
<Button
variant="destructive"
size="xs"
type="button"
className="is-danger"
disabled={!canManage || project.status === "disabled"}
title={project.status === "disabled" ? "已删除的项目不能操作" : "删除项目"}
onClick={() => setDeleteTarget(project)}
className={rowDangerButtonClass}
>
</button>
</Button>
</span>
</TableCell>
</TableRow>
+12 -6
View File
@@ -19,6 +19,12 @@ import {
modalFormClass,
} from "../platform/modalUi";
import { PermissionCheckboxList } from "./PermissionCheckboxList";
import {
formHintClass,
permissionGroupTitleClass,
permissionGroupsClass,
roleErrorClass,
} from "./adminUi";
const RESERVED_ROLE_CODES = new Set(["admin", "developer"]);
const ROLE_CODE_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/;
@@ -153,7 +159,7 @@ export function RoleEditDialog({
eyebrow="ROLE"
title={editing ? "编辑角色" : "新建角色"}
>
<form className={`${modalFormClass} role-create-form`} onSubmit={(event) => void handleSubmit(event)}>
<form className={modalFormClass} onSubmit={(event) => void handleSubmit(event)}>
{editing ? (
<label className={formFieldClass}>
<span></span>
@@ -169,10 +175,10 @@ export function RoleEditDialog({
onChange={handleRoleCodeChange}
placeholder="例如:qa_engineer"
/>
<span className="form-hint">
<span className={formHintClass}>
//线/2~64
</span>
{roleCodeError && <span className="role-error">{roleCodeError}</span>}
{roleCodeError && <span className={roleErrorClass}>{roleCodeError}</span>}
</label>
)}
<label className={formFieldClass}>
@@ -185,7 +191,7 @@ export function RoleEditDialog({
placeholder="请输入角色名称"
/>
{editing !== null && editing.is_builtin && (
<span className="form-hint"></span>
<span className={formHintClass}></span>
)}
</label>
<label className={formFieldClass}>
@@ -198,8 +204,8 @@ export function RoleEditDialog({
/>
</label>
{!editing && (
<div className="permission-groups">
<div className="permission-group__title"></div>
<div className={permissionGroupsClass}>
<div className={permissionGroupTitleClass}></div>
<PermissionCheckboxList
permissions={permissions}
selected={form.permission_codes}
@@ -22,8 +22,21 @@ import {
TableRow,
} from "~/components/ui/table";
import { AdminColgroup, ROLE_COL_WIDTHS } from "./AdminTable";
import "../../styles/admin.css";
import {
adminEmptyClass,
adminPageClass,
adminReadonlyClass,
adminToolbarClass,
adminToolbarInputClass,
adminToolbarSearchClass,
builtinBadgeClass,
permissionCountClass,
roleActionsClass,
roleCodeClass,
roleNameClass,
rowButtonClass,
rowDangerButtonClass,
} from "./adminUi";
const PRIMARY_BUTTON_CLASS =
"h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]";
@@ -177,14 +190,14 @@ export function RoleManagementPage({
});
return (
<section className="admin-page">
<div className="admin-toolbar">
<div className="admin-toolbar__search">
<section className={adminPageClass}>
<div className={adminToolbarClass}>
<div className={adminToolbarSearchClass}>
<Search size={14} />
<input
type="text"
placeholder="搜索角色编码 / 名称"
className="admin-toolbar__input"
className={adminToolbarInputClass}
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
/>
@@ -203,9 +216,9 @@ export function RoleManagementPage({
</div>
{!canManage && (
<div className="admin-readonly"></div>
<div className={adminReadonlyClass}></div>
)}
{loadError && <div className="admin-readonly">{loadError}</div>}
{loadError && <div className={adminReadonlyClass}>{loadError}</div>}
<Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<AdminColgroup widths={ROLE_COL_WIDTHS} />
@@ -222,13 +235,13 @@ export function RoleManagementPage({
{loading ? (
<TableRow className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell colSpan={5} className="p-0">
<p className="admin-empty"></p>
<p className={adminEmptyClass}></p>
</TableCell>
</TableRow>
) : filteredRoles.length === 0 ? (
<TableRow className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell colSpan={5} className="p-0">
<p className="admin-empty">
<p className={adminEmptyClass}>
{roles.length === 0 ? "暂无角色" : "没有匹配的角色"}
</p>
</TableCell>
@@ -236,22 +249,22 @@ export function RoleManagementPage({
) : (
filteredRoles.map((role) => (
<TableRow key={role.role_id} className="min-h-[60px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell className="p-3 px-4 align-middle"><code className="role-code">{role.role_code}</code></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className="role-name">{role.role_name}</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className={`builtin-badge${role.is_builtin ? "" : " is-custom"}`}>
<TableCell className="p-3 px-4 align-middle"><code className={roleCodeClass}>{role.role_code}</code></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className={roleNameClass}>{role.role_name}</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className={builtinBadgeClass(role.is_builtin)}>
{role.is_builtin ? "内置" : "自定义"}
</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className="permission-count">
<TableCell className="p-3 px-4 align-middle"><span className={permissionCountClass}>
{role.permission_codes.length}
</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className="role-actions">
<TableCell className="p-3 px-4 align-middle"><span className={roleActionsClass}>
<Button
variant="outline"
size="xs"
type="button"
disabled={!canManage}
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]"
className={rowButtonClass}
>
</Button>
@@ -261,7 +274,7 @@ export function RoleManagementPage({
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]"
className={rowButtonClass}
>
</Button>
@@ -272,7 +285,7 @@ export function RoleManagementPage({
disabled={!canManage || role.is_builtin}
title={role.is_builtin ? "内置角色不可删除" : "删除角色"}
onClick={() => setDeleteTarget(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]"
className={rowDangerButtonClass}
>
</Button>
@@ -25,8 +25,21 @@ import {
formInputClass,
modalFormClass,
} from "../platform/modalUi";
import "../../styles/admin.css";
import {
adminEmptyClass,
adminPageClass,
adminReadonlyClass,
adminToolbarClass,
adminToolbarInputClass,
adminToolbarSearchClass,
employeeActionsClass,
employeeAvatarClass,
employeeNameClass,
rolePillClass,
rowButtonClass,
rowDangerButtonClass,
statusPillClass,
} from "./adminUi";
const EMPTY_FORM = {
username: "",
@@ -41,11 +54,6 @@ const PRIMARY_BUTTON_CLASS =
"h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]";
const FORM_INPUT_CLASS =
"h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:shadow-[0_0_0_3px_rgb(38_125_207/8%)] disabled:cursor-not-allowed disabled:opacity-50";
const ROW_BUTTON_CLASS =
"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]";
const ROW_DANGER_BUTTON_CLASS =
"h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#c14a4a] hover:bg-[#fdf2f2] disabled:cursor-not-allowed disabled:opacity-[0.45]";
export function UserManagementPage({
onNotify,
onConnectionChange,
@@ -190,14 +198,14 @@ export function UserManagementPage({
};
return (
<section className="admin-page">
<div className="admin-toolbar">
<div className="admin-toolbar__search">
<section className={adminPageClass}>
<div className={adminToolbarClass}>
<div className={adminToolbarSearchClass}>
<Search size={14} />
<input
type="text"
placeholder="请输入用户名"
className="admin-toolbar__input"
className={adminToolbarInputClass}
value={userSearchTerm}
onChange={(event) => setUserSearchTerm(event.target.value)}
/>
@@ -215,7 +223,7 @@ export function UserManagementPage({
</Button>
</div>
{!canManage && <div className="admin-readonly"></div>}
{!canManage && <div className={adminReadonlyClass}></div>}
<Table className="rounded-[9px] border border-[#dfe7ef] overflow-hidden text-[11px] text-[#44576a] [--border-color:#dfe7ef] table-fixed bg-white">
<AdminColgroup widths={USER_PROJECT_COL_WIDTHS} />
<TableHeader className="bg-[#f5f8fb] text-[#748598]">
@@ -231,7 +239,7 @@ export function UserManagementPage({
{loading ? (
<TableRow className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<TableCell colSpan={5} className="p-0">
<p className="admin-empty"></p>
<p className={adminEmptyClass}></p>
</TableCell>
</TableRow>
) : (
@@ -249,18 +257,18 @@ export function UserManagementPage({
const isProtectedAdmin = employee.role_code === "admin";
return (
<TableRow key={employee.user_id} className="min-h-[64px] border-t border-[#edf1f5] hover:bg-[#f7fafc] data-[state=selected]:bg-transparent">
<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>
<TableCell className="p-3 px-4 align-middle"><span className={employeeNameClass}><b className={employeeAvatarClass}>{employee.display_name.slice(0, 1)}</b><span><strong>{employee.display_name}</strong><small>{employee.email ?? "未设置邮箱"}</small></span></span></TableCell>
<TableCell className="p-3 px-4 align-middle"><code>{employee.username}</code></TableCell>
<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">
<TableCell className="p-3 px-4 align-middle">{employee.role_code ? (<span className={rolePillClass(employee.role_code)}>{employee.role_name}</span>) : (<span>-</span>)}</TableCell>
<TableCell className="p-3 px-4 align-middle"><span className={statusPillClass(employee.status)}>{employee.status === "active" ? "正常" : employee.status === "disabled" ? "已停用" : "已锁定"}</span></TableCell>
<TableCell className="p-3 px-4 align-middle"><span className={employeeActionsClass}>
<Button
variant="outline"
size="xs"
type="button"
disabled={!canManage}
onClick={() => openEdit(employee)}
className={ROW_BUTTON_CLASS}
className={rowButtonClass}
>
</Button>
@@ -271,7 +279,7 @@ export function UserManagementPage({
disabled={!canManage || isProtectedAdmin}
title={isProtectedAdmin ? "管理员账号不能删除" : "删除"}
onClick={() => setDeleteTarget(employee)}
className={ROW_DANGER_BUTTON_CLASS}
className={rowDangerButtonClass}
>
</Button>
@@ -1,6 +1,7 @@
import { useEffect, useState, useRef } from "react";
import { type Employee } from "../../services/api";
import { adminToolbarInputClass } from "./adminUi";
// 多选用户下拉框组件 - 带 tag 显示
export function UserMultiSelect({
@@ -47,7 +48,7 @@ export function UserMultiSelect({
<div ref={containerRef} style={{ position: "relative" }}>
{/* 选择框 - 显示 tag */}
<div
className="admin-toolbar__input"
className={adminToolbarInputClass}
onClick={() => setIsOpen(!isOpen)}
style={{
display: "flex",
+89
View File
@@ -0,0 +1,89 @@
import { cn } from "~/lib/utils";
/** Shared Tailwind class strings for admin management pages. */
export const adminPageClass = "h-full overflow-auto bg-[#f3f6f9] p-6";
export const adminToolbarClass =
"mb-3.5 flex items-center justify-between rounded-lg border border-[#dfe7ef] bg-white px-3.5 py-3";
export const adminToolbarSearchClass =
"flex w-[280px] items-center gap-2 rounded-md border border-[#d9e2eb] bg-[#f8fafb] px-3 py-2 [&_svg]:text-[#8a99a8]";
export const adminToolbarInputClass =
"w-full border-none bg-transparent text-xs text-[#20364c] outline-none placeholder:text-[#a0b0bf]";
export const adminReadonlyClass =
"mb-3 rounded-md border border-[#f1d79c] bg-[#fff8e8] px-3.5 py-2.5 text-[11px] text-[#8a6416]";
export const adminEmptyClass = "p-[25px] text-center text-[#8796a6]";
export const employeeNameClass =
"flex items-center gap-2.5 [&_small]:mt-[3px] [&_small]:text-[#93a0ae] [&_strong]:text-[#23384e] [&>span]:flex [&>span]:min-w-0 [&>span]:flex-col";
export const employeeAvatarClass =
"grid size-8 place-items-center rounded-full bg-gradient-to-br from-[#3b92ed] to-[#1869c9] text-sm font-bold text-white";
export const employeeActionsClass = "flex gap-1.5";
export const roleActionsClass = "flex gap-1.5";
export const rowButtonClass =
"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]";
export const rowDangerButtonClass =
"h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#c14a4a] hover:bg-[#fdf2f2] disabled:cursor-not-allowed disabled:opacity-[0.45]";
export const roleCodeClass = "font-mono text-[11px] text-[#60758b]";
export const roleNameClass = "font-semibold text-[#23384e]";
export const permissionCountClass = "text-[#5d7488]";
export const formHintClass =
"mt-1.5 block text-[10px] font-normal leading-normal text-[#98a5b3]";
export const roleErrorClass = "mt-1.5 text-[10px] font-medium text-[#c0392b]";
export const permissionGroupsClass = "flex flex-col gap-3.5";
export const permissionGroupClass =
"rounded-[7px] border border-[#e3eaf1] bg-[#fafbfc] px-3 py-2.5";
export const permissionGroupTitleClass =
"mb-2 text-[10px] font-bold tracking-wide text-[#2e86de]";
export const permissionItemClass =
"flex cursor-pointer items-start gap-[9px] rounded-md px-2 py-[7px] hover:bg-[#f0f6fc] [&_small]:mt-0.5 [&_small]:text-[10px] [&_small]:text-[#8a99a8] [&_span]:flex [&_span]:min-w-0 [&_span]:flex-col [&_strong]:text-[11px] [&_strong]:text-[#33475c]";
export const permissionItemLockedClass =
"cursor-not-allowed bg-[#f1f3f5] opacity-80 hover:bg-[#f1f3f5]";
export function rolePillClass(roleCode: string): string {
return cn(
"w-max rounded-xl px-2 py-1",
roleCode === "admin"
? "bg-[#fff2d6] text-[#87580f]"
: "bg-[#eaf4ff] text-[#1d6ab3]",
);
}
export function statusPillClass(status: string): string {
return cn(
"w-max rounded-xl px-2 py-1",
status === "disabled" || status === "locked"
? "bg-[#ffeded] text-[#a64b4b]"
: "bg-[#e8f8f1] text-[#138657]",
);
}
export function builtinBadgeClass(isBuiltin: boolean): string {
return cn(
"w-max rounded-xl px-2 py-1 text-[10px]",
isBuiltin ? "bg-[#fff2d6] text-[#87580f]" : "bg-[#eaf4ff] text-[#1d6ab3]",
);
}
export function permissionItemClassName(locked: boolean): string {
return cn(permissionItemClass, locked && permissionItemLockedClass);
}
-6
View File
@@ -1,6 +0,0 @@
.admin-page{height:100%;padding:24px;overflow:auto;background:#f3f6f9}.admin-page__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:15px;padding:20px 22px;border:1px solid #dee7ef;border-radius:9px;background:#fff}.admin-page__header span{color:#1978d4;font-size:10px;font-weight:800}.admin-page__header h2{margin:4px 0;color:#20364c}.admin-page__header p{margin:0;color:#8a99a8;font-size:11px}.admin-readonly{margin-bottom:12px;padding:10px 13px;border:1px solid #f1d79c;border-radius:6px;color:#8a6416;background:#fff8e8;font-size:11px}.employee-name{display:flex;align-items:center;gap:10px}.employee-name .avatar{display:grid;width:32px;height:32px;place-items:center}.employee-name>span{display:flex;min-width:0;flex-direction:column}.employee-name strong{color:#23384e}.employee-name small{margin-top:3px;color:#93a0ae}.role-pill,.status-pill{width:max-content;padding:4px 8px;border-radius:12px}.role-pill{color:#1d6ab3;background:#eaf4ff}.role-pill.is-admin{color:#87580f;background:#fff2d6}.status-pill{color:#138657;background:#e8f8f1}.status-pill.is-disabled,.status-pill.is-locked{color:#a64b4b;background:#ffeded}.employee-actions{display:flex;gap:6px}.employee-actions button{padding:5px 9px;border:1px solid #d9e3ec;border-radius:4px;color:#4c6c88;background:#fff;cursor:pointer}.employee-actions button.is-danger{color:#c14a4a}.employee-actions button:disabled{cursor:not-allowed;opacity:.45}.admin-empty{padding:25px;text-align:center;color:#8796a6}
.admin-tabs{display:flex;gap:10px;margin-bottom:16px}.admin-tab{display:flex;align-items:center;gap:7px;height:42px;padding:0 16px;border:1px solid #d9e2eb;border-radius:8px;color:#54687e;background:#fff;cursor:pointer;font-size:12px;font-weight:600;transition:all .15s ease}.admin-tab:hover{border-color:#b8c9d9;background:#f7fafc}.admin-tab.is-active{border-color:#2e86de;color:#fff;background:linear-gradient(135deg,#2e86de,#1978d4);box-shadow:0 6px 18px rgb(28 119 222/22%)}.admin-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;padding:12px 14px;border:1px solid #dfe7ef;border-radius:8px;background:#fff}.admin-toolbar__search{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid #d9e2eb;border-radius:6px;background:#f8fafb;width:280px}.admin-toolbar__search svg{color:#8a99a8}.admin-toolbar__input{border:none;outline:none;background:transparent;color:#20364c;font-size:12px;width:100%}.admin-toolbar__input::placeholder{color:#a0b0bf}.admin-toolbar__button{display:flex;align-items:center;gap:6px;height:36px;padding:0 14px;border:none;border-radius:6px;color:#fff;background:linear-gradient(135deg,#2e86de,#1978d4);cursor:pointer;font-size:12px;font-weight:600;box-shadow:0 4px 12px rgb(28 119 222/18%)}.admin-toolbar__button:disabled{cursor:not-allowed;opacity:.5}.project-list-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:400px;border:1px solid #e0e7ee;border-radius:9px;background:#fff;color:#7a8fa3;text-align:center}.project-list-placeholder svg{margin-bottom:16px;opacity:.6}.project-list-placeholder h3{margin:12px 0 6px;color:#34475d;font-size:16px}.project-list-placeholder p{margin:0;font-size:12px}.form-field .required{color:#e53935;margin-left:4px;font-size:12px}
.role-code{color:#60758b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.role-name{color:#23384e;font-weight:600}.builtin-badge{width:max-content;padding:4px 8px;border-radius:12px;color:#87580f;background:#fff2d6;font-size:10px}.builtin-badge.is-custom{color:#1d6ab3;background:#eaf4ff}.permission-count{color:#5d7488}.role-actions{display:flex;gap:6px}.role-actions button{padding:5px 9px;border:1px solid #d9e3ec;border-radius:4px;color:#4c6c88;background:#fff;cursor:pointer}.role-actions button.is-danger{color:#c14a4a}.role-actions button:disabled{cursor:not-allowed;opacity:.45}.permission-groups{display:flex;flex-direction:column;gap:14px}.permission-groups>.permission-group__title{color:#2e86de;font-size:10px;font-weight:700}.permission-group{border:1px solid #e3eaf1;border-radius:7px;background:#fafbfc;padding:10px 12px}.permission-group__title{margin-bottom:8px;color:#2e86de;font-size:10px;font-weight:700;letter-spacing:.04em}.permission-item{display:flex;align-items:flex-start;gap:9px;padding:7px 8px;border-radius:6px;cursor:pointer}.permission-item:hover{background:#f0f6fc}.permission-item input{margin-top:3px}.permission-item span{display:flex;flex-direction:column;min-width:0}.permission-item strong{color:#33475c;font-size:11px}.permission-item small{color:#8a99a8;font-size:10px;margin-top:2px}.permission-item.is-locked{background:#f1f3f5;cursor:not-allowed;opacity:.8}.role-create-form .form-hint{display:block;margin-top:6px;color:#98a5b3;font-size:10px;font-weight:400;line-height:1.5}.role-error{margin-top:6px;color:#c0392b;font-size:10px;font-weight:500}