开发预览 更新于 2026-05-10

Drawer 抽屉

边缘锚定的滑入面板 —— 4 个方向、tone 变体、可缩放、异步确认、不锁背景模式、命令式服务、多抽屉栈管理。

基础用法

v-model:open (Vue) / open + onOpenChange (React) 双向绑定。Drawer 共用 Modal 的 portal / focus trap / body 滚动锁 / Esc 关闭基础设施。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开抽屉</CfButton>
  <CfDrawer v-model:open="open" title="基础抽屉">
    <p style="margin: 0;">从右侧滑入。按 Esc 或点击遮罩关闭。</p>
    <template #footer>
      <CfButton variant="ghost" @click="open = false">取消</CfButton>
      <CfButton @click="open = false">确定</CfButton>
    </template>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开抽屉</CfButton>
  <CfDrawer v-model:open="open" title="基础抽屉">
    <p style="margin: 0;">从右侧滑入。按 Esc 或点击遮罩关闭。</p>
    <template #footer>
      <CfButton variant="ghost" @click="open = false">取消</CfButton>
      <CfButton @click="open = false">确定</CfButton>
    </template>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开抽屉</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="基础抽屉">
          <p style={{ margin: 0 }}>从右侧滑入。按 Esc 或点击遮罩关闭。</p>
            <CfButton variant="ghost" onClick={() => setOpen(false)}>取消</CfButton>
            <CfButton onClick={() => setOpen(false)}>确定</CfButton>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开抽屉</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="基础抽屉">
          <p style={{ margin: 0 }}>从右侧滑入。按 Esc 或点击遮罩关闭。</p>
            <CfButton variant="ghost" onClick={() => setOpen(false)}>取消</CfButton>
            <CfButton onClick={() => setOpen(false)}>确定</CfButton>
    </>
  );
}

4 种滑入方向

placement 决定从哪一边滑入 — 右(默认,最常见的详情侧栏)/ 左(移动端导航)/ 顶 / 底(playground 风格的临时操作)。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const right = ref(false);
const left = ref(false);
const top = ref(false);
const bottom = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="right = true">从右侧</CfButton>
    <CfButton variant="secondary" @click="left = true">从左侧</CfButton>
    <CfButton variant="secondary" @click="top = true">从顶部</CfButton>
    <CfButton variant="secondary" @click="bottom = true">从底部</CfButton>
  </div>
  <CfDrawer v-model:open="right" title="右侧抽屉" placement="right" />
  <CfDrawer v-model:open="left" title="左侧抽屉" placement="left" />
  <CfDrawer v-model:open="top" title="顶部抽屉" placement="top" />
  <CfDrawer v-model:open="bottom" title="底部抽屉" placement="bottom" />
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const right = ref(false);
const left = ref(false);
const top = ref(false);
const bottom = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="right = true">从右侧</CfButton>
    <CfButton variant="secondary" @click="left = true">从左侧</CfButton>
    <CfButton variant="secondary" @click="top = true">从顶部</CfButton>
    <CfButton variant="secondary" @click="bottom = true">从底部</CfButton>
  </div>
  <CfDrawer v-model:open="right" title="右侧抽屉" placement="right" />
  <CfDrawer v-model:open="left" title="左侧抽屉" placement="left" />
  <CfDrawer v-model:open="top" title="顶部抽屉" placement="top" />
  <CfDrawer v-model:open="bottom" title="底部抽屉" placement="bottom" />
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [right, setRight] = useState(false);
  const [left, setLeft] = useState(false);
  const [top, setTop] = useState(false);
  const [bottom, setBottom] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={() => setRight(true)}>从右侧</CfButton>
          <CfButton variant="secondary" onClick={() => setLeft(true)}>从左侧</CfButton>
          <CfButton variant="secondary" onClick={() => setTop(true)}>从顶部</CfButton>
          <CfButton variant="secondary" onClick={() => setBottom(true)}>从底部</CfButton>
        </div>
        <CfDrawer open={right} onOpenChange={setRight} title="右侧抽屉" placement="right" />
        <CfDrawer open={left} onOpenChange={setLeft} title="左侧抽屉" placement="left" />
        <CfDrawer open={top} onOpenChange={setTop} title="顶部抽屉" placement="top" />
        <CfDrawer open={bottom} onOpenChange={setBottom} title="底部抽屉" placement="bottom" />
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [right, setRight] = useState(false);
  const [left, setLeft] = useState(false);
  const [top, setTop] = useState(false);
  const [bottom, setBottom] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={() => setRight(true)}>从右侧</CfButton>
          <CfButton variant="secondary" onClick={() => setLeft(true)}>从左侧</CfButton>
          <CfButton variant="secondary" onClick={() => setTop(true)}>从顶部</CfButton>
          <CfButton variant="secondary" onClick={() => setBottom(true)}>从底部</CfButton>
        </div>
        <CfDrawer open={right} onOpenChange={setRight} title="右侧抽屉" placement="right" />
        <CfDrawer open={left} onOpenChange={setLeft} title="左侧抽屉" placement="left" />
        <CfDrawer open={top} onOpenChange={setTop} title="顶部抽屉" placement="top" />
        <CfDrawer open={bottom} onOpenChange={setBottom} title="底部抽屉" placement="bottom" />
    </>
  );
}

5 档尺寸

