refactor: align system admin forms with shadcn fields
This commit is contained in:
@@ -2,6 +2,7 @@ import * as React from 'react'
|
|||||||
|
|
||||||
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
|
import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable'
|
||||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||||
|
import { AppFilterBar } from '@/components/app/AppFilterBar'
|
||||||
import { AppPage } from '@/components/app/AppPage'
|
import { AppPage } from '@/components/app/AppPage'
|
||||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
@@ -82,9 +83,9 @@ export function SystemMenusPage() {
|
|||||||
<AppPage>
|
<AppPage>
|
||||||
<AppPageHeader title="菜单权限" />
|
<AppPageHeader title="菜单权限" />
|
||||||
|
|
||||||
<div className="rounded-lg border border-border bg-surface p-3">
|
<AppFilterBar>
|
||||||
<Input placeholder="搜索菜单、路径或权限码" value={query} onChange={(event) => setQuery(event.target.value)} />
|
<Input placeholder="搜索菜单、路径或权限码" value={query} onChange={(event) => setQuery(event.target.value)} />
|
||||||
</div>
|
</AppFilterBar>
|
||||||
|
|
||||||
{error ? <AppEmptyState title="无法加载菜单" description={error} /> : null}
|
{error ? <AppEmptyState title="无法加载菜单" description={error} /> : null}
|
||||||
{!error ? (
|
{!error ? (
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataT
|
|||||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||||
import { AppPage } from '@/components/app/AppPage'
|
import { AppPage } from '@/components/app/AppPage'
|
||||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||||
|
import { AppSection } from '@/components/app/AppSection'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
|
import { Field, FieldGroup, FieldLabel, FieldLegend, FieldSet } from '@/components/ui/field'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
||||||
import { createRole, listPermissions, listRoles, setRolePermissions } from './api'
|
import { createRole, listPermissions, listRoles, setRolePermissions } from './api'
|
||||||
@@ -75,12 +77,16 @@ export function SystemRolesPage() {
|
|||||||
<AppPage>
|
<AppPage>
|
||||||
<AppPageHeader title="角色权限" />
|
<AppPageHeader title="角色权限" />
|
||||||
|
|
||||||
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[minmax(0,360px)_auto] md:items-end" onSubmit={handleCreate}>
|
<AppSection title="新增角色">
|
||||||
|
<form onSubmit={handleCreate}>
|
||||||
|
<FieldGroup className="grid gap-3 md:grid-cols-[minmax(0,360px)_auto] md:items-end">
|
||||||
<AppField label="角色名称" htmlFor="system-role-name" required>
|
<AppField label="角色名称" htmlFor="system-role-name" required>
|
||||||
<Input id="system-role-name" value={roleName} onChange={(event) => setRoleName(event.target.value)} required />
|
<Input id="system-role-name" value={roleName} onChange={(event) => setRoleName(event.target.value)} required />
|
||||||
</AppField>
|
</AppField>
|
||||||
<Button type="submit" className="w-full md:w-auto">创建角色</Button>
|
<Button type="submit" className="w-full md:w-auto">创建角色</Button>
|
||||||
|
</FieldGroup>
|
||||||
</form>
|
</form>
|
||||||
|
</AppSection>
|
||||||
|
|
||||||
{error ? <AppEmptyState title="无法加载角色" description={error} /> : null}
|
{error ? <AppEmptyState title="无法加载角色" description={error} /> : null}
|
||||||
{!error ? (
|
{!error ? (
|
||||||
@@ -142,12 +148,13 @@ function RolePermissionDialog({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid max-h-[56vh] gap-4 overflow-auto pr-1">
|
<div className="grid max-h-[56vh] gap-4 overflow-auto pr-1">
|
||||||
{Object.entries(groupedPermissions).map(([module, modulePermissions]) => (
|
{Object.entries(groupedPermissions).map(([module, modulePermissions]) => (
|
||||||
<section key={module} className="rounded-md border border-border p-3">
|
<FieldSet key={module} className="rounded-md border border-border p-3">
|
||||||
<p className="mb-3 text-sm font-medium text-foreground">{module}</p>
|
<FieldLegend variant="label">{module}</FieldLegend>
|
||||||
<div className="grid gap-2 md:grid-cols-2">
|
<FieldGroup data-slot="checkbox-group" className="grid gap-2 md:grid-cols-2">
|
||||||
{modulePermissions.map((permission) => (
|
{modulePermissions.map((permission) => (
|
||||||
<label key={permission.codename} className="flex items-start gap-2 text-sm">
|
<Field key={permission.codename} orientation="horizontal">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
id={`role-permission-${permission.codename}`}
|
||||||
checked={selected.includes(permission.codename)}
|
checked={selected.includes(permission.codename)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
setSelected((current) =>
|
setSelected((current) =>
|
||||||
@@ -157,14 +164,14 @@ function RolePermissionDialog({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span>
|
<FieldLabel htmlFor={`role-permission-${permission.codename}`} className="flex-col items-start gap-0.5">
|
||||||
<span className="block text-foreground">{permission.name}</span>
|
<span className="block text-foreground">{permission.name}</span>
|
||||||
<span className="text-xs text-muted-foreground">{permission.codename}</span>
|
<span className="text-xs text-muted-foreground">{permission.codename}</span>
|
||||||
</span>
|
</FieldLabel>
|
||||||
</label>
|
</Field>
|
||||||
))}
|
))}
|
||||||
</div>
|
</FieldGroup>
|
||||||
</section>
|
</FieldSet>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataT
|
|||||||
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
import { AppEmptyState } from '@/components/app/AppEmptyState'
|
||||||
import { AppPage } from '@/components/app/AppPage'
|
import { AppPage } from '@/components/app/AppPage'
|
||||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||||
|
import { AppSection } from '@/components/app/AppSection'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { FieldGroup } from '@/components/ui/field'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { formatDateTime } from '@/lib/format'
|
import { formatDateTime } from '@/lib/format'
|
||||||
@@ -83,7 +85,9 @@ export function SystemUserGroupsPage() {
|
|||||||
<AppPage>
|
<AppPage>
|
||||||
<AppPageHeader title="用户组管理" />
|
<AppPageHeader title="用户组管理" />
|
||||||
|
|
||||||
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[1fr_1fr_2fr_auto] md:items-end" onSubmit={handleCreate}>
|
<AppSection title="新增用户组">
|
||||||
|
<form onSubmit={handleCreate}>
|
||||||
|
<FieldGroup className="grid gap-3 md:grid-cols-[1fr_1fr_2fr_auto] md:items-end">
|
||||||
<AppField label="用户组名称" htmlFor="system-group-name" required>
|
<AppField label="用户组名称" htmlFor="system-group-name" required>
|
||||||
<Input id="system-group-name" value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} required />
|
<Input id="system-group-name" value={form.name} onChange={(event) => setForm({ ...form, name: event.target.value })} required />
|
||||||
</AppField>
|
</AppField>
|
||||||
@@ -94,7 +98,9 @@ export function SystemUserGroupsPage() {
|
|||||||
<Input id="system-group-description" value={form.description} onChange={(event) => setForm({ ...form, description: event.target.value })} />
|
<Input id="system-group-description" value={form.description} onChange={(event) => setForm({ ...form, description: event.target.value })} />
|
||||||
</AppField>
|
</AppField>
|
||||||
<Button type="submit" className="w-full md:w-auto">创建用户组</Button>
|
<Button type="submit" className="w-full md:w-auto">创建用户组</Button>
|
||||||
|
</FieldGroup>
|
||||||
</form>
|
</form>
|
||||||
|
</AppSection>
|
||||||
|
|
||||||
{error ? <AppEmptyState title="无法加载用户组" description={error} /> : null}
|
{error ? <AppEmptyState title="无法加载用户组" description={error} /> : null}
|
||||||
{!error ? (
|
{!error ? (
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { AppEmptyState } from '@/components/app/AppEmptyState'
|
|||||||
import { AppFilterBar } from '@/components/app/AppFilterBar'
|
import { AppFilterBar } from '@/components/app/AppFilterBar'
|
||||||
import { AppPage } from '@/components/app/AppPage'
|
import { AppPage } from '@/components/app/AppPage'
|
||||||
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
import { AppPageHeader } from '@/components/app/AppPageHeader'
|
||||||
|
import { AppSection } from '@/components/app/AppSection'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
|
import { Field, FieldGroup, FieldLabel, FieldLegend, FieldSet } from '@/components/ui/field'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Label } from '@/components/ui/label'
|
|
||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
|
|
||||||
import { createUser, listRoles, listUserGroups, listUsers, setUserGroups, setUserRoles, updateUser } from './api'
|
import { createUser, listRoles, listUserGroups, listUsers, setUserGroups, setUserRoles, updateUser } from './api'
|
||||||
@@ -142,7 +143,9 @@ export function SystemUsersPage() {
|
|||||||
<AppPage>
|
<AppPage>
|
||||||
<AppPageHeader title="用户管理" />
|
<AppPageHeader title="用户管理" />
|
||||||
|
|
||||||
<form className="grid gap-3 rounded-lg border border-border bg-surface p-3 md:grid-cols-[1fr_1fr_1fr_1fr_auto] md:items-end" onSubmit={handleCreateUser}>
|
<AppSection title="新增用户">
|
||||||
|
<form onSubmit={handleCreateUser}>
|
||||||
|
<FieldGroup className="grid gap-3 md:grid-cols-[1fr_1fr_1fr_1fr_auto] md:items-end">
|
||||||
<AppField label="账号" htmlFor="system-user-username" required>
|
<AppField label="账号" htmlFor="system-user-username" required>
|
||||||
<Input id="system-user-username" value={form.username} onChange={(event) => setForm({ ...form, username: event.target.value })} required />
|
<Input id="system-user-username" value={form.username} onChange={(event) => setForm({ ...form, username: event.target.value })} required />
|
||||||
</AppField>
|
</AppField>
|
||||||
@@ -156,7 +159,9 @@ export function SystemUsersPage() {
|
|||||||
<Input id="system-user-password" type="password" value={form.password} onChange={(event) => setForm({ ...form, password: event.target.value })} required />
|
<Input id="system-user-password" type="password" value={form.password} onChange={(event) => setForm({ ...form, password: event.target.value })} required />
|
||||||
</AppField>
|
</AppField>
|
||||||
<Button type="submit" className="w-full md:w-auto">创建用户</Button>
|
<Button type="submit" className="w-full md:w-auto">创建用户</Button>
|
||||||
|
</FieldGroup>
|
||||||
</form>
|
</form>
|
||||||
|
</AppSection>
|
||||||
|
|
||||||
<AppFilterBar>
|
<AppFilterBar>
|
||||||
<Input placeholder="搜索账号、姓名或邮箱" value={query} onChange={(event) => setQuery(event.target.value)} />
|
<Input placeholder="搜索账号、姓名或邮箱" value={query} onChange={(event) => setQuery(event.target.value)} />
|
||||||
@@ -219,38 +224,40 @@ function UserAccessDialog({
|
|||||||
<DialogTitle>用户授权{user ? `:${user.display_name || user.username}` : ''}</DialogTitle>
|
<DialogTitle>用户授权{user ? `:${user.display_name || user.username}` : ''}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div className="grid gap-2">
|
<FieldSet>
|
||||||
<Label>角色</Label>
|
<FieldLegend>角色</FieldLegend>
|
||||||
<div className="grid max-h-64 gap-2 overflow-auto rounded-md border border-border p-3">
|
<FieldGroup data-slot="checkbox-group" className="grid max-h-64 gap-2 overflow-auto rounded-md border border-border p-3">
|
||||||
{roles.map((role) => (
|
{roles.map((role) => (
|
||||||
<label key={role.id} className="flex items-center gap-2 text-sm">
|
<Field key={role.id} orientation="horizontal">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
id={`access-role-${role.id}`}
|
||||||
checked={roleIds.includes(role.id)}
|
checked={roleIds.includes(role.id)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
setRoleIds((current) => checked ? [...current, role.id] : current.filter((id) => id !== role.id))
|
setRoleIds((current) => checked ? [...current, role.id] : current.filter((id) => id !== role.id))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span>{role.name}</span>
|
<FieldLabel htmlFor={`access-role-${role.id}`}>{role.name}</FieldLabel>
|
||||||
</label>
|
</Field>
|
||||||
))}
|
))}
|
||||||
</div>
|
</FieldGroup>
|
||||||
</div>
|
</FieldSet>
|
||||||
<div className="grid gap-2">
|
<FieldSet>
|
||||||
<Label>用户组</Label>
|
<FieldLegend>用户组</FieldLegend>
|
||||||
<div className="grid max-h-64 gap-2 overflow-auto rounded-md border border-border p-3">
|
<FieldGroup data-slot="checkbox-group" className="grid max-h-64 gap-2 overflow-auto rounded-md border border-border p-3">
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<label key={group.id} className="flex items-center gap-2 text-sm">
|
<Field key={group.id} orientation="horizontal">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
id={`access-group-${group.id}`}
|
||||||
checked={groupIds.includes(group.id)}
|
checked={groupIds.includes(group.id)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
setGroupIds((current) => checked ? [...current, group.id] : current.filter((id) => id !== group.id))
|
setGroupIds((current) => checked ? [...current, group.id] : current.filter((id) => id !== group.id))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span>{group.name}</span>
|
<FieldLabel htmlFor={`access-group-${group.id}`}>{group.name}</FieldLabel>
|
||||||
</label>
|
</Field>
|
||||||
))}
|
))}
|
||||||
</div>
|
</FieldGroup>
|
||||||
</div>
|
</FieldSet>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={onClose}>取消</Button>
|
<Button variant="outline" onClick={onClose}>取消</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user