unis_sip/oms_web/docs/components/DetailDrawer.md

12 KiB
Raw Blame History

DetailDrawer 组件

组件说明

详情抽屉组件,用于从页面右侧滑出显示详细信息。支持自定义标题、操作按钮、标签页等功能,内容区域可滚动,顶部标题栏固定。

组件位置

src/components/DetailDrawer/DetailDrawer.jsx
src/components/DetailDrawer/DetailDrawer.css

参数说明

参数名 类型 必填 默认值 说明
visible boolean 是 - 是否显示抽屉
onClose function 是 - 关闭抽屉回调
title TitleConfig 否 - 标题配置对象
headerActions Array 否 [] 顶部操作按钮数组
width number 否 1080 抽屉宽度(像素)
children ReactNode 否 - 主要内容区域
tabs Array 否 - 标签页配置数组

1. 小型抽屉 (Small) - 480px

适用场景:

  • 简单的信息展示
  • 少量字段的表单(1-3个字段)
  • 快速操作面板
  • 通知详情

示例:

<Drawer width={480} ... />

2. 中型抽屉 (Medium) - 720px

适用场景:

  • 详细信息展示(如主机详情)
  • 中等复杂度的表单(4-10个字段)
  • 数据编辑面板
  • 配置设置

示例:

<Drawer width={720} ... />

当前主机列表页面使用此宽度模式

3. 大型抽屉 (Large) - 1080px

适用场景:

  • 复杂的多步骤表单
  • 需要并排展示多列信息
  • 包含图表或复杂可视化内容
  • 嵌套子表格或列表

示例:

<Drawer width={1080} ... />

TitleConfig 配置项

属性名 类型 必填 说明
text string 是 标题文本
badge ReactNode 否 状态徽标(如 Tag、Badge 组件)
icon ReactNode 否 标题图标

ActionConfig 配置项

属性名 类型 必填 说明
key string 是 按钮唯一标识
label string 是 按钮文本
icon ReactNode 否 按钮图标
type string 否 按钮类型(primary/default/dashed/text/link)
danger boolean 否 是否为危险按钮
disabled boolean 否 是否禁用
onClick function 否 点击回调函数

TabConfig 配置项

属性名 类型 必填 说明
key string 是 标签页唯一标识
label ReactNode 是 标签页标题(支持图标+文字)
content ReactNode 是 标签页内容

使用示例

基础用法

import { useState } from 'react'
import DetailDrawer from '../components/DetailDrawer/DetailDrawer'

function MyPage() {
  const [showDrawer, setShowDrawer] = useState(false)
  const [selectedItem, setSelectedItem] = useState(null)

  return (
    <>
      <Button onClick={() => setShowDrawer(true)}>查看详情</Button>

      <DetailDrawer
        visible={showDrawer}
        onClose={() => setShowDrawer(false)}
        title={{
          text: selectedItem?.name || '详情',
        }}
      >
        <div style={{ padding: 24 }}>
          <p>详情内容</p>
        </div>
      </DetailDrawer>
    </>
  )
}

带状态徽标

import { Tag, Badge } from 'antd'

<DetailDrawer
  visible={showDetailDrawer}
  onClose={() => setShowDetailDrawer(false)}
  title={{
    text: selectedUser?.userName || '',
    badge: (
      <Tag color={selectedUser?.status === 'enabled' ? 'green' : 'default'}>
        {selectedUser?.status === 'enabled' ? '启用' : '停用'}
      </Tag>
    ),
  }}
>
  {/* 内容 */}
</DetailDrawer>

带操作按钮

import { EditOutlined, DeleteOutlined } from '@ant-design/icons'

<DetailDrawer
  visible={showDetailDrawer}
  onClose={() => setShowDetailDrawer(false)}
  title={{
    text: selectedUser?.userName || '',
  }}
  headerActions={[
    {
      key: 'edit',
      label: '编辑',
      icon: <EditOutlined />,
      onClick: () => {
        setEditMode('edit')
        setShowEditDrawer(true)
        setShowDetailDrawer(false)
      },
    },
    {
      key: 'delete',
      label: '删除',
      icon: <DeleteOutlined />,
      danger: true,
      onClick: () => {
        setShowDetailDrawer(false)
        handleDelete(selectedUser)
      },
    },
  ]}