size 控制抽屉宽度(左右 placement)或高度(上下 placement) — sm / md(默认)/ lg / xl / full(全屏)。width / height 也可以传具体像素值覆盖档位。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const sm = ref(false);
const md = ref(false);
const lg = ref(false);
const xl = ref(false);
const full = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="sm = true">sm</CfButton>
    <CfButton @click="md = true">md</CfButton>
    <CfButton @click="lg = true">lg</CfButton>
    <CfButton @click="xl = true">xl</CfButton>
    <CfButton @click="full = true">full</CfButton>
  </div>
  <CfDrawer v-model:open="sm" title="size = sm" size="sm" />
  <CfDrawer v-model:open="md" title="size = md" size="md" />
  <CfDrawer v-model:open="lg" title="size = lg" size="lg" />
  <CfDrawer v-model:open="xl" title="size = xl" size="xl" />
  <CfDrawer v-model:open="full" title="size = full" size="full" />
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const sm = ref(false);
const md = ref(false);
const lg = ref(false);
const xl = ref(false);
const full = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="sm = true">sm</CfButton>
    <CfButton @click="md = true">md</CfButton>
    <CfButton @click="lg = true">lg</CfButton>
    <CfButton @click="xl = true">xl</CfButton>
    <CfButton @click="full = true">full</CfButton>
  </div>
  <CfDrawer v-model:open="sm" title="size = sm" size="sm" />
  <CfDrawer v-model:open="md" title="size = md" size="md" />
  <CfDrawer v-model:open="lg" title="size = lg" size="lg" />
  <CfDrawer v-model:open="xl" title="size = xl" size="xl" />
  <CfDrawer v-model:open="full" title="size = full" size="full" />
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [sm, setSm] = useState(false);
  const [md, setMd] = useState(false);
  const [lg, setLg] = useState(false);
  const [xl, setXl] = useState(false);
  const [full, setFull] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={() => setSm(true)}>sm</CfButton>
          <CfButton onClick={() => setMd(true)}>md</CfButton>
          <CfButton onClick={() => setLg(true)}>lg</CfButton>
          <CfButton onClick={() => setXl(true)}>xl</CfButton>
          <CfButton onClick={() => setFull(true)}>full</CfButton>
        </div>
        <CfDrawer open={sm} onOpenChange={setSm} title="size = sm" size="sm" />
        <CfDrawer open={md} onOpenChange={setMd} title="size = md" size="md" />
        <CfDrawer open={lg} onOpenChange={setLg} title="size = lg" size="lg" />
        <CfDrawer open={xl} onOpenChange={setXl} title="size = xl" size="xl" />
        <CfDrawer open={full} onOpenChange={setFull} title="size = full" size="full" />
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [sm, setSm] = useState(false);
  const [md, setMd] = useState(false);
  const [lg, setLg] = useState(false);
  const [xl, setXl] = useState(false);
  const [full, setFull] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={() => setSm(true)}>sm</CfButton>
          <CfButton onClick={() => setMd(true)}>md</CfButton>
          <CfButton onClick={() => setLg(true)}>lg</CfButton>
          <CfButton onClick={() => setXl(true)}>xl</CfButton>
          <CfButton onClick={() => setFull(true)}>full</CfButton>
        </div>
        <CfDrawer open={sm} onOpenChange={setSm} title="size = sm" size="sm" />
        <CfDrawer open={md} onOpenChange={setMd} title="size = md" size="md" />
        <CfDrawer open={lg} onOpenChange={setLg} title="size = lg" size="lg" />
        <CfDrawer open={xl} onOpenChange={setXl} title="size = xl" size="xl" />
        <CfDrawer open={full} onOpenChange={setFull} title="size = full" size="full" />
    </>
  );
}

Tone 变体

tone="info | success | warning | error" 给 header 加一个圆形 tone 图标 + 强调色。tone="error" 时默认 OK 按钮自动切到 danger 红。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const info = ref(false);
const success = ref(false);
const warning = ref(false);
const error = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton variant="tertiary" @click="info = true">info</CfButton>
    <CfButton variant="tertiary" @click="success = true">success</CfButton>
    <CfButton variant="tertiary" @click="warning = true">warning</CfButton>
    <CfButton variant="tertiary" @click="error = true">error</CfButton>
  </div>
  <CfDrawer
    v-model:open="info"
    tone="info"
    title="新版本上线"
    description="0.2.0 — 拖拽面板、tone 变体、命令式 service。"
    ok-text="知道了"
  >
    <p style="margin: 0;">使用 tone 在 header 上自动加圆形语义图标。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="success"
    tone="success"
    title="部署成功"
    description="cd-12 已发布到生产环境"
    ok-text="完成"
  >
    <p style="margin: 0;">tone="success" 适合状态提示型抽屉。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="warning"
    tone="warning"
    title="存在未保存的修改"
    description="离开前请先保存草稿"
    ok-text="保存并离开"
    cancel-text="取消"
  >
    <p style="margin: 0;">两侧按钮 + warning 头像。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="error"
    tone="error"
    title="即将永久删除项目"
    description="该操作不可撤销。"
    ok-text="删除"
    cancel-text="取消"
  >
    <p style="margin: 0;">tone="error" 时默认 OK 按钮自动切到 danger 红色。</p>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const info = ref(false);
const success = ref(false);
const warning = ref(false);
const error = ref(false);
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton variant="tertiary" @click="info = true">info</CfButton>
    <CfButton variant="tertiary" @click="success = true">success</CfButton>
    <CfButton variant="tertiary" @click="warning = true">warning</CfButton>
    <CfButton variant="tertiary" @click="error = true">error</CfButton>
  </div>
  <CfDrawer
    v-model:open="info"
    tone="info"
    title="新版本上线"
    description="0.2.0 — 拖拽面板、tone 变体、命令式 service。"
    ok-text="知道了"
  >
    <p style="margin: 0;">使用 tone 在 header 上自动加圆形语义图标。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="success"
    tone="success"
    title="部署成功"
    description="cd-12 已发布到生产环境"
    ok-text="完成"
  >
    <p style="margin: 0;">tone="success" 适合状态提示型抽屉。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="warning"
    tone="warning"
    title="存在未保存的修改"
    description="离开前请先保存草稿"
    ok-text="保存并离开"
    cancel-text="取消"
  >
    <p style="margin: 0;">两侧按钮 + warning 头像。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="error"
    tone="error"
    title="即将永久删除项目"
    description="该操作不可撤销。"
    ok-text="删除"
    cancel-text="取消"
  >
    <p style="margin: 0;">tone="error" 时默认 OK 按钮自动切到 danger 红色。</p>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [info, setInfo] = useState(false);
  const [success, setSuccess] = useState(false);
  const [warning, setWarning] = useState(false);
  const [error, setError] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton variant="tertiary" onClick={() => setInfo(true)}>info</CfButton>
          <CfButton variant="tertiary" onClick={() => setSuccess(true)}>success</CfButton>
          <CfButton variant="tertiary" onClick={() => setWarning(true)}>warning</CfButton>
          <CfButton variant="tertiary" onClick={() => setError(true)}>error</CfButton>
        </div>
        <CfDrawer open={info} onOpenChange={setInfo} tone="info" title="新版本上线" description="0.2.0 — 拖拽面板、tone 变体、命令式 service。" ok-text="知道了" >
          <p style={{ margin: 0 }}>使用 tone 在 header 上自动加圆形语义图标。</p>
        </CfDrawer>
        <CfDrawer open={success} onOpenChange={setSuccess} tone="success" title="部署成功" description="cd-12 已发布到生产环境" ok-text="完成" >
          <p style={{ margin: 0 }}>tone="success" 适合状态提示型抽屉。</p>
        </CfDrawer>
        <CfDrawer open={warning} onOpenChange={setWarning} tone="warning" title="存在未保存的修改" description="离开前请先保存草稿" ok-text="保存并离开" cancel-text="取消" >
          <p style={{ margin: 0 }}>两侧按钮 + warning 头像。</p>
        </CfDrawer>
        <CfDrawer open={error} onOpenChange={setError} tone="error" title="即将永久删除项目" description="该操作不可撤销。" ok-text="删除" cancel-text="取消" >
          <p style={{ margin: 0 }}>tone="error" 时默认 OK 按钮自动切到 danger 红色。</p>
        </CfDrawer>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [info, setInfo] = useState(false);
  const [success, setSuccess] = useState(false);
  const [warning, setWarning] = useState(false);
  const [error, setError] = useState(false);
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton variant="tertiary" onClick={() => setInfo(true)}>info</CfButton>
          <CfButton variant="tertiary" onClick={() => setSuccess(true)}>success</CfButton>
          <CfButton variant="tertiary" onClick={() => setWarning(true)}>warning</CfButton>
          <CfButton variant="tertiary" onClick={() => setError(true)}>error</CfButton>
        </div>
        <CfDrawer open={info} onOpenChange={setInfo} tone="info" title="新版本上线" description="0.2.0 — 拖拽面板、tone 变体、命令式 service。" ok-text="知道了" >
          <p style={{ margin: 0 }}>使用 tone 在 header 上自动加圆形语义图标。</p>
        </CfDrawer>
        <CfDrawer open={success} onOpenChange={setSuccess} tone="success" title="部署成功" description="cd-12 已发布到生产环境" ok-text="完成" >
          <p style={{ margin: 0 }}>tone="success" 适合状态提示型抽屉。</p>
        </CfDrawer>
        <CfDrawer open={warning} onOpenChange={setWarning} tone="warning" title="存在未保存的修改" description="离开前请先保存草稿" ok-text="保存并离开" cancel-text="取消" >
          <p style={{ margin: 0 }}>两侧按钮 + warning 头像。</p>
        </CfDrawer>
        <CfDrawer open={error} onOpenChange={setError} tone="error" title="即将永久删除项目" description="该操作不可撤销。" ok-text="删除" cancel-text="取消" >
          <p style={{ margin: 0 }}>tone="error" 时默认 OK 按钮自动切到 danger 红色。</p>
        </CfDrawer>
    </>
  );
}

内置 OK / Cancel + 异步确认

直接传 okText / cancelText,组件渲染默认按钮组。onBeforeOk 接异步函数:返回 false / 抛错 → 阻止关闭并恢复 loading 态;正常 resolve → 关闭并发 @ok 事件。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer, CfInput, toast } from '@chufix-design/vue';

const open = ref(false);
const name = ref('');

function onBeforeOk(): Promise<boolean | void> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (name.value.trim().length < 2) {
        toast({ type: 'error', message: '名称至少 2 个字符' });
        reject(new Error('invalid'));
        return;
      }
      toast({ type: 'success', message: `已保存:${name.value}` });
      resolve();
    }, 900);
  });
}
</script>
<template>
  <CfButton @click="open = true">编辑配置</CfButton>
  <CfDrawer
    v-model:open="open"
    placement="right"
    size="md"
    title="编辑配置"
    description="保存按钮接异步 onBeforeOk —— 期间所有关闭路径屏蔽。"
    ok-text="保存"
    cancel-text="取消"
    :on-before-ok="onBeforeOk"
    @ok="open = false; name = ''"
  >
    <div style="display: grid; gap: 8px;">
      <label style="font-size: 12px; color: var(--fg-3);">名称</label>
      <CfInput v-model="name" placeholder="输入≥2字符" />
    </div>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer, CfInput, toast } from '@chufix-design/vue';

const open = ref(false);
const name = ref('');