>
  {/* 内容 */}
</DetailDrawer>

使用 InfoPanel 显示信息

import DetailDrawer from '../components/DetailDrawer/DetailDrawer'
import InfoPanel from '../components/InfoPanel/InfoPanel'

const userFields = [
  { key: 'userName', label: '用户名', span: 6 },
  { key: 'group', label: '用户分组', span: 6 },
  { key: 'name', label: '姓名', span: 6 },
  { key: 'userType', label: '用户类型', span: 6 },
  {
    key: 'status',
    label: '状态',
    span: 6,
    render: (value) => (
      <Tag color={value === 'enabled' ? 'green' : 'default'}>
        {value === 'enabled' ? '启用' : '停用'}
      </Tag>
    ),
  },
]

<DetailDrawer
  visible={showDetailDrawer}
  onClose={() => setShowDetailDrawer(false)}
  title={{
    text: selectedUser?.userName || '',
  }}
>
  <InfoPanel
    data={selectedUser}
    fields={userFields}
    actions={[
      { key: 'reset', label: '重置密码', onClick: () => console.log('重置密码') },
      { key: 'disable', label: '停用', onClick: () => console.log('停用') },
    ]}
  />
</DetailDrawer>

带标签页

import { DatabaseOutlined, UserOutlined } from '@ant-design/icons'

<DetailDrawer
  visible={showDetailDrawer}
  onClose={() => setShowDetailDrawer(false)}
  title={{
    text: selectedHost?.name || '',
    badge: (
      <Badge
        status={selectedHost?.status === 'online' ? 'success' : 'default'}
        text={selectedHost?.status === 'online' ? '在线' : '离线'}
      />
    ),
  }}
  headerActions={[
    {
      key: 'edit',
      label: '编辑',
      icon: <EditOutlined />,
      onClick: handleEdit,
    },
  ]}
  width={1080}
  tabs={[
    {
      key: 'images',
      label: (
        <span>
          <DatabaseOutlined style={{ marginRight: 8 }} />
          终端镜像
        </span>
      ),
      content: (
        <div className="card-list">
          {/* 镜像列表内容 */}
        </div>
      ),
    },
    {
      key: 'users',
      label: (
        <span>
          <UserOutlined style={{ marginRight: 8 }} />
          终端用户
        </span>
      ),
      content: (
        <div className="card-list">
          {/* 用户列表内容 */}
        </div>
      ),
    },
  ]}
>
  <InfoPanel data={selectedHost} fields={hostFields} />
</DetailDrawer>

完整示例

import { useState } from 'react'
import DetailDrawer from '../components/DetailDrawer/DetailDrawer'
import InfoPanel from '../components/InfoPanel/InfoPanel'
import { Tag, Badge, Card } from 'antd'
import { EditOutlined, DeleteOutlined, DatabaseOutlined, UserOutlined } from '@ant-design/icons'