function onBeforeOk(): Promise<boolean | void> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (name.value.trim().length < 2) {
        toast({ type: 'error', message: '名称至少 2 个字符' });
        reject(new Error('invalid'));
        return;
      }
      toast({ type: 'success', message: `已保存:${name.value}` });
      resolve();
    }, 900);
  });
}
</script>
<template>
  <CfButton @click="open = true">编辑配置</CfButton>
  <CfDrawer
    v-model:open="open"
    placement="right"
    size="md"
    title="编辑配置"
    description="保存按钮接异步 onBeforeOk —— 期间所有关闭路径屏蔽。"
    ok-text="保存"
    cancel-text="取消"
    :on-before-ok="onBeforeOk"
    @ok="open = false; name = ''"
  >
    <div style="display: grid; gap: 8px;">
      <label style="font-size: 12px; color: var(--fg-3);">名称</label>
      <CfInput v-model="name" placeholder="输入≥2字符" />
    </div>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer, CfInput } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  const [name, setName] = useState('');

  function onBeforeOk(): Promise<boolean | void> {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (name.trim().length < 2) {
          toast({ type: 'error', message: '名称至少 2 个字符' });
          reject(new Error('invalid'));
          return;
        }
        toast({ type: 'success', message: `已保存:${name}` });
        resolve();
      }, 900);
    });
  }
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>编辑配置</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} placement="right" size="md" title="编辑配置" description="保存按钮接异步 onBeforeOk —— 期间所有关闭路径屏蔽。" ok-text="保存" cancel-text="取消" onBeforeOk={onBeforeOk} onOk={() => setOpen(false); setName('')}
        >
          <div style={{ display: "grid", gap: 8 }}>
            <label style={{ fontSize: 12, color: "var(--fg-3)" }}>名称</label>
            <CfInput value={name} onChange={setName} placeholder="输入≥2字符" />
          </div>
        </CfDrawer>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer, CfInput } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  const [name, setName] = useState('');

  function onBeforeOk(): Promise<boolean | void> {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (name.trim().length < 2) {
          toast({ type: 'error', message: '名称至少 2 个字符' });
          reject(new Error('invalid'));
          return;
        }
        toast({ type: 'success', message: `已保存:${name}` });
        resolve();
      }, 900);
    });
  }
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>编辑配置</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} placement="right" size="md" title="编辑配置" description="保存按钮接异步 onBeforeOk —— 期间所有关闭路径屏蔽。" ok-text="保存" cancel-text="取消" onBeforeOk={onBeforeOk} onOk={() => setOpen(false); setName('')}
        >
          <div style={{ display: "grid", gap: 8 }}>
            <label style={{ fontSize: 12, color: "var(--fg-3)" }}>名称</label>
            <CfInput value={name} onChange={setName} placeholder="输入≥2字符" />
          </div>
        </CfDrawer>
    </>
  );
}

内边缘缩放

resizable 让抽屉的内边缘变成拖拽把手 —— 用户可以横向(左右抽屉)或纵向(上下抽屉)调整尺寸。最小 240×160。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const right = ref(false);
const bottom = ref(false);
</script>
<template>
  <div style="display: flex; gap: 8px;">
    <CfButton @click="right = true">右侧可缩放</CfButton>
    <CfButton variant="tertiary" @click="bottom = true">底部可缩放</CfButton>
  </div>
  <CfDrawer
    v-model:open="right"
    placement="right"
    size="md"
    resizable
    title="右侧可缩放"
    description="抓住面板的左边缘横向拖拽(最小 240px)。"
  >
    <p style="margin: 0;">这种交互对“调试 / 详情”类抽屉非常实用 —— 用户可以根据自己屏幕宽度自由调整。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="bottom"
    placement="bottom"
    size="md"
    resizable
    title="底部可缩放"
    description="抓住面板的上边缘竖向拖拽(最小 160px)。"
  >
    <p style="margin: 0;">底部抽屉常用于日志、控制台、playground 等可调高度的工具区。</p>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const right = ref(false);
const bottom = ref(false);
</script>
<template>
  <div style="display: flex; gap: 8px;">
    <CfButton @click="right = true">右侧可缩放</CfButton>
    <CfButton variant="tertiary" @click="bottom = true">底部可缩放</CfButton>
  </div>
  <CfDrawer
    v-model:open="right"
    placement="right"
    size="md"
    resizable
    title="右侧可缩放"
    description="抓住面板的左边缘横向拖拽(最小 240px)。"
  >
    <p style="margin: 0;">这种交互对“调试 / 详情”类抽屉非常实用 —— 用户可以根据自己屏幕宽度自由调整。</p>
  </CfDrawer>
  <CfDrawer
    v-model:open="bottom"
    placement="bottom"
    size="md"
    resizable
    title="底部可缩放"
    description="抓住面板的上边缘竖向拖拽(最小 160px)。"
  >
    <p style="margin: 0;">底部抽屉常用于日志、控制台、playground 等可调高度的工具区。</p>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [right, setRight] = useState(false);
  const [bottom, setBottom] = useState(false);
  return (
    <>
      <div style={{ display: "flex", gap: 8 }}>
          <CfButton onClick={() => setRight(true)}>右侧可缩放</CfButton>
          <CfButton variant="tertiary" onClick={() => setBottom(true)}>底部可缩放</CfButton>
        </div>
        <CfDrawer open={right} onOpenChange={setRight} placement="right" size="md" resizable title="右侧可缩放" description="抓住面板的左边缘横向拖拽(最小 240px)。" >
          <p style={{ margin: 0 }}>这种交互对“调试 / 详情”类抽屉非常实用 —— 用户可以根据自己屏幕宽度自由调整。</p>
        </CfDrawer>
        <CfDrawer open={bottom} onOpenChange={setBottom} placement="bottom" size="md" resizable title="底部可缩放" description="抓住面板的上边缘竖向拖拽(最小 160px)。" >
          <p style={{ margin: 0 }}>底部抽屉常用于日志、控制台、playground 等可调高度的工具区。</p>
        </CfDrawer>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [right, setRight] = useState(false);
  const [bottom, setBottom] = useState(false);
  return (
    <>
      <div style={{ display: "flex", gap: 8 }}>
          <CfButton onClick={() => setRight(true)}>右侧可缩放</CfButton>
          <CfButton variant="tertiary" onClick={() => setBottom(true)}>底部可缩放</CfButton>
        </div>
        <CfDrawer open={right} onOpenChange={setRight} placement="right" size="md" resizable title="右侧可缩放" description="抓住面板的左边缘横向拖拽(最小 240px)。" >
          <p style={{ margin: 0 }}>这种交互对“调试 / 详情”类抽屉非常实用 —— 用户可以根据自己屏幕宽度自由调整。</p>
        </CfDrawer>
        <CfDrawer open={bottom} onOpenChange={setBottom} placement="bottom" size="md" resizable title="底部可缩放" description="抓住面板的上边缘竖向拖拽(最小 160px)。" >
          <p style={{ margin: 0 }}>底部抽屉常用于日志、控制台、playground 等可调高度的工具区。</p>
        </CfDrawer>
    </>
  );
}

不锁背景

默认 mask=true 渲染遮罩并锁定 body 滚动。设置 mask={false} 后抽屉浮在原页面之上,遮罩不渲染、滚动也不锁,适合”参考资料”、“实时日志”这类常驻面板。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开(不锁背景)</CfButton>
  <p style="margin: 8px 0 0; color: var(--fg-3); font-size: 12px;">
    抽屉打开后页面其他地方仍可继续滚动 / 点击 —— 适合“常驻参考面板”型场景。
  </p>
  <CfDrawer
    v-model:open="open"
    placement="right"
    size="sm"
    :mask="false"
    title="参考面板"
    description="mask=false 时不渲染遮罩、不锁滚动。"
  >
    <p style="margin: 0;">尝试在抽屉打开的状态下滚动或点击页面其他位置 —— 都能正常交互。</p>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开(不锁背景)</CfButton>
  <p style="margin: 8px 0 0; color: var(--fg-3); font-size: 12px;">
    抽屉打开后页面其他地方仍可继续滚动 / 点击 —— 适合“常驻参考面板”型场景。
  </p>
  <CfDrawer
    v-model:open="open"
    placement="right"
    size="sm"
    :mask="false"
    title="参考面板"
    description="mask=false 时不渲染遮罩、不锁滚动。"
  >
    <p style="margin: 0;">尝试在抽屉打开的状态下滚动或点击页面其他位置 —— 都能正常交互。</p>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开(不锁背景)</CfButton>
        <p style={{ margin: "8px 0 0", color: "var(--fg-3)", fontSize: 12 }}>
          抽屉打开后页面其他地方仍可继续滚动 / 点击 —— 适合“常驻参考面板”型场景。
        </p>
        <CfDrawer open={open} onOpenChange={setOpen} placement="right" size="sm" mask={false} title="参考面板" description="mask=false 时不渲染遮罩、不锁滚动。" >
          <p style={{ margin: 0 }}>尝试在抽屉打开的状态下滚动或点击页面其他位置 —— 都能正常交互。</p>
        </CfDrawer>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开(不锁背景)</CfButton>
        <p style={{ margin: "8px 0 0", color: "var(--fg-3)", fontSize: 12 }}>
          抽屉打开后页面其他地方仍可继续滚动 / 点击 —— 适合“常驻参考面板”型场景。
        </p>
        <CfDrawer open={open} onOpenChange={setOpen} placement="right" size="sm" mask={false} title="参考面板" description="mask=false 时不渲染遮罩、不锁滚动。" >
          <p style={{ margin: 0 }}>尝试在抽屉打开的状态下滚动或点击页面其他位置 —— 都能正常交互。</p>
        </CfDrawer>
    </>
  );
}

锁死关闭路径

表单 / 长流程里有时不希望误触关闭。三个开关全部置 false 让用户必须通过底部按钮显式提交或取消:

  • closeOnOverlay={false} —— 点击遮罩不关
  • closeOnEsc={false} —— Esc 不关
  • showClose={false} —— 隐藏右上角 ×

异步态(onBeforeOk 进行中)会自动屏蔽所有关闭路径,不需要手动控制。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开(仅按钮可关)</CfButton>
  <CfDrawer
    v-model:open="open"
    title="表单填写中"
    :close-on-overlay="false"
    :close-on-esc="false"
    :show-close="false"
  >
    <p style="margin: 0;">遮罩 / Esc / 右上角 × 全部禁用 — 用户必须通过底部按钮显式提交或取消。</p>
    <template #footer>
      <CfButton variant="ghost" @click="open = false">取消</CfButton>
      <CfButton @click="open = false">提交</CfButton>
    </template>
  </CfDrawer>
</template>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const open = ref(false);
</script>
<template>
  <CfButton @click="open = true">打开(仅按钮可关)</CfButton>
  <CfDrawer
    v-model:open="open"
    title="表单填写中"
    :close-on-overlay="false"
    :close-on-esc="false"
    :show-close="false"
  >
    <p style="margin: 0;">遮罩 / Esc / 右上角 × 全部禁用 — 用户必须通过底部按钮显式提交或取消。</p>
    <template #footer>
      <CfButton variant="ghost" @click="open = false">取消</CfButton>
      <CfButton @click="open = false">提交</CfButton>
    </template>
  </CfDrawer>
</template>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开(仅按钮可关)</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="表单填写中" closeOnOverlay={false} closeOnEsc={false} showClose={false} >
          <p style={{ margin: 0 }}>遮罩 / Esc / 右上角 × 全部禁用 — 用户必须通过底部按钮显式提交或取消。</p>
            <CfButton variant="ghost" onClick={() => setOpen(false)}>取消</CfButton>
            <CfButton onClick={() => setOpen(false)}>提交</CfButton>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>打开(仅按钮可关)</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="表单填写中" closeOnOverlay={false} closeOnEsc={false} showClose={false} >
          <p style={{ margin: 0 }}>遮罩 / Esc / 右上角 × 全部禁用 — 用户必须通过底部按钮显式提交或取消。</p>
            <CfButton variant="ghost" onClick={() => setOpen(false)}>取消</CfButton>
            <CfButton onClick={() => setOpen(false)}>提交</CfButton>
    </>
  );
}