function UserListPage() {
  const [showDetailDrawer, setShowDetailDrawer] = useState(false)
  const [selectedUser, setSelectedUser] = useState(null)

  const userFields = [
    { key: 'userName', label: '用户名', span: 6 },
    { key: 'group', label: '用户分组', span: 6 },
    { key: 'name', label: '姓名', span: 6 },
    { key: 'grantedImages', label: '授权镜像', span: 6 },
    { key: 'userType', label: '用户类型', span: 6 },
    { key: 'grantedTerminals', label: '授权终端', span: 6 },
    {
      key: 'status',
      label: '启停用',
      span: 6,
      render: (value) => (
        <Tag color={value === 'enabled' ? 'green' : 'default'}>
          {value === 'enabled' ? '启用' : '停用'}
        </Tag>
      ),
    },
  ]

  const detailTabs = [
    {
      key: 'terminals',
      label: (
        <span>
          <DesktopOutlined style={{ marginRight: 8 }} />
          授权终端
        </span>
      ),
      content: (
        <div className="card-list">
          {selectedUser?.terminals?.map((terminal) => (
            <Card key={terminal.id}>
              <h4>{terminal.name}</h4>
              <p>IP: {terminal.ip}</p>
            </Card>
          ))}
        </div>
      ),
    },
    {
      key: 'images',
      label: (
        <span>
          <DatabaseOutlined style={{ marginRight: 8 }} />
          授权镜像
        </span>
      ),
      content: (
        <div className="card-list">
          {selectedUser?.images?.map((image) => (
            <Card key={image.id}>
              <h4>{image.name}</h4>
              <p>系统: {image.os}</p>
            </Card>
          ))}
        </div>
      ),
    },
  ]

  return (
    <>
      {/* 列表页面 */}
      <ListTable
        columns={columns}
        dataSource={users}
        onRowClick={(record) => {
          setSelectedUser(record)
          setShowDetailDrawer(true)
        }}
      />

      {/* 详情抽屉 */}
      <DetailDrawer
        visible={showDetailDrawer}
        onClose={() => setShowDetailDrawer(false)}
        title={{
          text: selectedUser?.userName || '',
          badge: (
            <Tag color={selectedUser?.status === 'enabled' ? 'green' : 'default'}>
              {selectedUser?.status === 'enabled' ? '启用' : '停用'}
            </Tag>
          ),
        }}
        headerActions={[
          {
            key: 'edit',
            label: '编辑',
            icon: <EditOutlined />,
            onClick: () => {
              setShowDetailDrawer(false)
              handleEdit(selectedUser)
            },
          },
          {
            key: 'delete',
            label: '删除',
            icon: <DeleteOutlined />,
            danger: true,
            onClick: () => {
              setShowDetailDrawer(false)
              handleDelete(selectedUser)
            },
          },
        ]}
        width={1080}
        tabs={detailTabs}
      >
        <InfoPanel
          data={selectedUser}
          fields={userFields}
          actions={[
            { key: 'move', label: '转移分组', type: 'primary', onClick: () => console.log('转移分组') },
            { key: 'reset', label: '重置密码', onClick: () => console.log('重置密码') },
            { key: 'disable', label: '停用', onClick: () => console.log('停用') },
          ]}
        />
      </DetailDrawer>
    </>
  )
}

布局结构

抽屉采用固定头部、可滚动内容的布局:

┌─────────────────────────────────────┐
│ 标题栏(固定,不滚动)                  │
│ [关闭] [标题] [徽标]    [操作按钮]      │
├─────────────────────────────────────┤
│                                     │
│ 内容区域(可滚动)                      │
│ - children 主要内容                   │
│ - tabs 标签页(可选)                  │
│                                     │
└─────────────────────────────────────┘

样式定制

组件提供以下 CSS 类名供自定义样式:

  • .detail-drawer-content - 抽屉内容容器
  • .detail-drawer-header - 顶部标题栏
  • .detail-drawer-header-left - 标题栏左侧区域
  • .detail-drawer-header-right - 标题栏右侧区域
  • .detail-drawer-close-button - 关闭按钮
  • .detail-drawer-header-info - 标题信息容器
  • .detail-drawer-title-icon - 标题图标
  • .detail-drawer-title - 标题文本
  • .detail-drawer-badge - 徽标容器
  • .detail-drawer-scrollable-content - 可滚动内容区域
  • .detail-drawer-tabs - 标签页容器
  • .detail-drawer-tab-content - 标签页内容

使用场景

  1. 查看详细信息 - 点击列表行显示详细信息
  2. 多标签页详情 - 在详情中展示不同类型的关联数据
  3. 带快捷操作的详情 - 在详情顶部提供编辑、删除等操作
  4. 复杂数据展示 - 配合 InfoPanel、Card 等组件展示复杂信息

注意事项

  1. 抽屉宽度默认 1080px,可根据内容调整,建议取值范围:720-1200px
  2. 标题栏固定在顶部,不随内容滚动,确保操作按钮始终可见
  3. children 内容区域会自动应用内边距,tabs 内容需要自行控制样式
  4. 操作按钮数量不宜过多,建议不超过 3 个
  5. 使用 tabs 时,第一个标签页默认激活
  6. 关闭抽屉时建议清空选中状态,避免下次打开时显示旧数据
  7. 配合 InfoPanel 使用时,InfoPanel 会自动处理内边距