footer 具名插槽接收 { ok, cancel, loading } 三个作用域参数,可以完全自渲染按钮组:插入”保存草稿”等额外动作,同时复用组件内置的 ok/cancel 关闭逻辑与 onBeforeOk 等待态。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer, toast } from '@chufix-design/vue';

const open = ref<boolean>(false);
const saving = ref<boolean>(false);

async function saveDraft() {
  saving.value = true;
  await new Promise<void>((r) => setTimeout(r, 600));
  saving.value = false;
  toast.success('草稿已保存');
}

async function publish(): Promise<boolean> {
  await new Promise<void>((r) => setTimeout(r, 700));
  toast.success('已发布');
  return true;
}
</script>
<template>
  <CfButton @click="open = true">编辑文章…</CfButton>
  <CfDrawer
    v-model:open="open"
    title="编辑文章"
    description="保存草稿与发布是两个独立的动作。"
    placement="right"
    size="md"
    :on-before-ok="publish"
  >
    <p class="adm-p">这里是文章正文区域 …</p>
    <template #footer="{ ok, cancel, loading }">
      <CfButton variant="ghost"    :disabled="loading" @click="cancel">取消</CfButton>
      <CfButton variant="tertiary" :loading="saving"   @click="saveDraft">保存草稿</CfButton>
      <CfButton variant="primary"  :loading="loading"  @click="ok">发布</CfButton>
    </template>
  </CfDrawer>
</template>
<style scoped>
.adm-p { color: var(--fg-2); line-height: 1.6; margin: 0; }
</style>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer, toast } from '@chufix-design/vue';

const open = ref<boolean>(false);
const saving = ref<boolean>(false);

async function saveDraft() {
  saving.value = true;
  await new Promise<void>((r) => setTimeout(r, 600));
  saving.value = false;
  toast.success('草稿已保存');
}

async function publish(): Promise<boolean> {
  await new Promise<void>((r) => setTimeout(r, 700));
  toast.success('已发布');
  return true;
}
</script>
<template>
  <CfButton @click="open = true">编辑文章…</CfButton>
  <CfDrawer
    v-model:open="open"
    title="编辑文章"
    description="保存草稿与发布是两个独立的动作。"
    placement="right"
    size="md"
    :on-before-ok="publish"
  >
    <p class="adm-p">这里是文章正文区域 …</p>
    <template #footer="{ ok, cancel, loading }">
      <CfButton variant="ghost"    :disabled="loading" @click="cancel">取消</CfButton>
      <CfButton variant="tertiary" :loading="saving"   @click="saveDraft">保存草稿</CfButton>
      <CfButton variant="primary"  :loading="loading"  @click="ok">发布</CfButton>
    </template>
  </CfDrawer>
</template>
<style scoped>
.adm-p { color: var(--fg-2); line-height: 1.6; margin: 0; }
</style>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState<boolean>(false);
  const [saving, setSaving] = useState<boolean>(false);

  async function saveDraft() {
    setSaving(true);
    await new Promise<void>((r) => setTimeout(r, 600));
    setSaving(false);
    toast.success('草稿已保存');
  }

  async function publish(): Promise<boolean> {
    await new Promise<void>((r) => setTimeout(r, 700));
    toast.success('已发布');
    return true;
  }
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>编辑文章…</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="编辑文章" description="保存草稿与发布是两个独立的动作。" placement="right" size="md" onBeforeOk={publish} >
          <p className="adm-p">这里是文章正文区域 …</p>
            <CfButton variant="ghost" disabled={loading} onClick={cancel}>取消</CfButton>
            <CfButton variant="tertiary" loading={saving} onClick={saveDraft}>保存草稿</CfButton>
            <CfButton variant="primary" loading={loading} onClick={ok}>发布</CfButton>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [open, setOpen] = useState<boolean>(false);
  const [saving, setSaving] = useState<boolean>(false);

  async function saveDraft() {
    setSaving(true);
    await new Promise<void>((r) => setTimeout(r, 600));
    setSaving(false);
    toast.success('草稿已保存');
  }

  async function publish(): Promise<boolean> {
    await new Promise<void>((r) => setTimeout(r, 700));
    toast.success('已发布');
    return true;
  }
  return (
    <>
      <CfButton onClick={() => setOpen(true)}>编辑文章…</CfButton>
        <CfDrawer open={open} onOpenChange={setOpen} title="编辑文章" description="保存草稿与发布是两个独立的动作。" placement="right" size="md" onBeforeOk={publish} >
          <p className="adm-p">这里是文章正文区域 …</p>
            <CfButton variant="ghost" disabled={loading} onClick={cancel}>取消</CfButton>
            <CfButton variant="tertiary" loading={saving} onClick={saveDraft}>保存草稿</CfButton>
            <CfButton variant="primary" loading={loading} onClick={ok}>发布</CfButton>
    </>
  );
}

多抽屉栈

抽屉之间会自动维护一个 z-index 栈,每打开一层 z-index +10;Esc 只关闭最顶层。可以在一个抽屉内继续打开嵌套抽屉,不必关心层级管理。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const layer1 = ref<boolean>(false);
const layer2 = ref<boolean>(false);
</script>
<template>
  <CfButton @click="layer1 = true">打开第一层</CfButton>
  <CfDrawer
    v-model:open="layer1"
    title="第一层 · 设置"
    description="可以在这一层再打开嵌套抽屉。"
    placement="right"
    size="md"
    ok-text="保存"
    cancel-text="取消"
  >
    <p class="adm-p">
      两层 drawer 同时存在时,组件维护内部 z-index 栈,每层自动 +10。
      Esc 只关闭最顶层,关闭后从栈里弹出。
    </p>
    <CfButton variant="tertiary" @click="layer2 = true">打开第二层(高级设置)</CfButton>
    <CfDrawer
      v-model:open="layer2"
      tone="warning"
      title="第二层 · 高级设置"
      description="这一层是从第一层里打开的;遮罩仍然位于第一层之上。"
      placement="right"
      size="sm"
      ok-text="知道了"
    >
      <p class="adm-p">嵌套抽屉的关闭路径独立于父层。</p>
    </CfDrawer>
  </CfDrawer>
</template>
<style scoped>
.adm-p { color: var(--fg-2); line-height: 1.6; margin: 0 0 12px; }
</style>
<script setup>
import { ref } from 'vue';
import { CfButton, CfDrawer } from '@chufix-design/vue';

const layer1 = ref<boolean>(false);
const layer2 = ref<boolean>(false);
</script>
<template>
  <CfButton @click="layer1 = true">打开第一层</CfButton>
  <CfDrawer
    v-model:open="layer1"
    title="第一层 · 设置"
    description="可以在这一层再打开嵌套抽屉。"
    placement="right"
    size="md"
    ok-text="保存"
    cancel-text="取消"
  >
    <p class="adm-p">
      两层 drawer 同时存在时,组件维护内部 z-index 栈,每层自动 +10。
      Esc 只关闭最顶层,关闭后从栈里弹出。
    </p>
    <CfButton variant="tertiary" @click="layer2 = true">打开第二层(高级设置)</CfButton>
    <CfDrawer
      v-model:open="layer2"
      tone="warning"
      title="第二层 · 高级设置"
      description="这一层是从第一层里打开的;遮罩仍然位于第一层之上。"
      placement="right"
      size="sm"
      ok-text="知道了"
    >
      <p class="adm-p">嵌套抽屉的关闭路径独立于父层。</p>
    </CfDrawer>
  </CfDrawer>
</template>
<style scoped>
.adm-p { color: var(--fg-2); line-height: 1.6; margin: 0 0 12px; }
</style>
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [layer1, setLayer1] = useState<boolean>(false);
  const [layer2, setLayer2] = useState<boolean>(false);
  return (
    <>
      <CfButton onClick={() => setLayer1(true)}>打开第一层</CfButton>
        <CfDrawer open={layer1} onOpenChange={setLayer1} title="第一层 · 设置" description="可以在这一层再打开嵌套抽屉。" placement="right" size="md" ok-text="保存" cancel-text="取消" >
          <p className="adm-p">
            两层 drawer 同时存在时,组件维护内部 z-index 栈,每层自动 +10。
            Esc 只关闭最顶层,关闭后从栈里弹出。
          </p>
          <CfButton variant="tertiary" onClick={() => setLayer2(true)}>打开第二层(高级设置)</CfButton>
          <CfDrawer open={layer2} onOpenChange={setLayer2} tone="warning" title="第二层 · 高级设置" description="这一层是从第一层里打开的;遮罩仍然位于第一层之上。" placement="right" size="sm" ok-text="知道了" >
            <p className="adm-p">嵌套抽屉的关闭路径独立于父层。</p>
          </CfDrawer>
        </CfDrawer>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfDrawer } from '@chufix-design/react';

export default function Demo() {
  const [layer1, setLayer1] = useState<boolean>(false);
  const [layer2, setLayer2] = useState<boolean>(false);
  return (
    <>
      <CfButton onClick={() => setLayer1(true)}>打开第一层</CfButton>
        <CfDrawer open={layer1} onOpenChange={setLayer1} title="第一层 · 设置" description="可以在这一层再打开嵌套抽屉。" placement="right" size="md" ok-text="保存" cancel-text="取消" >
          <p className="adm-p">
            两层 drawer 同时存在时,组件维护内部 z-index 栈,每层自动 +10。
            Esc 只关闭最顶层,关闭后从栈里弹出。
          </p>
          <CfButton variant="tertiary" onClick={() => setLayer2(true)}>打开第二层(高级设置)</CfButton>
          <CfDrawer open={layer2} onOpenChange={setLayer2} tone="warning" title="第二层 · 高级设置" description="这一层是从第一层里打开的;遮罩仍然位于第一层之上。" placement="right" size="sm" ok-text="知道了" >
            <p className="adm-p">嵌套抽屉的关闭路径独立于父层。</p>
          </CfDrawer>
        </CfDrawer>
    </>
  );
}

命令式服务

drawer.open / confirm / danger 直接弹一个抽屉并返回 Promise<boolean> —— 不需要任何状态绑定。

import { drawer } from '@chufix-design/vue'; // React 端:从 @chufix-design/react 导入

const ok = await drawer.confirm({
  title: '设置',
  description: '快速调整服务参数',
  placement: 'right',
  content: SettingsForm, // 字符串 / 组件
  onOk: async () => {
    await api.save(); // throw 或 return false 阻止关闭
  },
});
背景 视口
src/App.vue
<script setup lang="ts">
import { CfButton, drawer, toast } from '@chufix-design/vue';

async function openSettings() {
  const ok = await drawer.confirm({
    title: '设置',
    description: '快速调整服务参数 —— 不需要关心 v-model。',
    placement: 'right',
    size: 'md',
    content: '这里通常会嵌入一个 Form 子组件。点击保存以应用更改。',
    onOk: async () => {
      await new Promise((r) => setTimeout(r, 600));
    },
  });
  if (ok) toast({ type: 'success', message: '设置已保存' });
}

async function deleteFlow() {
  const ok = await drawer.danger({
    title: '永久删除该工作区?',
    description: '所有数据将被清除,且无法恢复。',
    placement: 'right',
    okText: '我已了解,删除',
    cancelText: '保留',
    content: '建议先导出工作区作为备份。',
  });
  toast({
    type: ok ? 'error' : 'info',
    message: ok ? '工作区已删除' : '已取消删除',
  });
}
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="openSettings">drawer.confirm()</CfButton>
    <CfButton variant="danger" @click="deleteFlow">drawer.danger()</CfButton>
  </div>
</template>
<script setup>
import { CfButton, drawer, toast } from '@chufix-design/vue';

async function openSettings() {
  const ok = await drawer.confirm({
    title: '设置',
    description: '快速调整服务参数 —— 不需要关心 v-model。',
    placement: 'right',
    size: 'md',
    content: '这里通常会嵌入一个 Form 子组件。点击保存以应用更改。',
    onOk: async () => {
      await new Promise((r) => setTimeout(r, 600));
    },
  });
  if (ok) toast({ type: 'success', message: '设置已保存' });
}

async function deleteFlow() {
  const ok = await drawer.danger({
    title: '永久删除该工作区?',
    description: '所有数据将被清除,且无法恢复。',
    placement: 'right',
    okText: '我已了解,删除',
    cancelText: '保留',
    content: '建议先导出工作区作为备份。',
  });
  toast({
    type: ok ? 'error' : 'info',
    message: ok ? '工作区已删除' : '已取消删除',
  });
}
</script>
<template>
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
    <CfButton @click="openSettings">drawer.confirm()</CfButton>
    <CfButton variant="danger" @click="deleteFlow">drawer.danger()</CfButton>
  </div>
</template>
import { CfButton } from '@chufix-design/react';

export default function Demo() {
  async function openSettings() {
    const ok = await drawer.confirm({
      title: '设置',
      description: '快速调整服务参数 —— 不需要关心 v-model。',
      placement: 'right',
      size: 'md',
      content: '这里通常会嵌入一个 Form 子组件。点击保存以应用更改。',
      onOk: async () => {
        await new Promise((r) => setTimeout(r, 600));
      },
    });
    if (ok) toast({ type: 'success', message: '设置已保存' });
  }

  async function deleteFlow() {
    const ok = await drawer.danger({
      title: '永久删除该工作区?',
      description: '所有数据将被清除,且无法恢复。',
      placement: 'right',
      okText: '我已了解,删除',
      cancelText: '保留',
      content: '建议先导出工作区作为备份。',
    });
    toast({
      type: ok ? 'error' : 'info',
      message: ok ? '工作区已删除' : '已取消删除',
    });
  }
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={openSettings}>drawer.confirm()</CfButton>
          <CfButton variant="danger" onClick={deleteFlow}>drawer.danger()</CfButton>
        </div>
    </>
  );
}
import { CfButton } from '@chufix-design/react';

export default function Demo() {
  async function openSettings() {
    const ok = await drawer.confirm({
      title: '设置',
      description: '快速调整服务参数 —— 不需要关心 v-model。',
      placement: 'right',
      size: 'md',
      content: '这里通常会嵌入一个 Form 子组件。点击保存以应用更改。',
      onOk: async () => {
        await new Promise((r) => setTimeout(r, 600));
      },
    });
    if (ok) toast({ type: 'success', message: '设置已保存' });
  }

  async function deleteFlow() {
    const ok = await drawer.danger({
      title: '永久删除该工作区?',
      description: '所有数据将被清除,且无法恢复。',
      placement: 'right',
      okText: '我已了解,删除',
      cancelText: '保留',
      content: '建议先导出工作区作为备份。',
    });
    toast({
      type: ok ? 'error' : 'info',
      message: ok ? '工作区已删除' : '已取消删除',
    });
  }
  return (
    <>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
          <CfButton onClick={openSettings}>drawer.confirm()</CfButton>
          <CfButton variant="danger" onClick={deleteFlow}>drawer.danger()</CfButton>
        </div>
    </>
  );
}

API

Prop类型默认说明
openbooleanfalse受控开关
titlestringheader 标题
descriptionstring标题下副标题
placement'left' | 'right' | 'top' | 'bottom''right'滑入方向
size'sm' | 'md' | 'lg' | 'xl' | 'full''md'横向→宽度档位;纵向→高度档位
tone'default' | 'info' | 'success' | 'warning' | 'error''default'视觉 tone + 自动图标
widthnumber | string自定义宽度(左右 placement)
heightnumber | string自定义高度(上下 placement)
resizablebooleanfalse内边缘可拖拽缩放
maskbooleantrue是否渲染遮罩并锁滚动
closeOnOverlaybooleantrue点遮罩是否关闭
closeOnEscbooleantrueEsc 是否关闭
showClosebooleantrue右上角 ×
footerAlign'start' | 'center' | 'end' | 'space-between''end'footer 对齐
okText / cancelTextstring默认按钮文案;不传则不渲染默认按钮
okVariant'primary' | 'danger' | 'secondary''primary'确认按钮样式
onBeforeOk() => boolean | void | Promise<...>异步钩子;false / throw 阻止关闭
tostring | Element'body'Teleport / Portal 目标
zIndexnumber自动栈管理自定义 z-index 起点

插槽 / 事件

  • Vue:header / 默认 / footer 三个具名插槽。footer 插槽接收 { ok, cancel, loading } 用于自渲染按钮。
  • React:headerfooter(可传 ReactNode 或 ({ ok, cancel, loading }) => ReactNode)+ children
  • 事件:update:open / close / ok / cancel

Exposed 方法 (Vue ref)

方法说明
close()编程式关闭抽屉(受 onBeforeOk 异步态保护)
ok()编程式触发 OK 流程(含 onBeforeOk 校验)

通过 ref 拿到组件实例后调用:

<CfDrawer ref="drawerRef" v-model:open="open" title="设置" />
<CfButton @click="drawerRef?.close()">从外部关闭</CfButton>

服务方法

方法tone默认按钮
drawer.open(opts)跟 opts 一致跟 opts
drawer.confirm(opts)warningOK + Cancel
drawer.danger(opts)error红色 OK + Cancel

反馈与讨论

Drawer 抽屉 的讨论

0
0 / 600
正在加载评论...