# Miao Design · 完整文档 > Miao Design 是一个基于 Vue 3 + TypeScript 的轻量级组件库,提供 26 个高质量组件、完整类型定义与配套设计变量,开箱即用。 > 本文件由构建脚本生成,包含全部 35 个页面的 Markdown 正文;每个页面之间用 `---` 分隔,页面内相对链接以站点根路径书写(如 `/components/button`)。 --- Components 26 个组件,都是活的 下面的每个预览都由真实组件实时渲染 —— 直接点一下就能玩,进到文档页还能看到源码。 查看全部 26 个组件 → --- # 介绍 Miao Design 是一个基于 **Vue 3 + TypeScript** 的轻量级组件库,面向中后台系统、桌面应用与工具类产品。它不追求组件数量,而是把常用的那批组件做扎实:样式基于 MUI 的调色板、阴影、缓动曲线与尺寸令牌,交互细节对齐 Material Design。 ## 设计原则 **令牌先行。** 所有视觉表现都落在 `--m-*` CSS 自定义属性上。换主题、调品牌色、改圆角,只需要覆写一层变量,不必碰组件源码,也不会有样式优先级战争。 **零运行时依赖。** `miao-design` 的 `dependencies` 是空的,`vue@^3.5` 只作为 peerDependency。产物直接由业务侧的打包器处理,不会平白多出一棵依赖树。 **组合优于配置。** 需要父子配合的组件(`MCheckboxGroup` / `MRadioGroup` / `MDropdown` / `MTabPanes`)用 `provide / inject` 传递上下文,父组件只负责状态与事件,子组件只管渲染,不引入额外的配置对象。 **类型即文档。** 每个组件的 props / events / slots 都由 TypeScript 描述并随包发布 `.d.ts`,IDE 里的自动补全就是最准确的文档。 ## 组件一览 **26 个组件**与 `v-ripple` 指令,按用途分为五组: | 分组 | 组件 | | --- | --- | | 通用 | `MButton`、`MButtonGroup`、`MIcon`、`MDivider`、`MFab` | | 表单 | `MInput`、`MInputNumber`、`MSelect`、`MCheckbox` / `MCheckboxGroup`、`MRadio` / `MRadioGroup`、`MRadioButtonGroup`、`MSwitch`、`MSlider`、`MDatePicker`、`MDateRangePicker`、`MColorPicker` | | 数据展示 | `MAvatar`、`MProgress`、`MTooltip`、`MAlert`、`MTabs` / `MVTabs` / `MTabPanes` / `MTabPane`、`MNav` | | 反馈与弹层 | `MDialog`、`MDrawer`、`MDropdown` / `MDropdownItem`、`MToast` | | 指令 | `v-ripple` | ::: tip 26 个「组件」和 32 个「标签」不是一回事 上表按**功能单元**计数:像 `MTabs` / `MVTabs` / `MTabPanes` / `MTabPane` 这类必须配套使用的,算作一个组件。 `app.use(MiaoDesign)` 实际注册的**全局标签是 32 个**(外加 `v-ripple` 指令),因为复选框组、单选框组、下拉项、标签内容区、纵向标签栏等子组件也各自占一个名字。 ::: 另有编程式 API `toast`(`toast.success()` 等)与底层函数 `showToast`。 前往[组件总览](/components/)可以看到每个组件的实时预览。 ## 兼容性 | 项目 | 版本要求 | | --- | --- | | Vue | `^3.5.0`(peerDependency) | | 包格式 | ESM(`miao-design.js`)+ CJS(`miao-design.umd.cjs`) | | 浏览器 | 支持原生 CSS 自定义属性与 ES2018 的现代浏览器(Chrome / Edge 79+、Safari 14+、Firefox 78+) | | 无需构建工具 | 支持直接以 UMD 方式在浏览器中引入 | ## 下一步 - [快速开始](/guide/getting-started) —— 安装、引入与第一个组件。 - [主题定制](/guide/theme) —— 覆写设计令牌、接入深色模式。 - [AI 阅读](/guide/ai) —— 让 AI 助手直接读懂整份文档并生成正确代码。 --- # 快速开始 ## 安装 ```bash # npm npm i miao-design # pnpm pnpm add miao-design # yarn yarn add miao-design ``` `vue@^3.5` 是 peerDependency,由业务侧自行安装,组件库不会重复引入一份 Vue。 ## 引入样式 组件库的样式是一个单独的文件,**必须显式引入一次**(放在应用入口即可): ```ts import 'miao-design/style.css' ``` 这个文件里既有各组件的样式,也有挂在 `:root` 上的全部 `--m-*` 设计令牌,因此引入一次即可全局生效。 ## 完整引入 最省事的方式,适合中小型项目: ```ts // main.ts import { createApp } from 'vue' import MiaoDesign from 'miao-design' import 'miao-design/style.css' import App from './App.vue' createApp(App).use(MiaoDesign).mount('#app') ``` `app.use(MiaoDesign)` 会一次性注册 32 个组件与 `v-ripple` 指令,之后在任意模板里直接写 `` 就行,不需要 import。 ```vue 保存 取消 ``` ## 按需引入 需要更小的包体时,用具名导入。组件内部不依赖全局注册,两种方式可以混用: ```vue 悬停看看 ``` ::: tip 树摇说明 组件库产物的模块图是「一个组件一个 chunk」的结构,配合支持 tree-shaking 的打包器(Vite / Rollup / webpack 5)时,未使用的组件不会进入最终产物。 ::: ## 使用 v-ripple 指令 如果你没有 `app.use(MiaoDesign)`,需要单独注册指令: ```ts import { createApp } from 'vue' import { vRipple } from 'miao-design' createApp(App).directive('ripple', vRipple).mount('#app') ``` 之后可以给任意元素加水波纹反馈: ```vue 点我 自定义参数 ``` 详见 [v-ripple 涟漪](/components/ripple)。 ## 编程式 Toast `toast` 是一个独立的编程式 API,首次调用时会自动把容器挂载到 `body`,无需在模板里放组件: ```ts import { toast } from 'miao-design' toast.success('保存成功') toast.error('网络异常,请重试', { title: '错误', duration: 5000, position: 'top-right' }) const id = toast.show('可手动关闭的提示', { closable: true }) toast.close(id) ``` 完整参数见 [Toast 轻提示](/components/toast)。 ## TypeScript 组件的类型定义随包发布,直接可用: ```ts import type { MButtonProps, ButtonColor, MiaoDesign } from 'miao-design' ``` ::: tip 全局组件类型提示 完整引入时,模板里的 `` 不会有类型报错,因为组件是通过 `app.component()` 在运行时注册的,Volar 无法静态推断。如果需要严格的模板类型检查,改用具名导入,或者在 `env.d.ts` 里补一份全局组件声明: ```ts // env.d.ts declare module 'vue' { export interface GlobalComponents { MButton: typeof import('miao-design')['MButton'] MInput: typeof import('miao-design')['MInput'] // …按需补充 } } ``` ::: ## 无需构建工具(UMD) 组件库同时提供 UMD 产物,可以直接在浏览器里用 ` Hello Miao ``` ## 下一步 - [主题定制](/guide/theme) —— 覆写 `--m-*` 令牌、接入深色模式。 - [组件总览](/components/) —— 全部组件与指令的实时预览与 API。 - [AI 阅读](/guide/ai) —— 把整份文档投喂给 AI 助手。 --- # 主题定制 组件库的所有视觉表现都由 `--m-*` 形式的 CSS 自定义属性驱动。这套令牌定义在 `miao-design/style.css` 的 `:root` 中,**严格对齐 MUI v5 的默认主题**:调色板取自 MUI `createPalette`,阴影取自 `shadows.js`,缓动曲线与时长取自 `createTransitions`。 这意味着两件事:一是换肤只需要覆写变量;二是如果你的团队本来就在用 MUI 的设计语言,视觉可以直接对齐。 ## 语义色 六组语义色,每组包含五个变量: | 变量 | 含义 | | --- | --- | | `--m-` | 主色(`main`) | | `--m--hover` | 悬停态(`dark`) | | `--m--light` | 浅色变体(`light`) | | `--m--contrast` | 主色之上的文字色 | | `--m--rgb` | 以空格分隔的 RGB 通道,用于 `rgb(var(--m-primary-rgb) / 0.2)` 这类透明度写法 | ## 背景与浮层 `--m-popup` 专门给下拉菜单、选择器、日期面板这类临时浮层使用(暗色下是 `#242424` 而非纯黑),`--m-card` 是兼容旧代码保留的卡片底。 ## 交互态 ## 文字与边框 ## 形状、排版与尺寸 ```css :root { --m-radius: 6px; /* 全局圆角(theme.shape.borderRadius) */ --m-font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; --m-font-weight-medium: 500; --m-size-default: 36px; /* 输入框 / 下拉 / 按钮等默认高度 */ --m-size-small: 28px; --m-size-large: 48px; } ``` ## 缓动与时长 组件的过渡动画统一使用这四个缓动函数与六个时长,做自定义动效时直接复用它们即可保持节奏一致。 | 变量 | 值 | | --- | --- | | `--m-ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` | | `--m-ease-out` | `cubic-bezier(0, 0, 0.2, 1)` | | `--m-ease-in` | `cubic-bezier(0.4, 0, 1, 1)` | | `--m-ease-sharp` | `cubic-bezier(0.4, 0, 0.6, 1)` | | `--m-dur-shortest` ~ `--m-dur-complex` | `150ms` / `200ms` / `250ms` / `300ms` / `375ms` | | `--m-dur-entering` / `--m-dur-leaving` | `225ms` / `195ms`(JS 读取,用于弹出面板) | ## 阴影 阴影直接取自 MUI 的 `shadows.js`。注意源码只定义了 `0 / 1 / 2 / 3 / 4 / 6 / 8 / 12 / 16 / 24` 这几档,其余档位不存在。 | 变量 | 用途 | | --- | --- | | `--m-shadow-0` ~ `--m-shadow-24` | 通用 elevation | | `--m-dialog-shadow` | 对话框纸面(等于 `--m-shadow-24`) | | `--m-dropdown-shadow` | 菜单 / 浮层(Vuetify MD3 的 6dp 双层柔影) | ## 层级 | 变量 | 值 | 用途 | | --- | --- | --- | | `--m-z-modal` | `1300` | 模态层(Dialog / Drawer) | | `--m-z-menu` | `1301` | 菜单 / 下拉面板 | | `--m-z-tooltip` | `1500` | 文字提示 | | `--m-z-toast` | `1600` | Toast(最顶层) | ## 覆写令牌 在业务样式里重新声明同名变量即可,不需要 `!important`,也不需要修改组件库源码: ```css /* 把品牌主色换成青色系 */ :root { --m-primary: #0f766e; --m-primary-hover: #115e59; --m-primary-light: #14b8a6; --m-primary-rgb: 15 118 110; } ``` 或者做一个作用域内的局部主题: ```css /* 只在这块区域里换掉圆角与主色 */ .brand-panel { --m-radius: 12px; --m-primary: #7c3aed; --m-primary-rgb: 124 58 237; } ``` ## 深色模式 暗色令牌定义在 `:root[data-theme='dark']` 上,因此只需要在根元素(或任意局部容器)上挂一个属性: ```ts // 跟随系统 const dark = window.matchMedia('(prefers-color-scheme: dark)').matches document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light') ``` ```ts // 手动切换 function toggleDark(on: boolean) { document.documentElement.setAttribute('data-theme', on ? 'dark' : 'light') } ``` ::: tip 暗色下的取值变化 暗色模式并不是简单地把颜色压暗:语义色会切换为 MUI dark palette 的对应值(例如 `--m-primary` 从 `#1976d2` 变成 `#90caf9`),`-contrast` 变成深色文字,阴影的 alpha 会加重。业务侧不要硬编码浅色态的值。 ::: ::: tip 与框架主题联动 本站在用 VitePress,它的深色模式类名是 `html.dark`,而组件库认的是 `data-theme="dark"`。站点的做法是在 `Layout.vue` 里监听 `isDark` 并同步属性,让文档站里的真实组件跟随站点外观切换 —— 这是一种通用做法,任何框架都可以照搬。 ::: ## 组件级变量 除全局令牌外,部分组件会把自己的 prop 下发给局部变量,方便在不改动 prop 的情况下做样式覆盖: | 变量 | 来源 | 含义 | | --- | --- | --- | | `--m-control-radius` | Input / Select / DatePicker 的 `radius` | 触发区圆角 | | `--m-panel-radius` | Select / DatePicker / Dropdown 的 `panelRadius` | 弹出面板圆角 | | `--m-dialog-radius` | Dialog 的 `radius` | 对话框圆角 | | `--m-divider-color` / `--m-divider-size` / `--m-divider-gap` | Divider 的 `color` / `size` / `gap` | 分割线颜色 / 粗细 / 间距 | | `--m-slider-percent` | Slider | 已填充行程百分比 | | `--m-slider-rail-h` / `--m-slider-thumb` | Slider 的 `trackSize` / `thumbSize` | 轨道粗细 / 拇指直径 | | `--m-ripple-duration` / `--m-ripple-initial-scale` | `v-ripple` 的 `duration` / `initialScale` | 涟漪时长 / 起始缩放 | | `--m-toast-duration` / `--m-toast-offset` | Toast | 自动关闭时长 / 角位堆叠间距 | ## 下一步 - [AI 阅读](/guide/ai) —— 让 AI 直接读懂这套令牌并帮你写主题代码。 - [常见问题](/guide/faq) —— 样式不生效、弹层被裁剪等高频问题。 --- # 图标检索 图标数据全部来自 [Material Design Icons](https://pictogrammers.com/library/mdi/),通过 `@mdi/js` 以**纯字符串常量**的形式提供:每个图标就是一条 24×24 viewBox 的 SVG path,按需引入即可被 tree-shaking 掉未使用的部分。 `MIcon` 组件本身只做渲染,不内置任何图标集合,所以必须自行安装 `@mdi/js`: ```bash npm i @mdi/js ``` ## 基本用法 ```vue ``` ## 属性一览 | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `path` | `string` | — | SVG path data,通常来自 `@mdi/js`;也可传入任意 24×24 viewBox 的自定义路径 | | `size` | `number \| string` | `24` | 数字按 px 处理,字符串原样输出(如 `'1.5em'`) | | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info'` | — | 语义色;缺省时继承 `currentColor` | | `disabled` | `boolean` | `false` | 禁用态,使用 `action.disabled` 文字色 | | `flip` | `'horizontal' \| 'vertical' \| 'both'` | — | 翻转方向 | | `rotate` | `number` | `0` | 旋转角度(deg,顺时针) | | `spin` | `boolean` | `false` | 自旋动画,常用于加载 / 同步 | | `title` | `string` | — | 无障碍名称;不传时图标对屏幕阅读器隐藏 | ::: tip 颜色优先跟随上下文 不传 `color` 时 SVG 使用 `fill="currentColor"`,所以直接把 `MIcon` 放进按钮或文字里,它就会继承父级的文字颜色 —— 这也是在 `MButton` 里混排图标不需要额外设色的原因。 ::: ## 图标检索 数据量较大(7000+),这里按需加载,点击任意图标即可复制其变量名。 ## 命名规则 图标名遵循 `mdi{PascalCaseName}` 模式,例如 `mdiWindowMaximize`、`mdiArrowLeftBold`。 | 后缀 | 含义 | 示例 | | --- | --- | --- | | `Outline` | 轮廓线版本(无填充) | `mdiAccountOutline` | | `Bold` | 加粗版本 | `mdiCheckBold` | | `Multiple` | 多实例 | `mdiAccountMultiple` | | `Plus` / `Minus` | 添加 / 移除 | `mdiAccountPlus` | | `Check` | 带勾选标记 | `mdiAccountCheck` | | `Alert` | 带警告标记 | `mdiAccountAlert` | | `Off` | 关闭 / 禁用 | `mdiBluetoothOff` | | `Variant` | 变体 | `mdiPackageVariant` | | `Circle` / `Box` | 圆形 / 方形容器 | `mdiAlertCircle` | ## 常用图标速查 ```ts import { mdiPlus, mdiMinus, mdiClose, mdiCheck, mdiCheckCircle, // 基础操作 mdiPencilOutline, mdiDeleteOutline, mdiContentCopy, // 编辑 mdiMagnify, mdiFilterVariant, mdiRefresh, mdiSync, // 检索 / 刷新 mdiChevronDown, mdiChevronRight, mdiArrowLeft, mdiMenu, // 方向 mdiAccountOutline, mdiBellOutline, mdiCogOutline, // 用户 / 设置 mdiEyeOutline, mdiEyeOffOutline, mdiInformationOutline, // 状态 mdiAlertOutline, mdiCloseCircleOutline, mdiLoading // 反馈 } from '@mdi/js' ``` ::: warning 别用 `import * as mdi` `import * as mdi from '@mdi/js'` 会把 7000+ 条路径全部打进产物(体积以 MB 计)。始终用具名导入。 ::: ## 下一步 - [Icon 图标组件](/components/icon) —— `MIcon` 的完整 API 与演示。 --- # AI 阅读 组件库文档最容易出错的地方是「AI 凭印象编 API」。本站因此把全部文档额外产出为**三种纯文本形态**,任何 AI 智能体只要拿到站点域名就能读完,不必去解析渲染后的 HTML。 | 入口 | 路径 | 适合场景 | | --- | --- | --- | | 全站索引 | [`/llms.txt`](/llms.txt) | 先让模型知道有哪些页面,再按链接取用 | | 单页 Markdown | `/md/<页面路径>.md` | 精确投喂某一两个组件 | | 全站单文件 | [`/llms-full.txt`](/llms-full.txt) | 一次性通读整份文档 | 三个入口都是**纯净 Markdown**。页面里的演示代码块已经被展开成完整的 Vue SFC 源码 —— 模型看到的是可以直接运行的代码,而不是文档站的插件语法。 ## 推荐用法 ### 一、精确投喂单个组件 最省 token 的方式,把目标页面的 Markdown 原文地址直接给模型: ```text 请阅读 https://tongmingwang.github.io/miao-design/md/components/button.md 然后按里面的 API,帮我写一个带加载态的提交按钮。 ``` ### 二、给项目写一份接入约定 把下面这段放进 `.cursor/rules`、`CLAUDE.md`、`AGENTS.md` 或任何自定义指令的位置,能显著降低 API 幻觉率: ```text 本项目使用 miao-design(Vue 3 组件库)。 - 文档索引:https://tongmingwang.github.io/miao-design/llms.txt - 需要某个组件的用法时,先抓取 https://tongmingwang.github.io/miao-design/md/components/<组件目录名>.md 再写代码,不要凭印象生成 API。 - 组件标签名一律 M 前缀(MButton / MInput / MSelect …),已在 main.ts 里通过 app.use(MiaoDesign) 全局注册,模板里无需 import。 - 样式必须引入一次:import 'miao-design/style.css'。 - 主题令牌统一是 CSS 自定义属性,形如 --m-primary / --m-radius / --m-size-default, 不要硬编码颜色,需要换肤时覆写变量。 - 事件与 v-model 命名遵循 Vue 3 约定:update:modelValue / change / click。 ``` ### 三、一次性通读 只有在需要「通读整个库」时才用全量文件,否则会浪费上下文: ```bash curl -s https://tongmingwang.github.io/miao-design/llms-full.txt ``` ## 本站为 AI 做的其他约定 - **每页都有 Markdown 替代链接**:页面 `` 里带有 ``,爬虫与智能体可以直接发现。 - **演示源码就是真实文件**:`docs/demos/<组件>/<用例>.vue`,文档里的代码与仓库中的文件一一对应,不存在文档与实现漂移。 - **robots.txt 显式放行**,并在注释里标注了三个纯文本入口。 - **API 表格用 Markdown 表格书写**,而不是自定义组件渲染 —— 保证在任何文本形态下都可读。 - **每页开头有 `description`**,被索引进 `llms.txt`,方便模型快速判断是否要展开该页。 ## 人类阅读也能用 页面顶部有一个小工具条,随时可以: - **复制为 Markdown** —— 把当前页原文(含完整演示源码)复制到剪贴板,粘进对话即可。 - **原文** —— 在浏览器里打开纯 Markdown 版本。 - **llms.txt** —— 打开全站索引。 ## 下一步 - [组件总览](/components/) —— 全部组件与指令的实时预览。 - [常见问题](/guide/faq) —— 高频踩坑点,也建议一并投喂给 AI。 --- # 常见问题 下面是使用时最容易踩到的点,也建议把这一页一并投喂给 AI 助手。 ## 组件没有任何样式 组件库的样式是独立文件,必须显式引入一次: ```ts import 'miao-design/style.css' ``` 只写 `import MiaoDesign from 'miao-design'` 不会带出样式。样式文件里同时包含全部 `--m-*` 设计令牌,漏引会导致颜色、圆角、阴影全部失效。 ## 换了主题色但组件没变 先确认覆写的是**组件库的令牌名**(`--m-*`),而不是你自己项目的变量: ```css :root { --m-primary: #0f766e; /* ✅ 组件库认这个 */ --primary-color: #0f766e; /* ❌ 没有组件会读它 */ } ``` 另外要注意 CSS 的加载顺序:覆写规则要在 `miao-design/style.css` **之后**生效,否则会被原值覆盖。 ## 弹层被父容器裁剪 / 层级不对 下拉菜单、日期面板、Tooltip 这类浮层都有明确的层级令牌,遇到被 `overflow: hidden` 裁剪时,检查父链路里有没有设置 `transform` / `filter` / `contain`,它们会创建新的层叠上下文: | 令牌 | 值 | 用途 | | --- | --- | --- | | `--m-z-modal` | `1300` | Dialog / Drawer | | `--m-z-menu` | `1301` | 下拉面板 | | `--m-z-tooltip` | `1500` | Tooltip | | `--m-z-toast` | `1600` | Toast | ## MDivider 默认是纵向的 `vertical` 的默认值是 **`true`**,所以 `` 渲染出来是一条纵线(高度 100%)。要水平分割线必须显式关闭: ```vue ``` 另外 `width` / `height` 两个 prop 虽然在类型里存在,但实现中没有被引用,改宽度请用外层容器或样式覆盖。 ## 组件放进 Group 之后就不生效了 `MCheckbox`、`MRadio` 在组内会**改由父组件管理状态**: - 必须传 `value`,否则会以 `undefined` 参与比对,选中判断出错; - 组内子组件自身**不再抛出** `update:modelValue` / `change`,这些事件由父组件(`MCheckboxGroup` / `MRadioGroup`)统一抛出; - 组内子组件的 `modelValue` 会被忽略。 ```vue 选项 A 选项 B ``` ## MTabs 里放不下 MTabPane `MTabs` / `MVTabs` 是**纯标签栏**,由 `items` 数组驱动,不收集子组件。标签栏与内容区是两个平级组件,靠绑定同一个 `v-model` 联动: ```vue 内容 A 内容 B ``` `MTabPanes` ↔ `MTabPane` 之间才是父子关系(通过 `provide / inject` 注册面板)。 ## 按钮点击偶尔不响应 `MButton` 的点击默认有 **200ms 节流**,连点会被丢弃。需要每次都响应时显式关闭: ```vue 立即响应 ``` `MButton.loading` 或 `disabled` 时按钮处于原生 disabled 状态,`click` 不会触发,这是预期行为。 ## MButtonGroup 里的按钮外观不受自己控制 放进 `MButtonGroup` 后: - `variant` 与 `size` 会被**组的设置覆盖**(组没传 size 时也会写成 `default`); - `color` 是「子优先」——子组件自己设了就用子的,没设才取组值; - `disabled` 取父子并集。 ## DatePicker 什么时候会变成「日期时间」选择器 取决于 `format` 字符串里**是否包含时间片段**(大小写敏感): | `format` | 结果 | | --- | --- | | `YYYY-MM-DD` | 纯日期,点选即关闭面板 | | `YYYY-MM-DD HH:mm` | 日期 + 时间,点选日期不关闭,需点「确定」 | | `YYYY-MM-DD HH:mm:ss` | 日期 + 时 / 分 / 秒 | ## Toast 只弹出一次 / 被宿主容器影响 `toast` 是模块级单例,容器在**首次调用时**才懒挂载到 `document.body`。因此在 SSR 场景下不要在模块顶层调用它,放到用户交互里: ```ts // ❌ SSR 环境会在服务端执行 toast.success('欢迎回来') // ✅ function onMountedClick() { toast.success('欢迎回来') } ``` ## 移到暗色模式后颜色很怪 组件库的暗色不是把浅色简单压暗:语义色会换成 MUI dark palette 的取值(`--m-primary` 由 `#1976d2` 变为 `#90caf9`),`-contrast` 变成深色文字,阴影 alpha 加重。业务样式里**不要硬编码浅色态的具体色值**,始终引用 `--m-*` 变量。 ## 仍然找不到答案? - 在[组件总览](/components/)里搜索对应的组件页,注意看每页底部的「使用建议」。 - 到 [GitHub 仓库](https://github.com/tongmingwang/miao-design/issues) 提 issue,附上最小复现。 --- # 组件总览 共 {{ componentCount }} 个组件与 `v-ripple` 指令,覆盖从按钮、表单控件到弹层与反馈的常见场景。 下面每张卡片里的预览都由**真实组件实时渲染**,可以直接点击体验;点进卡片即可看到完整用法、API 表格与可一键复制的源码。 ::: tip 还没安装? 一条命令即可接入,详见[快速开始](/guide/getting-started)。 ```bash npm i miao-design ``` ::: --- # Button 按钮 最常用的操作触发器。提供六种语义色、五种外观变体与多种形状,内置 Material 涟漪反馈,点击默认带 200ms 节流以避免重复提交。 ## 代码演示 ### 基础用法 通过 `variant` 切换外观,`color` 指定语义色。默认变体是 `text`,业务里最常用的是 `contained` 与 `outlined`。 **演示说明:** 点击按钮会通过编程式 `toast.success()` 弹出提示。 ```vue 主要按钮 次要按钮 文字按钮 禁用状态 ``` ### 外观变体 `contained` 实底、`outlined` 描边、`tonal` 浅底、`text` 纯文字。 ```vue contained outlined tonal text ``` ### 语义色 六种语义色与组件的 `--m-*` 主题令牌一一对应,切换主题时无需改动业务代码。 ```vue {{ color }} ``` ### 尺寸 `small` / `default` / `large` 三档,高度取自全局令牌 `--m-size-small`(28px)、`--m-size-default`(36px)、`--m-size-large`(48px)。 ```vue small default large ``` ### 加载态 `loading` 会显示旋转指示器并禁用按钮,适合提交类操作。 ```vue {{ loading ? '提交中' : '点击提交' }} 加载中 text 加载 ``` ### 形状 `shape="round"` 得到胶囊形,`shape="circle"` 得到正方形圆形按钮(配合 `MIcon` 做纯图标按钮)。 ```vue 圆角按钮 round ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info'` | `undefined` | 语义色;在 `MButtonGroup` 内若未设置则继承组的 `color` | | `variant` | `'text' \| 'outlined' \| 'contained' \| 'tonal' \| 'circle'` | `'text'` | 外观变体 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `shape` | `'round' \| 'circle' \| ''` | `''` | 形状:胶囊 / 圆形 / 默认 | | `disabled` | `boolean` | `false` | 是否禁用 | | `loading` | `boolean` | `false` | 加载态:显示旋转指示器并禁用交互 | | `block` | `boolean` | `false` | 占满父容器宽度(`shape="circle"` 时忽略) | | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | 原生 `button` 的 `type` | | `throttle` | `number` | `200` | 点击事件节流时间(ms) | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `click` | `event: MouseEvent` | 点击事件;经过 `throttle` 节流,`disabled` / `loading` 时不触发 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 按钮内容,可混排文本与 `MIcon` | ## 使用建议 ::: tip 按钮内容可以带图标 `MButton` 内部是 flex 布局,直接往默认插槽里放 `MIcon` 即可自动对齐,不需要额外包一层容器。 ```vue 新增成员 ``` ::: ::: warning 节流不是防抖 默认 `throttle = 200`,意味着 200ms 内的连续点击会被丢弃。如果业务上确实需要「每次点击都有响应」,显式传 `:throttle="0"`。 ::: ::: warning 组内变体会被父级覆盖 把 `MButton` 放进 `MButtonGroup` 后,`variant` 与 `size` 会被组的同名属性覆盖(`color` 仅在自身未设置时才取组值),这是为了避免组内按钮外观不一致。 ::: ## 相关组件 - [ButtonGroup 按钮组](/components/button-group):把多个 `MButton` 拼成一个整体。 - [Icon 图标](/components/icon):按钮里的图标来自 `@mdi/js`。 --- # ButtonGroup 按钮组 把若干个 `MButton` 拼成一个视觉整体,适合「左中右」这类互斥或连续的操作用途。组本身不渲染任何按钮,只通过 `provide/inject` 向内部按钮统一下发 `variant` / `color` / `size` / `disabled`,因此组内按钮的外观天然一致。 ## 代码演示 ### 基础用法 `MButtonGroup` 必须搭配 `MButton` 使用,组只负责统一外观与圆角拼接。 **演示说明:** 组内按钮通过 `provide/inject` 拿到组下发的上下文,点击时各自抛出 `click`;组本身不代表某次选中状态,需要互斥选中请自行记录当前项。 ```vue {{ label }} ``` ### 外观变体 组上的 `variant` 会覆盖内部每个按钮自己的 `variant`,即使子按钮显式传了 `variant` 也会被组改写。 **演示说明:** 非 `outlined` 变体下,组内按钮之间的分隔边框会转为透明,只保留拼接后的整体轮廓。 ```vue {{ variant }} 中间 右侧 ``` ### 尺寸 `small` / `default` / `large` 三档由组统一决定,子按钮传 `size` 无效。 **演示说明:** `size` 未设置时组会在上下文里写入 `'default'`,所以「不传」并不等于「子按钮自己决定」,而是统一按 `default` 渲染。 ```vue {{ size }} 中间 右侧 ``` ### 语义色与子级覆盖 `color` 是唯一「子优先」的属性:子按钮自己设置了 `color` 就用自己的,没设置才继承组的。 **演示说明:** 每组的最后一个按钮都显式写了 `color="info"`,用于对比组色被覆盖的效果。 ```vue {{ color }} 中间 info ``` ### 禁用 `disabled` 取父子并集:组禁用则整组禁用,组不禁用时单个子按钮仍可自行禁用。 **演示说明:** 点击下方按钮可实时切换整组的禁用状态。 ```vue 左 中 右 可用 子级禁用 可用 {{ groupDisabled ? '恢复整组' : '禁用整组' }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `variant` | `'text' \| 'outlined' \| 'contained' \| 'tonal' \| 'circle'` | `'outlined'` | 组内按钮统一变体;**始终覆盖**子按钮自身的 `variant` | | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info'` | `undefined` | 组内按钮统一语义色;子按钮自己设置了 `color` 时以子为准 | | `size` | `'small' \| 'default' \| 'large'` | `undefined`(上下文回落 `'default'`) | 组内按钮统一尺寸;**始终覆盖**子按钮自身的 `size` | | `disabled` | `boolean` | `false` | 是否整体禁用组内所有按钮(与子按钮的 `disabled` 取并集) | ### Events 无。 ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 组内的 `MButton` 列表 | ### 组内上下文 组通过 `provide/inject` 把下面的对象下发给内部按钮(`ButtonGroupContext`),子按钮读取后按各自的优先级合并: ```ts export interface ButtonGroupContext { color?: ButtonColor variant: ButtonVariant size: 'small' | 'default' | 'large' disabled: boolean } ``` 合并规则: | 属性 | 合并方式 | 结果 | | --- | --- | --- | | `variant` | 组优先 | 子按钮的 `variant` 被忽略 | | `size` | 组优先 | 子按钮的 `size` 被忽略(组未传时按 `default` 覆盖) | | `color` | 子优先 | 子按钮未设置时才取组的颜色 | | `disabled` | 取并集 | 组或子按钮任一为 `true` 即禁用 | ## 使用建议 ::: tip 组只统一外观,不管理选中状态 `MButtonGroup` 是纯样式与上下文容器,没有 `modelValue` 也不抛 `change`。需要「单选按钮组」这类互斥语义时,用业务里的 `ref` 记录当前项,再在按钮的 `click` 里更新即可。 ::: ::: warning 子按钮的 variant 与 size 会被覆盖 这是刻意的设计:组内按钮外观必须一致,所以组的 `variant` / `size` 永远赢。只有 `color` 允许子按钮覆盖。如果你的按钮需要各自不同的变体,就不要把它们放进同一个组。 ```vue 仍然按 contained + small 渲染 ``` ::: ::: warning 组内按钮的圆角与边框被改写 组会给首尾按钮加外圆角、给中间按钮去掉圆角,并把相邻按钮的 `margin-left` 设为 `-1px` 让边框重叠。如果按钮原本带了 `shape="round"` 或 `shape="circle"`,拼接效果会和预期不同。 ::: ## 相关组件 - [Button 按钮](/components/button):组内唯一可用的子组件,`variant` / `color` / `size` 都会被组接管。 - [Fab 悬浮按钮](/components/fab):另一种操作入口,通常单独出现而不成组。 --- # Icon 图标 图标是按钮、列表项、状态提示等几乎所有组件的视觉零件。`MIcon` 只做一件事:把一个 24x24 viewBox 的 SVG path 字符串渲染成可着色、可缩放、可旋转的 SVG,颜色默认跟随 `currentColor`,因此放进任何容器都会自动继承文字色。 ## 代码演示 ### 基础用法 `path` 来自 `@mdi/js`,按需 import 单个图标常量即可,不会把整套图标打进产物。 **演示说明:** 点击任意图标会弹出提示。`title` 同时提供悬停提示与无障碍名称。 ```vue ``` ### 尺寸 `size` 传数字按 px 处理,传字符串则原样输出,可以用 `em` 这类相对单位跟随父级字号。 **演示说明:** `size` 会同时写到 `width`、`height` 与 `font-size` 上。 ```vue ``` ### 语义色 六种语义色与主题令牌一一对应;不传 `color` 时继承 `currentColor`,`disabled` 使用禁用态文字色。 **演示说明:** 紫色的那个图标没有设置 `color`,颜色来自外层 `span` 的 `color`。 ```vue ``` ### 翻转与旋转 `flip` 处理镜像,`rotate` 处理旋转,两者同时设置时会合并成一个 `transform`:先镜像再旋转。 **演示说明:** 第一行依次是原始、`horizontal`、`vertical`、`both`;第二行依次是 `rotate` 45 / 90 / 180 / 270 度。 ```vue ``` ### 自旋 `spin` 用于加载、同步这类需要持续动效的场景。 **演示说明:** 开启 `spin` 后图标以 1.6s 线性循环旋转;系统开启「减少动态效果」时该动画会被禁用。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `path` | `string` | `undefined` | SVG path data,通常来自 `@mdi/js`;也可传入任意 24x24 viewBox 的单条 path `d` 字符串 | | `size` | `number \| string` | `24` | 图标尺寸,数字按 px;字符串原样输出(如 `'1.5em'`) | | `color` | `'primary' \| 'secondary' \| 'error' \| 'success' \| 'warning' \| 'info'` | `undefined` | 语义色;未提供时继承 `currentColor` | | `disabled` | `boolean` | `false` | 禁用态:使用 `--m-action-disabled` 文字色 | | `flip` | `'horizontal' \| 'vertical' \| 'both'` | `undefined` | 翻转方向 | | `rotate` | `number` | `0` | 旋转角度(deg),正向为顺时针 | | `spin` | `boolean` | `false` | 自旋动画(加载、同步等场景) | | `title` | `string` | `undefined` | 无障碍可访问名称;缺省时图标对屏幕阅读器隐藏 | ### Events 无。 ### Slots 无(不支持插槽内容,图标只渲染 `path`)。 ### 类型定义 ```ts export type IconColor = | 'primary' | 'secondary' | 'error' | 'success' | 'warning' | 'info' export type IconFlip = 'horizontal' | 'vertical' | 'both' ``` ## 使用建议 ::: tip 图标名在 @mdi/js 里按需 import `MIcon` 不内置任何图标,`path` 必须由使用方提供。安装 `@mdi/js` 后按需引入,避免整套图标进入产物: ```bash npm i @mdi/js ``` ```vue ``` [图标检索页](/guide/icons) 可以按名称搜索并直接复制常量名。 ::: ::: tip 组件上挂的点击事件会落到 svg 上 `MIcon` 没有声明 emits,所以 `@click` 这类监听器会作为普通属性透传到根 `` 上。做可点击图标时建议外面套一层 `MButton`,语义与焦点行为更完整。 ::: ::: warning 只支持单条 path 组件内部只渲染一个 ``。需要多路径的图标(例如带镂空的组合图形)要自己用内联 `` 写,或者换一个单路径的图标。 ::: ::: warning path 为空不会报错 `path` 为空时组件渲染一个占位 ``(透明、不占视觉),而不是抛错或警告。图标没显示出来时,先确认 `@mdi/js` 的常量名拼写与 import 是否漏了。 ::: ## 相关组件 - [Button 按钮](/components/button):按钮里最常出现的图标用法,直接放进默认插槽即可对齐。 - [Fab 悬浮按钮](/components/fab):`icon` 插槽接收任意内容,通常放一个 `MIcon`。 --- # Divider 分割线 用一条细线区隔相邻内容,把视觉上的「一个整体」切成有层级的几块。颜色、粗细、间距都通过 CSS 变量下发,改主题色时分割线会自动跟随。注意它的默认方向是**纵向**,横向分割线必须显式声明。 ## 代码演示 ### 横向分割线 横向分割线需要显式写 `:vertical="false"`,宽度恒为父容器宽度的 100%。 **演示说明:** `` 默认渲染的是纵向线,所以水平方向必须写 `:vertical="false"`。 ```vue {{ blocks[0] }} {{ blocks[1] }} ``` ### 纵向分割线(默认) 纵向线的高度是父容器的 100%,所以父容器必须有确定高度,否则线会塌成 0。 **演示说明:** 这里把分割线放进一个高 48px 的 flex 行里,当成图标之间的分隔符使用。 ```vue ``` ### 颜色、粗细与间距 `color`、`size`、`gap` 三个属性会分别写进 `--m-divider-color`、`--m-divider-size`、`--m-divider-gap`。 **演示说明:** `size` 必须带单位(如 `2px`),`gap` 在横向线里表现为上下外边距、在纵向线里表现为左右外边距。 ```vue {{ item.label }} ``` ### 列表分隔 在纵向堆叠的列表里用分割线切开每一项,是它最常见的用法。 **演示说明:** 循环渲染列表项,除最后一项外都插入一条分割线。 ```vue {{ item }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `vertical` | `boolean` | `true` | 是否纵向。**默认为 `true`**,纵向时高度 100%、使用左边框;横向需显式传 `false` | | `color` | `string` | `undefined`(回退 `var(--m-border)`) | 线条颜色,下发到 `--m-divider-color` | | `size` | `string` | `undefined`(回退 `'1px'`) | 线条粗细,下发到 `--m-divider-size`;需要带单位 | | `gap` | `string` | `'8px'` | 间距,下发到 `--m-divider-gap`;横向为上下外边距,纵向为左右外边距 | | `width` | `string` | `undefined` | **声明了但实现中未引用**(横向宽度恒为 100%) | | `height` | `string` | `'0'` | **声明了但实现中未引用**(横向高度恒为 0) | ### Events 无。 ### Slots 无。 ## 使用建议 ::: tip 靠 CSS 变量定制,不靠新属性 组件把 `color` / `size` / `gap` 转成三个 CSS 变量再消费。需要更细的控制(虚线、渐变、动画)时,直接在自己的样式里覆盖 `--m-divider-color` 等变量或给容器加类名更省事,不必等组件加新属性。 ```vue ``` ::: ::: warning vertical 默认是 true,这一点最容易踩 `` 默认渲染**纵向**线,横向写法必须显式声明: ```vue ``` 如果直接写 `` 却发现「什么都没显示」,通常是因为父容器没有高度,纵向线被压成了 0。 ::: ::: warning width / height 两个属性目前不生效 `width` 与 `height` 都已声明在 props 里,但实现中并未使用:横向线宽度恒为容器的 100%、高度恒为 0,纵向线高度恒为 100%、宽度恒为 0。想改尺寸请通过间距和容器的尺寸来控制,不要指望这两个属性。 ::: ## 相关组件 - [Icon 图标](/components/icon):纵向分割线常用来分隔一排图标。 - [ButtonGroup 按钮组](/components/button-group):如果目的是把按钮拼成整体,用按钮组而不是分割线。 --- # Fab 悬浮按钮 悬浮在界面之上、始终可达的主操作入口。`MFab` 有两种形态:默认的圆形图标按钮,以及 `extended` 打开的图标加文字胶囊。传入 `position` 后按钮会脱离文档流,固定在视口角落后随页面滚动保持可见。 ## 代码演示 ### 基础用法 不传 `position` 时按钮留在文档流里,跟普通块级元素一样参与布局。 **演示说明:** `size` 的档位是 `small` / `medium` / `large`(注意不是 `default`),对应 40 / 56 / 72px。 ```vue ``` ### 四角定位 `position` 取四个角之一时按钮改用 `fixed` 定位,距视口边缘 24px。 **演示说明:** 真实使用时按钮固定在视口角落;这里为了演示放在相对定位容器内,舞台上的 `transform` 会让 `fixed` 按钮以舞台为参照。 ```vue ``` ### 扩展模式 `extended` 把按钮变成图标加文字的胶囊,适合能明确写出动作名的场景。 **演示说明:** `icon` 插槽可以替换内置的加号图标,默认插槽渲染文字标签。 ```vue {{ action.label }} ``` ### 禁用 `disabled` 会同时设置原生 `disabled` 与禁用样式,点击事件不再触发。 **演示说明:** 点击下方按钮可以实时切换禁用状态,观察阴影与背景色的变化。 ```vue 新建 {{ disabled ? '恢复可用' : '切换为禁用' }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 语义色 | | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸,对应 40 / 56 / 72px | | `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' \| ''` | `''` | 悬浮定位;传值后使用 `fixed` 定位并贴到对应角落,距视口边缘 24px;空字符串表示留在文档流 | | `extended` | `boolean` | `false` | 扩展模式:图标与文字横排的胶囊形 | | `disabled` | `boolean` | `false` | 是否禁用 | | `ariaLabel` | `string` | `undefined` | 无障碍标签,纯图标无文字时建议传入 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `click` | `event: MouseEvent` | 点击事件;`disabled` 时不触发 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `icon` | 无 | 自定义图标;未提供时渲染内置加号图标 | | `default` | 无 | 文字标签;`extended` 为 `true` 或存在默认插槽内容时渲染 | ## 使用建议 ::: tip 纯图标按钮记得写 aria-label 默认插槽为空时按钮里只有一个加号图标,屏幕阅读器读不出含义,请通过 `ariaLabel` 补上动作名。 ```vue 新建任务 ``` ::: ::: tip 组件自带涟漪 `MFab` 内部已经挂好了 `v-ripple`,按下即有 Material 水波纹,不需要再自己加指令。 ::: ::: warning position 会让按钮脱离文档流 `position` 非空时按钮是 `position: fixed`,会脱离父容器的布局参与,父级的 `overflow: hidden` 也拦不住它。一个页面里通常只放一个位置固定的 FAB;同类动作不要在多处重复固定,否则会互相叠加遮挡。 另外 `position: fixed` 只认「视口」或「带 transform / filter / contain 的祖先」作为定位参照,普通的 `position: relative` 祖先不会把它框住——这也是文档演示里给舞台加 `transform` 的原因。 ::: ## 相关组件 - [Button 按钮](/components/button):常规尺寸的操作用 `MButton`,FAB 用于全局唯一的强操作。 - [Icon 图标](/components/icon):`icon` 插槽里通常放一个 `MIcon`。 --- # Input 输入框 单行文本录入的基础控件。只承载「取值」这一件事:值完全由 `v-model` 控制,组件内部不缓存输入内容,因此可以安全地做格式化、过滤或异步校验。 ## 代码演示 ### 基础用法 `v-model` 绑定字符串,输入过程实时同步;下方两个输入框共用同一个 `ref`,在任意一个里输入都会同步到另一个。 ```vue ``` ### 尺寸 `size` 三档对应全局令牌 `--m-size-small`(28px)、`--m-size-default`(36px)、`--m-size-large`(48px)。 ```vue ``` ### 前后缀插槽 `prefix` / `suffix` 分别渲染在输入框左右两侧,可以直接放 `MIcon`、货币符号或纯文本。 **演示说明:** 往 `suffix` 里放计数表达式,就是一个现成的字数指示器。 ```vue {{ keyword.length }} ¥ .00 ``` ### 清除与字数限制 `clearable` 在「有值且非只读、非禁用」时显示清除按钮,点击后清空并重新聚焦;`maxlength` 透传给原生 `input`。 **演示说明:** 点击清除会触发 `clear` 事件,这里用 `toast.info()` 给出反馈。 ```vue {{ limited.length }}/10 ``` ### 只读、禁用与原生 type `readonly` 保留聚焦与选中复制能力但不接受输入,`disabled` 完全阻断交互;`type` 直接透传给原生 `input`。 ```vue ``` ### 圆角与宽度 `radius` 接受任意 CSS 长度并写入 `--m-control-radius`,`width` 覆盖组件默认宽度。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string` | `undefined`(内部按 `''` 处理) | 输入值(`v-model`) | | `type` | `string` | `'text'` | 原生 `input` 的 `type` | | `placeholder` | `string` | `undefined`(未提供) | 占位文本 | | `disabled` | `boolean` | `false` | 是否禁用 | | `readonly` | `boolean` | `false` | 是否只读 | | `clearable` | `boolean` | `false` | 有值时显示清除按钮(`readonly` / `disabled` 时不显示) | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 高度取全局 `--m-size-*` 令牌:small 28px / default 36px / large 48px | | `maxlength` | `number` | `undefined`(未提供) | 最大输入长度(原生 `maxlength`) | | `name` | `string` | `undefined`(未提供) | 原生 `input` 的 `name`(表单提交) | | `autofocus` | `boolean` | `undefined`(未提供) | 自动聚焦 | | `width` | `string` | `undefined`(未提供;样式默认 220px) | 宽度 | | `radius` | `string` | `undefined`(未提供) | 输入框圆角(任意 CSS 长度,如 `'8px'`);默认 4px | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: string` | 输入值变化 | | `focus` | `event: FocusEvent` | 聚焦 | | `blur` | `event: FocusEvent` | 失焦 | | `change` | `value: string` | 失焦时触发 | | `clear` | 无 | 点击清除按钮 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `prefix` | 无 | 输入框左侧附加内容 | | `suffix` | 无 | 输入框右侧附加内容 | ### 类型定义 ```ts export type InputSize = 'small' | 'default' | 'large'; ``` `Expose`:源码未暴露实例方法,需要操作原生输入框时请通过 `focus` / `blur` 事件配合模板引用处理。 ## 使用建议 ::: tip change 是「失焦时」触发 `change` 不是每次输入都抛,而是失焦(`blur`)时抛出当前值,语义与原生 `change` 一致。需要实时响应请用 `update:modelValue`(即 `v-model`)。 ::: ::: warning 清除按钮的出现条件 清除按钮仅在 `clearable && !disabled && !readonly && 有值` 时渲染。如果只读输入框也需要「清空」动作,请在业务层放一个独立的按钮。 ::: ::: warning v-model 值请用字符串 `modelValue` 的类型是 `string`。传入 `number` 不会被自动转换,而是被 `String()` 处理后渲染,回写时仍是字符串——需要数字请改用 [InputNumber 数字输入框](/components/input-number)。 ::: ## 相关组件 - [InputNumber 数字输入框](/components/input-number):数值录入、步进与精度控制。 - [Select 选择器](/components/select):从固定选项中选择,替代自由文本。 - [Checkbox 复选框](/components/checkbox) / [Radio 单选框](/components/radio):布尔值与互斥单选的录入。 --- # InputNumber 数字输入框 受控的数值录入控件。所有写回都经过同一套 `parse → clamp → precision → commit` 流程,因此外部拿到的 `modelValue` 永远是「合法且在区间内」的值,业务侧不必再做一次校验与取整。 ## 代码演示 ### 基础用法 `v-model` 绑定 `number | null`,默认步进 1,`min` / `max` 会把越界值收敛到边界。 ```vue ``` ### 控制器布局 `controls="right"` 在右侧并排上下箭头,`controls="both"` 在左右两侧放减号与加号。 ```vue ``` ### 上下限 到达边界后对应的步进按钮会自动禁用,手动输入越界数字也会被收敛。 ```vue ``` ### 精度与步长 `precision` 在小数录入、金额计算这类场景下避免浮点尾数;`step` 支持小数。 **演示说明:** 第一个输入框把 `change` 抛出的值弹成 toast,可以直观看到精度归一化后的结果。 ```vue ``` ### 尺寸 与 `MInput` 同体系,高度取全局 `--m-size-*` 令牌。 ```vue ``` ### 清空、只读与禁用 `clearable` 清空后把值置为 `null` 并重新聚焦;只读与禁用都会同时停用步进按钮。 **演示说明:** 非数字输入会在失焦时被解析成 `null`。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `number \| null` | `undefined`(未提供) | 数值(`v-model`);空值传 `undefined` / `null` 均可 | | `placeholder` | `string` | `undefined`(未提供) | 占位文本 | | `disabled` | `boolean` | `false` | 是否禁用 | | `readonly` | `boolean` | `false` | 是否只读 | | `clearable` | `boolean` | `false` | 是否显示清除按钮 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 高度取全局 `--m-size-*` 令牌:small 28px / default 36px / large 48px | | `controls` | `'right' \| 'both'` | `'right'` | 步进按钮布局:`right` 右侧并排(down / up 箭头)/`both` 左右两侧(减号 / 加号) | | `min` | `number` | `undefined`(内部按 `-Infinity`) | 最小值 | | `max` | `number` | `undefined`(内部按 `+Infinity`) | 最大值 | | `step` | `number` | `1` | 步进值 | | `precision` | `number` | `undefined`(未提供) | 显示精度,归一化时按该精度四舍五入 | | `width` | `string` | `undefined`(未提供;样式默认 180px) | 宽度 | `MInputNumber` **没有** `radius` 属性,圆角固定取全局令牌 `--m-radius`。 ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: number \| null` | 值变化(清空或非法输入为 `null`) | | `focus` | `event: FocusEvent` | 聚焦 | | `blur` | `event: FocusEvent` | 失焦 | | `change` | `value: number \| null` | 归一化后的值发生变化时触发(失焦 / 步进 / 清空) | | `clear` | 无 | 点击清除按钮 | ### Slots 无。 ### 类型定义 ```ts export type InputNumberControls = 'right' | 'both'; // size 复用 InputSize = 'small' | 'default' | 'large' ``` ::: warning InputNumberControls 未从包入口导出 `miao-design` 的入口只再导出了 `MInputNumberProps`,`InputNumberControls` 这个类型别名不在入口的导出清单里。需要标注类型时,直接写字面量 `'right' | 'both'`,或者用索引访问 `MInputNumberProps['controls']`。 ::: ## 使用建议 ::: tip 回车提交,Escape 放弃 输入框内的值在失焦时才会写回外部。为了避免「输入后按回车看着生效、外部还是旧值」,组件额外支持:**回车立即提交**,**Escape 放弃本次编辑**回到外部传入的值。键盘上下键也可以步进。 ::: ::: warning 空值与非法输入的语义 空字符串、纯空格、`Number()` 解析为 `NaN` 的输入都会在提交时变成 `null`,而不是 `0`。如果业务需要一个非空默认值,请在提交前兜底(例如 `value ?? 0`)。 ::: ::: warning change 只在值真正变化时抛 `commit()` 内部比较归一化后的值与原 `modelValue`,相同则不抛 `update:modelValue` / `change`。因此「输入 10 再失焦」「输入 11 被 `max=10` 收敛回 10」都不会产生事件,不要依赖 `change` 计数。 ::: ## 相关组件 - [Input 输入框](/components/input):自由文本录入。 - [Slider 滑动条](/components/slider):需要连续拖拽调值时比步进按钮更顺手。 - [Select 选择器](/components/select):取值来自固定选项集合时改用下拉。 --- # Select 选择器 从固定选项集合中取值。单选用单值 `v-model`,多选改用数组;面板定位自带视口夹紧、上下翻转与滚动跟随,长列表在面板内部滚动,不会把页面顶开。 ## 代码演示 ### 基础用法 `options` 是 `{ label, value }` 组成的数组,未选中时显示 `placeholder`。 **演示说明:** 点击触发区展开面板,选择后单选面板自动收起。 ```vue ``` ### 多选 `multiple` 打开后 `modelValue` 必须改为数组,触发区把已选项的 `label` 用逗号拼接展示。 ```vue ``` ### 选项图标与禁用项 `SelectOption.icon` 是文本图标(emoji / 字符),选中后会一并出现在触发区;`disabled` 的选项不可点击,键盘导航也会跳过。 ```vue ``` ### 尺寸与圆角 `size` 控制触发区高度,`radius` 只作用于触发区(面板圆角是 `panelRadius`)。 ```vue ``` ### 面板宽度与圆角 面板宽度以触发区为下限、随最长选项自动撑开,视口右侧放不下时会压缩;高度上限 280px,超出后内部滚动。 **演示说明:** 左侧 14 个选项触发内部滚动,右侧的长 label 会把面板撑得比触发区宽。 ```vue ``` ### 清空与禁用 清除按钮在「有值、未禁用」时出现;单选清除抛 `undefined`,多选清除抛 `[]`。 **演示说明:** 两个 `change` 处理器会把抛出的值弹成 toast。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number \| (string \| number)[] \| undefined` | `undefined`(未提供) | 当前值(单选为单值,多选为数组) | | `options` | `SelectOption[]` | `[]`(工厂函数) | 选项列表 | | `placeholder` | `string` | `undefined`(未提供) | 占位文本 | | `disabled` | `boolean` | `false` | 是否禁用 | | `clearable` | `boolean` | `false` | 有值时显示清除按钮 | | `multiple` | `boolean` | `false` | 多选模式:`modelValue` 为数组 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 高度取全局 `--m-size-*` 令牌:small 28px / default 36px / large 48px | | `width` | `string` | `undefined`(未提供;样式默认 220px) | 触发区宽度 | | `radius` | `string` | `undefined`(未提供;默认 8px) | 触发区圆角(任意 CSS 长度,如 `'8px'`) | | `panelRadius` | `string` | `undefined`(未提供;默认 8px) | 弹出面板圆角(任意 CSS 长度) | | `popupClass` | `string` | `undefined`(未提供) | 弹出面板额外类名(面板 Teleport 到 body,需配合全局样式使用) | | `appendToBody` | `boolean` | `true` | 是否将面板挂载到 body;在 Chrome 扩展 popup 等受限容器中建议设为 `false` | | `lockScroll` | `boolean` | `true` | 打开面板时是否锁定 body 滚动 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: SelectValue` | 选中变化(多选为数组;单选清除为 `undefined`,多选清除为 `[]`) | | `change` | `value: SelectValue` | 选中变化,与 `update:modelValue` 同值同刻抛出 | ### Slots 无。 ### 类型定义 ```ts export interface SelectOption { label: string; value: string | number; icon?: string; iconUrl?: string; disabled?: boolean; } export type SelectValue = string | number | (string | number)[] | undefined; export type SelectSize = InputSize; // 'small' | 'default' | 'large' ``` `Expose`:源码未暴露实例方法。 ### 面板行为 - **定位**:默认 Teleport 到 `body`,用 `fixed` 定位;下方空间不足时向上翻转,两侧都不够时选空间较大的一侧并把高度锁在该侧空间内(面板始终完整落在视口内)。 - **跟随**:滚动或缩放时重新读取触发区位置并重算定位;触发区整体滚出视口后面板收起。 - **inline 模式**:`appendToBody="false"` 时面板 `absolute` 定位在组件根节点内,宽度固定等于触发区宽度,且不转移焦点,适合受限容器。 - **键盘**:未展开时 `Enter` / `Space` / `ArrowUp` / `ArrowDown` 展开;展开后上下键移动、`Home` / `End` 跳首尾、`Enter` 选中、`Escape` 关闭并归还焦点、`Tab` 关闭面板继续走焦。 ## 使用建议 ::: tip 多选时 modelValue 必须是数组 `multiple` 与 `modelValue` 的类型必须成对出现。多选时传了单值,组件会按空数组处理,表现为「选了不显示」。 ::: ::: warning 面板内容不可自定义 面板由 `options` 数据驱动,**没有**默认插槽、选项插槽或空状态插槽,也不支持选项分组与多列布局。空列表会渲染内置的「无匹配选项」文案。需要完全自定义的下拉内容时请改用 [Dropdown 下拉菜单](/components/dropdown)。 ::: ::: warning icon 与 iconUrl 的区别 `icon` 是直接渲染在选项里的**文本**(emoji、字符),不是 `MIcon` 的 path;要放图片请用 `iconUrl`(作为 `background-image`),两者同时存在时 `iconUrl` 优先。 ::: ## 相关组件 - [Dropdown 下拉菜单](/components/dropdown):需要自定义面板内容时使用。 - [Radio 单选框](/components/radio) / [Checkbox 复选框](/components/checkbox):选项数量少且需要全量可见时,比下拉更省一次点击。 - [Input 输入框](/components/input):候选项不固定、需要自由输入时使用。 --- # Checkbox 复选框 `MCheckbox` 与 `MCheckboxGroup` 是一对组合:单个复选框负责「是 / 否」,复选框组负责「从若干项里挑若干项」。组件内部通过 `provide / inject` 通信,因此同一个 `MCheckbox` 放在组内外时用的是两套完全不同的取值与抛事件逻辑。 ## 代码演示 ### 基础用法 上面是独立使用的 `MCheckbox`(`v-model` 为 `boolean`),下面是 `MCheckboxGroup` + 带 `value` 的子项(`v-model` 为数组)。 **演示说明:** 点击任意一项,观察自己维护的两个 `ref` 如何变化。 ```vue 已阅读并同意《用户协议》 可读 可写 ``` ### 复选框组 组的 `color` / `size` 会下发给所有子项,`vertical` 改为纵向排列,子项可用同名属性单独覆盖。 ```vue {{ stack.label }} 当前选中:{{ picked.join('、') || '(空数组)' }} ``` ### 半选状态 `indeterminate` 只改变勾选角标的图形与 `aria-checked`,配合「全选」联动使用。 ```vue 全选 {{ item }} 选中 {{ picked.length }} / {{ items.length }} ``` ### 禁用 独立的复选框与整组都可以禁用,禁用后点击不再触发任何事件。 ```vue 独立禁用(已选中) 独立禁用(未选中) 整组禁用 A 整组禁用 B ``` ### 颜色与尺寸 六种语义色与三档尺寸,组内继承时可被单项覆盖。 ```vue {{ color }} small default large 继承组的 error / large 单项覆盖为 info / small ``` ## API ### MCheckbox Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `value` | `CheckboxValue` | `undefined`(未提供) | 组内项值:在 `MCheckboxGroup` 内用于维护选中数组 | | `modelValue` | `boolean` | `undefined`(未提供) | 独立使用时的选中状态(`v-model`,`boolean`);组内忽略此值 | | `indeterminate` | `boolean` | `false` | 半选状态:勾选角标显示为横线(通常用于「全选」联动) | | `name` | `string` | `undefined`(未提供) | 原生 `input` 的 `name` | | `disabled` | `boolean` | `false` | 是否禁用 | | `color` | `CheckboxColor` | `undefined`(组内继承,独立时回落 `'primary'`) | 选中态颜色 | | `size` | `CheckboxSize` | `undefined`(组内继承,独立时回落 `'default'`) | 尺寸 | ### MCheckboxGroup Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `CheckboxValue[]` | `undefined`(内部按 `[]` 处理) | 选中值数组(`v-model`) | | `disabled` | `boolean` | `false` | 是否禁用整组 | | `color` | `CheckboxColor` | `undefined`(内部回落 `'primary'`) | 组内 `MCheckbox` 的统一颜色 | | `size` | `CheckboxSize` | `undefined`(内部回落 `'default'`) | 组内 `MCheckbox` 的统一尺寸 | | `vertical` | `boolean` | `false` | 纵向排列 | ### MCheckbox Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 独立使用时选中状态变化 | | `change` | `value: boolean` | 独立使用时选中状态变化 | ### MCheckboxGroup Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: CheckboxValue[]` | 选中数组变化(勾选 / 取消) | | `change` | `value: CheckboxValue[]` | 选中数组变化(勾选 / 取消) | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | `MCheckbox` 的标签文本;`MCheckboxGroup` 的子项列表 | ### 类型定义 ```ts export type CheckboxColor = | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'; export type CheckboxSize = 'small' | 'default' | 'large'; export type CheckboxValue = string | number | boolean; export interface CheckboxGroupContext { modelValue: Ref; color: CheckboxColor; size: CheckboxSize; disabled: boolean; toggleValue: (value: CheckboxValue, checked: boolean) => void; } ``` `Expose`:两个组件源码均未暴露实例方法。 ### 组内使用:值从哪来,事件由谁抛 | 场景 | `v-model` 类型 | 是否必须传 `value` | 事件由谁抛出 | | --- | --- | --- | --- | | 独立使用 | `boolean` | 不需要 | `MCheckbox` 自己抛 `update:modelValue` / `change` | | 放在 `MCheckboxGroup` 内 | `CheckboxValue[]`(绑定在组上) | **必须** | 只有 `MCheckboxGroup` 抛,子组件不抛 | 组内子项的选中状态由 `group.modelValue.value.includes(props.value)` 计算,勾选 / 取消统一走组的 `toggleValue(value, checked)`。 ## 使用建议 ::: tip 半选是纯展示状态 `indeterminate` 只影响角标图形与 `aria-checked="mixed"`,**不参与** `checked` 的计算,也不会随点击自动清除。搭配「全选」使用时,请把 `indeterminate` 写成由选中数量推导的 `computed`: ```vue (picked = checked ? [...all] : [])" /> ``` ::: ::: warning 组内不传 value 会出错 组内子项的选中判断是 `includes(props.value)`。不传 `value` 时每一项都以 `undefined` 参与比对,会出现「点一项全部选中」的错误表现。放进组就必须给 `value`。 ::: ::: warning 组内子组件不再抛事件 在 `MCheckboxGroup` 里给子项写 `@change` 是无效的——子组件只在独立使用时才 `emit`,组内一律交给组的 `toggleValue` 处理。需要感知变化请监听组的事件。 ::: ::: warning 组禁用会吞掉点击 组 `disabled` 为真时 `toggleValue` 直接返回,子项的 `disabled` 也会被置为禁用态。此时子项的 `@change` 不会被触发。 ::: ## 相关组件 - [Radio 单选框](/components/radio):同样「组 + 子项」的机制,但取值为单值、互斥。 - [Switch 开关](/components/switch):只需一个布尔开关时比复选框更醒目。 - [Select 选择器](/components/select):选项数量多到不适合全部铺开时改用下拉。 --- # Radio 单选框 `MRadio` 与 `MRadioGroup` 的机制与 Checkbox 一组同源,差别只在取值:组维护的是**单值**,子项通过 `group.modelValue === props.value` 判断自己是否被选中,因此天然互斥。独立使用时 `MRadio` 退化为一个布尔开关。 ## 代码演示 ### 基础用法 上面是独立使用的 `MRadio`(`v-model` 为 `boolean`),下面是 `MRadioGroup` + 带 `value` 的子项(`v-model` 为单值)。 ```vue 独立使用 S M L ``` ### 单选框组 组的 `name` 会下发给所有子项,`vertical` 改为纵向排列,`color` / `size` 也由组统一控制。 ```vue {{ plan.label }} 当前选中值:{{ picked }} ``` ### 禁用 既可以禁用组内某一个选项,也可以用组的 `disabled` 一次性禁用整组。 ```vue 基础版 标准版(单项禁用) 专业版 整组禁用 A 整组禁用 B ``` ### 颜色与尺寸 六种语义色与三档尺寸;组内继承的值可被单项覆盖。 ```vue {{ color }} small default large 继承组的 error / large 单项覆盖为 info / small ``` ### name 继承与原生表单 组把 `name` 下发给每个子项,所以单选框组可以直接参与原生表单提交,不需要额外的隐藏字段。 **演示说明:** 点击提交,`FormData.get('plan')` 取到的就是当前选中项的 `value`。 ```vue 基础版 标准版 专业版 提交 FormData.get('plan') = {{ submitted }} ``` ## API ### MRadio Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `value` | `RadioValue` | `undefined`(未提供) | 单选值:在 `MRadioGroup` 内必须提供,用于与组激活值比对 | | `modelValue` | `boolean` | `undefined`(未提供) | 独立使用时的选中状态(`v-model`,`boolean`);组内忽略此值 | | `name` | `string` | `undefined`(组内可继承) | 原生 `input` 的 `name`(表单语义 / 同组互斥) | | `disabled` | `boolean` | `false` | 是否禁用 | | `color` | `RadioColor` | `undefined`(组内继承,独立时回落 `'primary'`) | 选中态颜色 | | `size` | `RadioSize` | `undefined`(组内继承,独立时回落 `'default'`) | 尺寸 | ### MRadioGroup Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `RadioValue` | `undefined`(未提供) | 当前选中值(`v-model`) | | `name` | `string` | `undefined`(未提供) | 原生 `input` 的 `name`,下发给组内所有 `MRadio` | | `disabled` | `boolean` | `false` | 是否禁用整组 | | `color` | `RadioColor` | `undefined`(内部回落 `'primary'`) | 组内统一颜色 | | `size` | `RadioSize` | `undefined`(内部回落 `'default'`) | 组内统一尺寸 | | `vertical` | `boolean` | `false` | 纵向排列:默认横向 flex 排列 | ### MRadio Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 独立使用时被选中(仅在选中时抛,取消不会抛) | | `change` | `value: boolean` | 独立使用时被选中(仅在选中时抛) | ### MRadioGroup Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: RadioValue` | 选中值变化(值未变化时不抛) | | `change` | `value: RadioValue` | 选中值变化 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | `MRadio` 的标签文本;`MRadioGroup` 的子项列表 | ### 类型定义 ```ts export type RadioColor = | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'; export type RadioSize = 'small' | 'default' | 'large'; export type RadioValue = string | number | boolean; export interface RadioGroupContext { modelValue: Ref; color: RadioColor; size: RadioSize; disabled: boolean; name?: string; updateValue: (value: RadioValue) => void; } ``` `Expose`:两个组件源码均未暴露实例方法。 ### 组内使用:与 Checkbox 组的差异 | 维度 | `MCheckboxGroup` | `MRadioGroup` | | --- | --- | --- | | `v-model` 类型 | `CheckboxValue[]`(数组) | `RadioValue`(单值) | | 子项选中判断 | `group.modelValue.includes(value)` | `group.modelValue === value` | | 组内可否取消 | 可以(再次点击取消该项) | 不可以,只能切换到另一项 | 共同点:**组内子项都必须传 `value`**,且组内不再抛 `update:modelValue` / `change`,统一由组抛。 ## 使用建议 ::: tip 选中态会带居中的涟漪反馈 `MRadio` 默认挂载 `v-ripple`(居中模式),点击时有 Material 涟漪动画,不需要额外配置。 ::: ::: warning 组内不传 value 会互斥失效 组内判断是 `group.modelValue === props.value`。不传 `value` 时每一项都用 `undefined` 参与比对,会出现「全部同时选中」或「怎么点都选不中」的表现。 ::: ::: warning 同一页面里多个组请给不同的 name `name` 会透传到原生 `input`。多个单选框组共用同一个 `name` 时,浏览器会把它们当成一个原生单选组,与组件自己的状态判断产生冲突。要么每组用不同的 `name`,要么都不传。 ::: ::: warning 重复选同一项不会抛事件 `updateValue` 在 `value === props.modelValue` 时直接返回。因此点击已选中项不会触发 `change`,需要「点击即上报」的行为请在业务侧自行处理。 ::: ## 相关组件 - [Checkbox 复选框](/components/checkbox):同样的「组 + 子项」结构,但可多选。 - [RadioButtonGroup 按钮式单选组](/components/radio-button-group):用按钮外观呈现单选的场景。 - [Select 选择器](/components/select):选项较多、需要收起时改用下拉。 --- # RadioButtonGroup 按钮单选 把一组互斥选项拼成整块的分段控件,选中的一段以语义色高亮。它是独立组件,内部自带隐藏原生 `input` 与同名互斥逻辑,**不使用 `MRadio` / `MRadioGroup`**,因此与单选框那套的 API、DOM 与样式互不牵连。数据由 `options` 数组驱动,选项可等宽(`block`)或按内容自适应。 ## 代码演示 ### 基础用法 `options` 描述选项,`v-model` 绑定选中值。加 `block` 后各组等宽平分父容器宽度,是视图切换最常见的形态。 **演示说明:** `modelValue` 支持 `string | number | boolean` 三种类型。 ```vue 当前视图:{{ view }} ``` ### 受控切换视图 组件完全是受控的:选中值变化即驱动外部内容切换,适合「列表 / 网格 / 看板」这类视图切换器。 ```vue {{ hints[String(view)] }} ``` ### 禁用项 `option.disabled` 只禁用单个选项,组件的 `disabled` 禁用整组,两者可叠加。 ```vue ``` ### 尺寸 `small` / `default` / `large` 三档,高度取自全局令牌 `--m-size-small`(28px)、`--m-size-default`(36px)、`--m-size-large`(48px)。 ```vue ``` ### 图标与纵向排列 `option.icon` 传入 `@mdi/js` 的 SVG path(与 `MIcon` 同源)。只给 `icon` 不给 `label` 时,会用 `String(value)` 生成可访问名称。`vertical` 让选项纵向排列。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number \| boolean` | `undefined` | 当前选中值(v-model) | | `options` | `RadioButtonGroupOption[]` | `[]` | 选项列表 | | `name` | `string` | `undefined`(自动生成 `m-radio-button-group-{uid}`) | 原生 input name:同名才互斥、方向键才在组内切换;缺省时自动生成 | | `disabled` | `boolean` | `false` | 整体禁用 | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 选中态颜色 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `block` | `boolean` | `false` | 占满父容器宽度,各段等宽平分 | | `vertical` | `boolean` | `false` | 纵向排列(默认横向) | | `ariaLabel` | `string` | `undefined` | 组的可访问名称,写在 `role="radiogroup"` 容器上 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: string \| number \| boolean` | 选中变化(已选中项不重复抛出) | | `change` | `value: string \| number \| boolean` | 选中变化 | ### Slots 无(纯 `options` 数据驱动)。 ### 类型定义 ```ts type RadioValue = string | number | boolean interface RadioButtonGroupOption { /** 选项值:与 modelValue 比对判定选中(组内应唯一) */ value: RadioValue /** 选项文本 */ label?: string /** 图标:@mdi/js 的 SVG path,与 MIcon 的 path 同源 */ icon?: string /** 单独禁用该选项 */ disabled?: boolean } ``` ## 使用建议 ::: tip 与 MRadio 的分工 `MRadio` 用于表单里带文字说明的单选列表,`MRadioButtonGroup` 用于工具栏式的紧凑切换。两者互不依赖,可以同时出现在同一页面,不会共享样式或 `name`。 ::: ::: tip label 与 icon 至少给一项 只给 `icon` 时,组件会用 `String(value)` 兜底生成可访问名称(`aria-label`)。若两者都缺,屏幕阅读器读不出该选项的语义。 ::: ::: warning 选项值比较是严格相等 选中判定用 `option.value === modelValue`,`1` 与 `'1'`、`true` 与 `'true'` 不会互相命中。切换 `modelValue` 类型时要同步改 `options` 里的 `value` 类型。 ::: ::: warning 不传 name 也会自动生成 `name` 缺省时会用 `useId()` 生成唯一值,保证同名互斥与方向键切换正常工作。只有当你要把该组与外部表单的字段对齐时,才需要显式传 `name`。 ::: ## 相关组件 - [Radio 单选框](/components/radio):表单场景的单选组,带文字说明与子项标签。 - [Button 按钮](/components/button):需要的是「点击执行」而非「选中某一项」时使用。 - [Icon 图标](/components/icon):`option.icon` 的 path 与 `MIcon` 同源。 --- # Switch 开关 在两个互斥状态之间即时切换,常用于「开 / 关」型设置项。底层是原生 `checkbox`,因此保留了表单提交、键盘操作与屏幕阅读器语义;默认带居中涟漪反馈。 ## 代码演示 ### 基础用法 `v-model` 绑定布尔值,默认插槽渲染轨道右侧的标签文本。 **演示说明:** 不传默认插槽时不渲染标签容器,只有一枚开关。 ```vue 启用通知 接收周报 通知状态:{{ notify ? '开启' : '关闭' }} ``` ### 尺寸 `small` / `default` / `large` 三档,轨道与拇指同步缩放。 ```vue 小 默认 大 ``` ### 语义色 六种语义色对应选中态的轨道颜色,与全局 `--m-*` 令牌联动。 ```vue {{ color }} ``` ### 禁用与加载 `disabled` 与 `loading` 都不可交互;`loading` 会在拇指内显示旋转指示器,适合状态尚未落库的过渡。 ```vue 禁用 · 开 禁用 · 关 加载 · 开 加载 · 关 ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `boolean` | `undefined`(内部按 `!!` 处理) | 开关状态(v-model) | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 选中态轨道颜色 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `disabled` | `boolean` | `false` | 是否禁用 | | `loading` | `boolean` | `false` | 加载态:不可交互,拇指内显示旋转指示器 | | `name` | `string` | `undefined` | 原生 input name(表单提交) | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 开关状态变化 | | `change` | `value: boolean` | 开关状态变化 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 开关右侧标签文本(存在时才渲染 label 容器) | ## 使用建议 ::: tip 提交表单时补上 name 组件底层是原生 `checkbox`,传入 `name` 后即可随 `` 一起提交。未选中时原生 `checkbox` 不会带上该字段,取值时需要按「缺省即 false」处理。 ::: ::: tip 即时生效 vs 确认后生效 开关默认「拨动即生效」。若切换背后是异步请求,建议在请求期间把 `loading` 置为 `true`,避免用户重复拨动造成状态错乱。 ::: ::: warning loading 会连带禁用原生 input `loading` 为真时内部 `input` 处于 `disabled`(即 `disabled || loading`),点击不会切换状态,同时涟漪反馈也被关闭。 ::: ::: warning modelValue 会被强制布尔化 传入 `0`、`''`、`undefined` 都会被当作 `false`。不要用开关去承载三态(如 `null`)语义。 ::: ## 相关组件 - [Checkbox 复选框](/components/checkbox):需要勾选样式、或需要一次选中多项时使用。 - [Radio 单选框](/components/radio):多于两个互斥状态时应改用单选组。 --- # Slider 滑块 在连续区间内取值,比数字输入框更直观。支持横向与纵向两种布局,拖动过程通过 `requestAnimationFrame` 合帧(每帧最多抛一次),松手或键盘调节时才提交最终值。轨道粗细与拇指直径既可由 `size` 预设整体缩放,也能用 `trackSize` / `thumbSize` 单独覆盖。 ## 代码演示 ### 基础用法 `v-model` 绑定数值,`min` / `max` / `step` 描述取值范围。拖动过程中 `update:modelValue` 持续更新,松手时抛一次 `change`。 **演示说明:** `change` 的参数是拖动结束时的最终值。 ```vue 拖动中:{{ value }},松手后(change):{{ committed }} ``` ### 纵向布局 `vertical` 后轨道自上而下、最小值在底部。组件自身高度是 `100%`,**必须由父容器给出高度**,否则会塌陷。 **演示说明:** 父容器 `height: 160px`,三个纵向滑块共享同一高度。 ```vue 音量 {{ volume }} / 亮度 {{ brightness }} / 缩放 {{ zoom }} ``` ### 尺寸与尺寸覆盖 `size` 三档整体等比缩放(`small` 0.75×、`large` 1.5×)。`trackSize` / `thumbSize` 以 px 覆盖对应部分,优先级高于 `size`。 **演示说明:** 后两条分别只改轨道(细线型)和只改拇指(大触摸目标)。 ```vue ``` ### 语义色 六种语义色对应当前值的填充色,与全局 `--m-*` 令牌联动。 ```vue ``` ### 禁用、气泡与步进 `disabled` 时不可交互;`showLabel="false"` 关闭拖动时跟随拇指的数值气泡;`step` 决定取值的量化粒度。 ```vue 步进值(step = 10):{{ stepped }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `number` | `undefined`(内部按 `min` 处理) | 当前值(v-model) | | `min` | `number` | `0` | 最小值 | | `max` | `number` | `100` | 最大值 | | `step` | `number` | `1` | 步进 | | `disabled` | `boolean` | `false` | 是否禁用 | | `showLabel` | `boolean` | `true` | 拖动 / 键盘调节时显示数值气泡 | | `vertical` | `boolean` | `false` | 纵向布局:轨道自上而下,最小值在底部,需由父容器提供高度 | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 语义色变体 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸预设:同时缩放轨道粗细、拇指直径、描边与光圈 | | `trackSize` | `number` | `undefined` | 轨道粗细 px:覆盖 `size` 预设的轨道值,优先级最高 | | `thumbSize` | `number` | `undefined` | 拇指直径 px:覆盖 `size` 预设的拇指值,优先级最高 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: number` | 值变化(拖动中每帧最多一次) | | `change` | `value: number` | 拖动结束 / 键盘调节时触发 | ### Slots 无。 ### 尺寸预设 `size` 以 `default`(拇指 16px / 轨道 4px)为基准等比缩放,`trackSize` / `thumbSize` 会覆盖对应部分。 | 取值 | 缩放 | 拇指直径 | 轨道粗细 | | --- | --- | --- | --- | | `small` | 0.75× | 12px | 3px | | `default` | 1× | 16px | 4px | | `large` | 1.5× | 24px | 6px | ## 使用建议 ::: tip 纵向滑块要给容器高度 `vertical` 时滑块根元素高度是 `100%`,尺寸完全来自父容器。用一层定高(或 `flex: 1`)的容器包住它是最省事的做法。 ::: ::: tip 拖动中不要做重活 `update:modelValue` 已经按帧合帧,但每一帧仍会触发一次响应式更新。若下游依赖很重,建议只在 `change` 里做真正的业务处理。 ::: ::: warning trackSize / thumbSize 只接受有限正数 传入 `0`、负数或 `NaN` 会被忽略并回退到 `size` 预设值,不会把轨道或拇指变成不可见的尺寸。 ::: ::: warning 点击轨道是跳转而非拖拽 点轨道会立即把值跳到该位置(带过渡),首次移动才进入拖拽态。需要「点击不改变值」的交互时要自行拦截。 ::: ## 相关组件 - [InputNumber 数字输入框](/components/input-number):需要精确输入或键盘长按步进时使用。 - [Progress 进度条](/components/progress):只读展示进度、不接受输入时使用。 --- # DatePicker 日期选择 输入框样式的触发器,点击唤出月历面板。是否带时间选择完全由 `format` 模板决定:纯日期模板选中即回填,带时间占位符的模板则进入「日期 + 时间」模式,需要点「确定」收尾。输出**始终是字符串**,即使传入的是 `Date` 对象。 ## 代码演示 ### 基础用法 默认 `format` 是 `YYYY-MM-DD`,纯日期模式,点选某天即回填并关闭面板。加 `clearable` 可一键清空。 **演示说明:** 选中值始终是 format 描述的字符串,清空时为 `''`。 ```vue 选中:{{ date || '未选择' }} ``` ### 日期时间模式 `format` 中出现 `H` / `mm` / `ss` 即启用时间选择。此时点选日期不会关闭面板,要再点「确定」。 **演示说明:** 时间面板是「时 : 分 : 秒」三列步进器;输出保留几位由 format 决定。 ```vue 分:{{ toMinute || '未选择' }} / 秒:{{ toSecond || '未选择' }} ``` ### 自定义格式 模板支持 `YYYY` / `MM` / `DD` / `HH` / `mm` / `ss` 六种占位符,分隔符随意,中文前缀也照常渲染。 **演示说明:** 这两条都不含时间占位符,所以仍是纯日期模式。 ```vue 斜杠:{{ slash || '未选择' }} / 中文:{{ chinese || '未选择' }} ``` ### 尺寸与圆角 `size` 三档;`radius` 改触发区圆角,`panelRadius` 改弹出面板圆角,`width` 改触发区宽度。 **演示说明:** 最后一条用 `radius="999px"` 把触发器做成胶囊形。 ```vue ``` ### 可选范围与禁用 `min` / `max` 限定可选区间,越界日期在面板中置灰;`disabled` 整体禁用。 **演示说明:** `min` / `max` 接受 `Date` 或可解析字符串。 ```vue 范围内选中:{{ inRange || '未选择' }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `Date \| string \| null` | `undefined` | 当前值(v-model):`Date` 或可解析字符串,输出始终为 `format` 格式字符串 | | `placeholder` | `string` | `undefined` | 占位文本 | | `format` | `string` | `'YYYY-MM-DD'` | 输出格式模板,同时决定是否启用时间选择 | | `disabled` | `boolean` | `false` | 是否禁用 | | `clearable` | `boolean` | `false` | 有值时显示清除按钮 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `min` | `Date \| string` | `undefined` | 可选最小日期 | | `max` | `Date \| string` | `undefined` | 可选最大日期 | | `appendToBody` | `boolean` | `true` | 挂载到 body | | `popupPlacement` | `'top' \| 'bottom'` | `'bottom'` | 弹出方向(空间不足时自动翻转) | | `width` | `string` | `undefined` | 触发区宽度 | | `radius` | `string` | `undefined` | 触发区圆角(任意 CSS 长度);默认 4px | | `panelRadius` | `string` | `undefined` | 弹出面板圆角(任意 CSS 长度);默认 4px | | `name` | `string` | `undefined` | 原生 input name(表单提交) | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: string` | 选中值变化(`format` 格式字符串;清空时为 `''`) | | `change` | `value: string` | 选中值变化 | | `open` | 无 | 面板打开 | | `close` | 无 | 面板关闭 | ### Slots 无。 ### format 与日期时间模式 是否启用时间选择由 `format` 中的占位符决定,判定正则是 `/H|mm|ss/`,**区分大小写**。 | format 示例 | 模式 | 交互差异 | | --- | --- | --- | | `YYYY-MM-DD` | 纯日期 | 点选某天即回填并关闭;底部只有「今天」 | | `YYYY-MM-DD HH:mm` | 日期时间 | 点选日期不关闭,底部出现「此刻」与「确定」,需点「确定」收尾 | | `YYYY-MM-DD HH:mm:ss` | 日期时间 | 同上一行,输出保留到秒 | 时间部分由内部的 `TimeSpinner` 渲染,固定是「时 : 分 : 秒」三列步进器(列内可上下微调或直接键入)。最终字符串里保留哪些单位,取决于 `format` 里写了哪些占位符。 ## 使用建议 ::: tip 输出统一按字符串处理 `modelValue` 可以传 `Date`,但组件回写的永远是字符串。提交前如果要和 `Date` 运算,请自行用同一套格式解析,不要假设拿到的是对象。 ::: ::: tip 面板里的年月标签可以点 顶部年月标签点击后进入年 / 月快捷面板,跨年跳转比逐月翻页快得多。 ::: ::: warning 小写 mm 是「分」,不是「月」 `MM` 才是月份,`mm` 是分钟。写 `YYYY-mm-DD` 会意外触发时间模式。反过来,如果只想要日期,format 里就不要出现 `H`、`mm`、`ss`,例如 `YYYY年MM月DD日` 是安全的。 ::: ::: warning 打开面板会锁定页面滚动 面板打开期间会调用 `lockBodyScroll()` 锁住 body 滚动,关闭后恢复。若在面板未关闭时卸载组件,页面滚动会被锁住,注意生命周期与关闭时机。 ::: ## 相关组件 - [DateRangePicker 日期范围](/components/date-range-picker):需要一次选起止两端的场景。 - [Input 输入框](/components/input):只需要自由文本输入、不做日期约束时使用。 - [Select 选择器](/components/select):取值来自固定几项、而非任意日期时使用。 --- # DateRangePicker 日期范围 一次选出「开始 ~ 结束」两端。面板并排展示两个月,选中开始日期后悬停即可预览整段区间。与 `MDatePicker` 共用同一套日期工具与 format 语义:`format` 里出现 `H` / `mm` / `ss` 时启用时间选择,输出数组两端都是 `format` 格式字符串(未选的一端为 `null`)。 ## 代码演示 ### 基础用法 `v-model` 绑定 `[开始, 结束]`。未选满两端时,另一端是 `null`;整体清空时绑定值为 `null`。 **演示说明:** `clearable` 在两端都有值后显示清除按钮。 ```vue 选中:{{ range ? `${range[0] ?? '未选'} ~ ${range[1] ?? '未选'}` : '未选择' }} ``` ### 日期时间模式 `format` 含时间占位符时进入 datetime 模式,面板底部出现时钟按钮,可分别调整起止时间的时分秒。 **演示说明:** 点选日期不会立即关闭面板,需要点「确定」。 ```vue 选中:{{ fmt(range) }} ``` ### 自定义占位符与格式 `placeholder` 传单个字符串时两格共用,传二元组时分别用于开始 / 结束。`format` 决定回写的字符串格式。 **演示说明:** 最后两条分别展示两种占位符写法与两种输出格式。 ```vue 单个字符串:{{ fmt(single) }} 数组分别指定:{{ fmt(pair) }} ``` ### 尺寸与圆角 `size` 三档;`radius` 改触发区圆角,`panelRadius` 改弹出面板圆角,`width` 改触发区宽度。 ```vue ``` ### 可选范围与禁用 `min` / `max` 限定两端可选区间;`disabled` 整体禁用。 **演示说明:** `min` / `max` 接受 `Date` 或可解析字符串。 ```vue 范围内选中:{{ fmt(inRange) }} 禁用态值:{{ fmt(locked) }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `DateRangeValue` | `undefined` | 当前值(v-model):`[开始, 结束]` | | `placeholder` | `string \| [string, string]` | `'开始日期 ~ 结束日期'` | 占位符:单个字符串同时用于两格,数组则分别用于开始 / 结束 | | `format` | `string` | `'YYYY-MM-DD'` | 输出格式模板,同时决定是否启用时间选择 | | `disabled` | `boolean` | `false` | 是否禁用 | | `clearable` | `boolean` | `false` | 有值时显示清除按钮 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `min` | `Date \| string` | `undefined` | 可选最小日期 | | `max` | `Date \| string` | `undefined` | 可选最大日期 | | `appendToBody` | `boolean` | `true` | 挂载到 body | | `popupPlacement` | `'top' \| 'bottom'` | `'bottom'` | 弹出方向(空间不足时自动翻转) | | `width` | `string` | `undefined` | 触发区宽度 | | `radius` | `string` | `undefined` | 触发区圆角(任意 CSS 长度);默认 4px | | `panelRadius` | `string` | `undefined` | 弹出面板圆角(任意 CSS 长度);默认 4px | | `name` | `string` | `undefined` | 原生 input name(表单提交) | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: [string \| null, string \| null] \| null` | 范围变化(各端为 `format` 字符串,未选为 `null`),清空时为 `null` | | `change` | `value: [string \| null, string \| null] \| null` | 范围变化 | | `open` | 无 | 面板打开 | | `close` | 无 | 面板关闭 | ### Slots 无。 ### 类型定义 ```ts type DateRangeValue = | [Date | string | null, Date | string | null] | null ``` ### 范围选择规则 - 当前无选择、或已选满两端时再次点击,会以该点为新的开始日期; - 已有开始日期、再点击更早的日期,会把开始日期替换为新的这一天; - 已有开始日期、再点击更晚的日期,补全结束日期,完成本次选择。 ## 使用建议 ::: tip 输出是数组,不是拼接字符串 回写值形如 `['2026-09-01', '2026-09-30']`,两端都可能为 `null`;「整体为 `null`」表示两侧都被清除。处理时先判 `null` 再取下标,不要把 `[null, null]` 当成有效范围。 ::: ::: tip 与 DatePicker 保持同一套 format 两个组件的 format 语义完全一致,同一页面内建议统一,避免日期与区间出现不同分隔符或不同时间精度。 ::: ::: warning 小写 mm 仍是「分」 和 `MDatePicker` 一样,`MM`(月)与 `mm`(分)区分大小写,`format` 里出现 `mm` 就会进入时间模式。只想要日期就用 `YYYY/MM/DD` 这类不含 `H`、`mm`、`ss` 的模板。 ::: ::: warning 没有内置的「最近 7 天」快捷预设 面板只提供年 / 月快捷跳转,以及底部的「清除」「确定」。要「今天 / 近 7 天 / 本月」这类相对区间,需要业务侧算好两端日期后写进 `v-model`。 ::: ## 相关组件 - [DatePicker 日期选择](/components/date-picker):只需要选单个日期时使用。 - [Input 输入框](/components/input):只需要自由文本输入、不做日期约束时使用。 - [Select 选择器](/components/select):取值来自固定几项时使用。 --- # ColorPicker 颜色选择 从一个按钮触发器唤出取色面板:饱和 / 亮度色板、色相条,以及(开启后)透明度滑杆。`v-model` 始终是 HEX 字符串,面板内可在 HEX 与 RGB 两种输入方式之间切换,两者都写回同一个值。 ## 代码演示 ### 基础用法 `v-model` 绑定 HEX 字符串,空字符串表示「未选择」。加 `clearable` 后触发器上出现清除按钮。 **演示说明:** 触发器左侧是当前色的色板,右侧是色值文本。 ```vue 主色 {{ primary }} / 强调色 {{ accent || '未选择' }} ``` ### 尺寸 `small` / `default` / `large` 三档,同时影响触发区高度与文字。 ```vue ``` ### 透明度 `showAlpha` 开启后输出 9 位 `#RRGGBBAA`,关闭时输出 7 位 `#RRGGBB`。 **演示说明:** 面板里会多出一条透明度滑杆。 ```vue 不含透明度 #RRGGBB:{{ opaque }} 含透明度 #RRGGBBAA:{{ alpha }} ``` ### 预设色 `presets` 传入常用色,面板底部会渲染成一排色块,点击即选中。 **演示说明:** 非法项会被过滤,重复项会去重。 ```vue 品牌色:{{ brand }},自定义色:{{ custom || '未选择' }} ``` ### 禁用 `disabled` 时触发器不可点击,面板不会展开。 ```vue ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string` | `''` | HEX:`#RGB` / `#RGBA` / `#RRGGBB` / `#RRGGBBAA`;空字符串表示未选择 | | `disabled` | `boolean` | `false` | 是否禁用 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸 | | `showAlpha` | `boolean` | `false` | 开启后输出 `#RRGGBBAA`,否则输出 `#RRGGBB` | | `clearable` | `boolean` | `false` | 是否显示清除按钮 | | `presets` | `string[]` | `[]` | 预设色列表(会做归一化与去重) | | `ariaLabel` | `string` | `'选择颜色'` | 无障碍标签 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: string` | 颜色值变化 | | `change` | `value: string` | 颜色值变化 | | `clear` | 无 | 点击清除且此前确有值时触发 | ### Slots 无。 ## 使用建议 ::: tip 绑定值始终是 HEX 面板右上角的按钮可以在 HEX 输入与 RGB 三通道输入之间切换,但那只是输入方式;`v-model` 读到的永远是 `#RRGGBB` 或 `#RRGGBBAA` 字符串,业务侧不需要判断当前是哪种输入模式。 ::: ::: tip 与设计令牌配合 把取色结果回填到 CSS 变量(如 `--m-primary`)时,建议统一开启或关闭 `showAlpha`,避免同一套令牌里混入 7 位与 9 位两种长度。 ::: ::: warning 非法输入会在面板内提示 在 HEX 输入框里输入无法解析的内容时,面板会显示「请输入有效的 HEX 颜色」并标记 `aria-invalid`,此时不会写回 `modelValue`。 ::: ::: warning 组件没有圆角 prop `MColorPicker` 未提供 `radius` / `panelRadius` 一类的圆角参数,触发区与弹出面板使用组件内置圆角。需要改圆角时只能从样式层覆盖,不要传入不存在的 prop。 ::: ## 相关组件 - [Input 输入框](/components/input):只需要输入十六进制色值、不需要取色面板时使用。 - [Slider 滑块](/components/slider):透明度、色相等连续量的滑杆交互参考。 --- # Avatar 头像 用于表示用户或实体的图像占位。核心在于**回退链**:只要图片拿不到,组件会自己退到可读的内容上,业务侧不必再写 `onerror` 逻辑。 ## 代码演示 ### 基础用法 图片、`alt` 首字符、默认插槽、人形兜底四种形态。没有 `src` 也没有 `alt` 时落到内置人形占位。 **演示说明:** 第一个头像用内联 SVG 作为图片源,避免演示依赖外部网络。 ```vue ``` ### 尺寸 `size` 传 `'small'` / `'default'` / `'large'` 走预设尺寸(32 / 40 / 56 px),传数字则直接按像素生效。 **演示说明:** 数字尺寸下字号按 `size * 0.42` 自动换算。 ```vue ``` ### 形状 `circle` 正圆、`rounded` 圆角方形、`square` 直角方形。 ```vue ``` ### 语义色 `color` 只在文本 / 插槽 / 人形占位时起作用,图片模式下会被忽略。 ```vue ``` ### 加载失败与重新加载 第一排的头像指向一个不存在的路径,图片 `error` 后自动回退;点按钮换 `src`,组件会重置失败标记重新尝试加载。 **演示说明:** 第二个头像演示了「有 `#default` 插槽时,插槽内容优先于 `alt` 首字符」。 ```vue 自定义 切换 src 重新加载 ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `src` | `string` | `undefined` | 图片地址;加载失败自动回退到默认插槽 / 首字符 / 人形占位 | | `alt` | `string` | `undefined` | 图片 `alt`;无图片时作为首字符占位的来源 | | `size` | `number \| 'small' \| 'default' \| 'large'` | `40` | 数字按 px 处理,预设值对应 32 / 40 / 56 px | | `shape` | `'circle' \| 'square' \| 'rounded'` | `'circle'` | 形状 | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 文本 / 图标占位的背景色,图片模式忽略 | ### Events 无。 ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 自定义占位内容,优先级高于 `alt` 首字符与人形兜底 | ## 使用建议 ::: tip 回退优先级 渲染顺序为「图片 → 默认插槽 → `alt` 首字符(取第一个字符并大写)→ 内置人形 SVG」。只想用一个名字做占位时,写 `` 即可得到「张」字头像。 ::: ::: tip `src` 变化会重置失败标记 组件内部用 `failed` 记录加载失败。`src` 一旦变化就会把 `failed` 置回 `false` 并重新尝试加载,因此切换用户头像时不需要换 `key` 或重建组件。 ::: ::: warning 数字尺寸会同时改写字号 传数字 `size` 时,组件会把 `width` / `height` 设为该值,并把 `fontSize` 设为 `size * 0.42`。如果传入的默认插槽内容自带 `font-size`,它会覆盖这一换算。 ::: ## 相关组件 - [Icon 图标](/components/icon):默认插槽里放 `MIcon` 可以做图标头像。 - [Nav 导航](/components/nav):导航项常配头像或图标作视觉标识。 --- # Progress 进度条 表达「某件事进行到什么程度」。线性用于页面顶部或卡片内的横向进度,环形用于按钮旁或数据面板里的紧凑指示。 ## 代码演示 ### 基础用法 `determinate` 配合 `value` 展示确定进度,点按钮可以把进度推进 20%。线性与环形可以绑同一个值。 ```vue 推进 20% ``` ### 三种变体 `determinate` 需要 `value`;`indeterminate` 表示时长未知,走循环动画;`buffer` 用于流媒体式的「已加载 / 已缓冲」双进度。 **演示说明:** 最后一个环形传了 `variant="buffer"`,组件会把它回退成 `indeterminate`。 ```vue ``` ### 环形尺寸与粗细 `size` 控制环形直径、`thickness` 控制描边宽度,两者只对 `circular` 生效。 ```vue ``` ### 语义色 六种语义色与 `--m-*` 主题令牌对应,线性与环形同一套取值。 ```vue ``` ### 缓冲进度 `value` 是主进度、`valueBuffer` 是缓冲进度,两者都收敛在 0–100。 ```vue 加载一批 已加载 {{ value }}% · 已缓冲 {{ valueBuffer }}% ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `type` | `'linear' \| 'circular'` | `'linear'` | 形态:线性进度条 / 环形进度 | | `variant` | `'determinate' \| 'indeterminate' \| 'buffer'` | `'indeterminate'` | 变体;`buffer` 仅线性支持,环形下自动回退为 `indeterminate` | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 语义色 | | `value` | `number` | `0` | 进度值 0–100,`determinate` / `buffer` 生效,超出区间自动收敛 | | `valueBuffer` | `number` | `0` | 缓冲进度 0–100,仅 `buffer` 变体生效 | | `size` | `number` | `40` | 环形直径 px,仅 `circular` 生效 | | `thickness` | `number` | `3.6` | 环形描边厚度 px,仅 `circular` 生效 | ### Events 无。 ### Slots 无。 ## 使用建议 ::: tip 线性进度会撑满父容器宽度 线性形态的根元素是 `display: block; width: 100%` 的块,放在 flex 行里会自然占满剩余空间,不需要手动设宽。需要窄条时给它加一个限宽容器。 ::: ::: warning 默认变体是不确定态 `variant` 的默认值是 `indeterminate`,不是 `determinate`。如果只想画一条静态进度条,必须显式写 `variant="determinate"` 并传 `value`,否则看到的是循环动画。 ::: ::: warning `buffer` 在环形下无效 `type="circular"` 与 `variant="buffer"` 同时出现时,组件内部会把变体改写成 `indeterminate`(与 MUI 的 `CircularProgress` 一致,环形没有缓冲语义)。环形要用缓冲效果只能换回线性。 ::: ::: tip 无障碍取值 `determinate` / `buffer` 会写入 `aria-valuenow`;`aria-valuemin` / `aria-valuemax` 只在 `linear` 下上报。 ::: ## 相关组件 - [Button 按钮](/components/button):提交类按钮配合进度条展示后台任务状态。 - [Toast 轻提示](/components/toast):短任务用轻提示即可,不需要进度条。 --- # Tooltip 文字提示 给一个元素补充一句解释,不占布局。气泡挂在 `body` 上用 `position: fixed` 定位,因此不会被父级 `overflow` 裁剪;主轴空间不足时会自动翻到对侧。 ## 代码演示 ### 基础用法 `content` 传纯文本,或用 `#content` 插槽写结构化内容。默认 hover 触发、延迟 300ms。 ```vue 悬停查看 自定义内容 支持多行与任意结构 插槽内容 ``` ### 方位 `placement` 支持四个主轴方向与四组带对齐的方位,共 12 种取值。靠近视口边缘时组件会自动翻转成对侧。 **演示说明:** 逐个悬停可以对照入场位移与对齐方式。 ```vue {{ placement }} ``` ### 触发方式 `hover` 适合补充说明,`click` 适合需要停留阅读的内容,`focus` 只靠键盘与焦点也能触达。 **演示说明:** click 模式下点击气泡外任意处会关闭。 ```vue hover click focus ``` ### 自定义内容 `#content` 插槽优先于 `content` 属性。 ```vue 自定义内容 插槽里可以放标题、列表或任意节点。 #content 插槽 content 属性 ``` ### 延迟与禁用 `openDelay` / `closeDelay` 只对 `hover` 生效;`disabled` 置为 `true` 时正在显示的气泡会立即关闭。 ```vue openDelay 0 closeDelay 600 disabled 禁用提示 ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `content` | `string` | `undefined` | 提示内容;也可用 `#content` 插槽 | | `placement` | `TooltipPlacement` | `'top'` | 弹出方向,空间不足时自动翻转并按视口夹紧 | | `trigger` | `'hover' \| 'click' \| 'focus'` | `'hover'` | 触发方式 | | `disabled` | `boolean` | `false` | 是否禁用;置为 `true` 时立即关闭 | | `openDelay` | `number` | `300` | 显示延迟(ms),仅 `hover` 生效 | | `closeDelay` | `number` | `0` | 关闭延迟(ms),仅 `hover` 生效 | | `modelValue` | `boolean` | `undefined` | 受控显示状态(`v-model`) | | `appendToBody` | `boolean` | `true` | 挂载到 `body`,避免被父级 `overflow` 裁剪 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 受控显示状态变化 | | `open` | 无 | 气泡显示 | | `close` | 无 | 气泡隐藏 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 触发区内容 | | `content` | 无 | 自定义提示内容;无内容时回退到 `content` 属性 | ### 类型定义 ```ts export type TooltipSide = 'top' | 'bottom' | 'left' | 'right'; export type TooltipPlacement = | TooltipSide | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end'; export type TooltipTrigger = 'hover' | 'click' | 'focus'; ``` ## 使用建议 ::: tip 鼠标可以从触发区移到气泡上 气泡本身也监听 `hover`:鼠标进入气泡会取消关闭计时。因此可以让提示内容足够长、允许用户把鼠标移进去选中文字。 ::: ::: tip 位置是算出来的,不是纯 CSS 组件读取触发区与气泡的实际尺寸,先按 `placement` 算基准位置,主轴空间不足时翻到对侧,最后把坐标夹紧在视口内。滚动与窗口尺寸变化时会重新计算。 ::: ::: warning `openDelay` / `closeDelay` 只对 hover 有效 `click` 与 `focus` 触发的显示、隐藏是即时的,设置延迟不会生效。 ::: ::: warning 气泡没有箭头 `MTooltip` 的气泡是一条纯色圆角矩形,内部没有指向触发区的三角箭头,方向完全由位置表达。 ::: ## 相关组件 - [Dropdown 下拉菜单](/components/dropdown):需要承载可交互内容的浮层。 - [Icon 图标](/components/icon):给纯图标元素配 Tooltip 补足语义。 --- # Alert 警告提示 把一条需要被看见、但不需要用户操作的消息固定在页面流里。与 `MToast` 的区别是:Alert 占据布局位置、常驻展示;Toast 悬浮在角落、自动消失。 ## 代码演示 ### 基础用法 `severity` 决定语义与配色,`title` 是加粗的标题行,正文放在默认插槽。 **演示说明:** 点关闭按钮会触发 `close` 事件并弹出提示。 ```vue 数据已同步到服务器。 ``` ### 四种严重级别 `success` / `info` / `warning` / `error` 各自对应一套图标与配色。 ```vue 配置已发布到生产环境。 本次变更为灰度发布,覆盖 10% 流量。 磁盘使用率已达 85%,请及时清理。 数据库连接超时,请检查网络与白名单。 ``` ### 三种外观变体 `standard` 浅底、`filled` 实底、`outlined` 描边。变体只改外观,语义色仍由 `severity` 决定。 ```vue variant 只改外观,语义色仍然由 severity 决定。 ``` ### 图标控制 `showIcon` 控制内置语义图标;一旦传入 `#icon` 插槽,内置图标会被替换掉。 ```vue `showIcon` 默认是 `true`,显示内置的语义图标。 `showIcon` 设为 `false` 后左侧不再留出图标位置。 传了 `#icon` 插槽后内置图标自动隐藏,此时 `showIcon` 不再起作用。 ``` ### 标题 标题可以来自 `title` 属性,也可以用 `#title` 插槽写富文本;两者同时存在时插槽优先。 ```vue 正文放在默认插槽里。 通过 #title 插槽 (支持富文本) `#title` 插槽优先于 `title` 属性。 只有正文、没有标题时不会渲染标题行。 ``` ### 关闭与重新挂载 关闭是组件内部状态。点关闭后组件自己动画离场并保持消失,需要重新显示必须由外部换 `key` 或重新挂载。 ```vue 点击右上角关闭后,组件内部会置 `closed = true` 并播放离场动画,但不会自己复位。 换 key 重新挂载 ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `severity` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | 语义类型,决定图标与配色 | | `variant` | `'standard' \| 'filled' \| 'outlined'` | `'standard'` | 外观变体:浅底 / 实底 / 描边 | | `closable` | `boolean` | `false` | 显示右上角关闭按钮,点击后进入离场动画 | | `showIcon` | `boolean` | `true` | 是否显示内置语义图标;传入 `#icon` 插槽时内置图标自动隐藏 | | `title` | `string` | `undefined` | 标题文本;也可用 `#title` 插槽 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `close` | 无 | 点击关闭按钮时触发,同时组件内部置 `closed = true` 开始离场动画 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 提示正文 | | `icon` | 无 | 自定义图标;提供后自动隐藏内置语义图标 | | `title` | 无 | 自定义标题;无内容时回退到 `title` 属性 | ## 使用建议 ::: tip 只有正文时不会渲染标题行 `title` 属性为空且没有 `#title` 插槽内容时,标题行整行不渲染。同理,默认插槽为空时正文行也不渲染,所以「只有标题」的 Alert 是合法写法。 ::: ::: warning 关闭后组件不会自行复位 关闭动作只改组件内部的 `closed` 状态:动画播完后内容从 DOM 移除,`closable` 的图标也跟着消失。想再次展示必须由消费方干预,通常是给组件换一个 `key` 或用 `v-if` 重新挂载。 ```vue ... ``` 注意 `v-if` 与 `@close` 绑同一个开关时,组件会在同一帧被卸载,离场动画会被打断;`close` 后先置内部状态、再由用户操作重新显示的写法(例如换 `key`)才能看到完整的离场动画。 ::: ::: warning `showIcon` 对 `#icon` 插槽无效 内置图标的渲染条件是 `showIcon && !$slots.icon`。只要传了 `#icon`,内置图标就会隐藏,此时把 `showIcon` 设成 `false` 也不会影响你自定义的那个图标。 ::: ## 相关组件 - [Toast 轻提示](/components/toast):非阻塞的临时消息,自动消失,不占布局。 - [Dialog 对话框](/components/dialog):需要用户确认后再继续的场景。 --- # Tabs 标签页 标签页由四个组件分工协作:`MTabs` / `MVTabs` 只负责画标签栏,`MTabPanes` / `MTabPane` 只负责画内容区。 这套拆分是刻意的:**标签栏不渲染内容,内容区不渲染标签**。两者之间没有父子关系,把同一份数据分成 `items`(给标签栏)和 `MTabPane`(给内容区)两份描述,再用一个共享的 `v-model` 把激活名对齐。好处是标签栏可以独立放在任何位置——比如贴近页面顶部的 sticky 区域——而内容区留在卡片里。 ## 代码演示 ### 基础用法 `MTabs` 用 `items` 描述标签,`MTabPanes` 里放对应的 `MTabPane`,两者绑定同一个 `active`。 **演示说明:** 注意这是两个平级组件,`MTabs` 并没有包住 `MTabPanes`。 ```vue 标签栏与内容区绑定同一个 v-model。 只有激活的面板会挂载到 DOM。 切换方向决定偏移动画的方向。 ``` ### 卡片与分段形态 `type="card"` 去掉下沿指示条,改用淡彩背景标记激活项,相邻标签的圆角相接,视觉上接近分段控件。 **演示说明:** `type` 只有 `line` 与 `card` 两种取值,没有单独的 segmented 形态。 ```vue card 形态:去掉下沿指示条,用淡彩背景标记激活项。 相邻标签圆角相接,视觉上接近分段控件。 切换仍然由同一个 v-model 驱动。 ``` ### 纵向标签栏 纵向用独立的 `MVTabs`,其余用法与 `MTabs` 一致。 **演示说明:** `align` 取值变成 `top` / `middle` / `bottom`。 ```vue 纵向标签栏立在左侧,指示条沿纵向滑动。 标签栏与内容区仍是两个平级组件。 方向键换成了 ↑ / ↓。 ``` ### 可关闭与可新增 `addable` 在标签栏尾部加一个新增按钮。`close` 与 `add` 都只抛事件,**不会**自动改动 `items`,需要消费方自己更新数组。 **演示说明:** 关掉当前激活的标签时,`MTabPanes` 会自动把激活名换成相邻项并回写。 ```vue 「{{ item.label }}」的内容 ``` ### 禁用项 `MTabPane` / `items` 上的 `disabled` 只锁住单项,标签栏顶层的 `disabled` 锁住全部。 ```vue 中间那一项被 item 上的 `disabled` 锁住,点击无响应。 这一项无法被激活。 其余项正常切换。 ``` ### 尺寸、对齐与语义色 `size` 影响标签高度与字号(32 / 40 / 48 px),`align` 控制标签在栏内的对齐,`color` 决定激活文字与指示条的颜色。 ```vue ``` ## API ### MTabs Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number` | `undefined` | 当前激活的标签 `name`(`v-model`) | | `items` | `TabsItem[]` | `[]` | 标签项数据;`MTabs` 是纯标签栏,标签只能通过 `items` 传入 | | `type` | `'line' \| 'card'` | `'line'` | 标签形态:下划线 / 卡片 | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `undefined` | 主题色,决定激活文字、指示条、hover 底色与卡片激活背景;未设置时按 `primary` 渲染 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸,影响标签高度与字号 | | `align` | `'left' \| 'center' \| 'right'` | `'left'` | 标签在栏内的对齐方式 | | `disabled` | `boolean` | `false` | 是否禁用全部标签 | | `closable` | `boolean` | `false` | 所有标签是否显示关闭按钮,与 item 自身的 `closable` 取「或」 | | `addable` | `boolean` | `false` | 是否在标签栏尾部显示「新增」按钮,点击触发 `add` | ### MVTabs Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number` | `undefined` | 当前激活的标签 `name`(`v-model`) | | `items` | `TabsItem[]` | `[]` | 标签项数据 | | `type` | `'line' \| 'card'` | `'line'` | 标签形态:右沿指示条 / 卡片 | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `undefined` | 主题色,未设置时按 `primary` 渲染 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸,影响标签高度与字号 | | `align` | `'top' \| 'middle' \| 'bottom'` | `'top'` | 标签在栏内的对齐方式 | | `disabled` | `boolean` | `false` | 是否禁用全部标签 | | `closable` | `boolean` | `false` | 所有标签是否显示关闭按钮,与 item 自身的 `closable` 取「或」 | | `addable` | `boolean` | `false` | 是否在标签栏底部显示「新增」按钮,点击触发 `add` | ### MTabPane Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `name` | `string \| number` | `undefined` | 唯一标识;不传时由 `MTabPanes` 按注册顺序分配 `pane-{index}`,独立使用时为 `0` | | `label` | `string` | `undefined` | 标签文本(纯文本) | | `icon` | `Component` | `undefined` | 标签图标组件 | | `disabled` | `boolean` | `false` | 是否禁用该标签 | | `closable` | `boolean` | `undefined` | 是否显示关闭按钮,未显式设置时继承标签栏的 `closable` | ### MTabPanes Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number` | `undefined` | 当前激活的 `MTabPane` 的 `name`;与标签栏共享同一个变量即可联动 | ### Events | 组件 | 事件 | 参数 | 说明 | | --- | --- | --- | --- | | `MTabs` / `MVTabs` | `update:modelValue` | `value: string \| number` | 激活项变化,包含失效回退时的回写 | | `MTabs` / `MVTabs` | `change` | `name: string \| number` | 用户切换激活标签 | | `MTabs` / `MVTabs` | `close` | `name: string \| number` | 点击标签关闭按钮;不自动移除数据 | | `MTabs` / `MVTabs` | `add` | 无 | 点击「新增」按钮 | | `MTabPanes` | `update:modelValue` | `value: string \| number` | 激活项变化,包含关闭激活项后自动激活相邻项的回写 | | `MTabPane` | 无 | | | ### Slots | 组件 | 插槽 | 参数 | 说明 | | --- | --- | --- | --- | | `MTabs` / `MVTabs` | 无 | | | | `MTabPanes` | `default` | 无 | 内部的 `MTabPane` 列表 | | `MTabPane` | `default` | 无 | 面板内容 | ### 类型定义 ```ts export type TabsType = 'line' | 'card'; export type TabsSize = 'small' | 'default' | 'large'; export type TabsAlign = 'left' | 'center' | 'right'; export type VTabsAlign = 'top' | 'middle' | 'bottom'; export type TabsColor = | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'; export interface TabsItem { name: string | number; label: string; icon?: Component; disabled?: boolean; closable?: boolean; } ``` ### 组合关系 ```vue 概览内容 用量内容 ``` | 关系 | 说明 | | --- | --- | | `MTabs` / `MVTabs` ↔ `MTabPanes` | **平级**,没有 `provide` / `inject`,只靠绑定同一个 `v-model` 联动 | | `MTabPanes` ↔ `MTabPane` | **父子**,`MTabPanes` 通过 `provide` 下发注册表,`MTabPane` 在 `setup` 里 `inject` 并注册自己 | | `MTabs` / `MVTabs` ↔ `MTabPane` | 无任何直接关系,标签栏不认识面板组件 | ## 使用建议 ::: tip 为什么不能写 `MTabs` 包住 `MTabPane` `MTabs` 是纯标签栏组件,它的渲染完全由 `items` 决定,模板里没有默认插槽。写成「标签栏包住面板」不会报错,但里面的 `MTabPane` 会被直接丢弃。正确写法是两者平级、共享一个激活变量。 ::: ::: warning `close` / `add` 不会改动 `items` 组件只负责抛事件,不持有数据源。删除标签要自己在 `@close` 里过滤数组,新增标签要在 `@add` 里往数组里 push。`MTabPane` 在卸载时会通过 `onBeforeUnmount` 自动注销注册表,所以用 `v-for` 渲染面板时不需要手工清理。 ::: ::: warning `closable` 是「或」不是「覆盖」 渲染条件是 `item.closable || 标签栏的 closable`。所以标签栏上传 `closable` 会让**所有**标签都出现关闭按钮,item 上的 `closable: false` 也压不住它;反过来,标签栏不传 `closable` 时可以靠单个 item 的 `closable: true` 只给某些标签加关闭按钮。 ::: ::: tip 只有激活的面板在 DOM 里 `MTabPane` 用 `v-if="isActive"` 控制渲染,非激活面板完全不存在于 DOM 中,因此面板里放 `MDatePicker` 这类需要初始化尺寸的组件不会在隐藏状态下量错。代价是切换时状态会被重建——需要保留的状态提到父级,或用 `v-show` 自己控制内容。 离场面板会被临时绝对定位在原来的位置和宽度上,配合切换方向(新旧索引比较得出的 `forward` / `backward`)播偏移动画;`MTabPanes` 同时会做一次容器高度过渡,避免高矮面板切换时跳变。`prefers-reduced-motion: reduce` 下会跳过高度过渡。 ::: ::: tip 面板不传 `name` 会被自动编号 `MTabPane` 省略 `name` 时,`MTabPanes` 按注册(渲染)顺序分配 `pane-{index}`,从 `pane-0` 开始。所以在 `MTabPanes` 外部无法预知它的名字,只适合「内容区与标签栏无关、不需要点名激活面板」的场景。要和标签栏联动,请显式写 `name`,并保证它与 `items[].name` 一一对应。 ::: ::: tip 标签失效时会自动回退 `MTabs` / `MVTabs` 的 `modelValue` 指向一个不存在或被禁用的标签时,组件会回退到第一个可用项并回写 `update:modelValue`;`MTabPanes` 在激活项被关闭时也会把激活名换成相邻项。外部状态因此始终是有效的。 ::: ## 相关组件 - [Nav 导航](/components/nav):同样由 `items` 驱动,但用于主导航而非内容切换。 - [RadioButtonGroup 按钮单选](/components/radio-button-group):轻量的分段式视图切换。 --- # Nav 导航 用于 App 主导航:纵向当侧栏、横向当底栏。激活项由一个可滑动的实心块标记,键盘方向键可以在项之间移动。与 `MTabs` 一样是纯 `items` 数据驱动,不通过插槽收集子组件。 ## 代码演示 ### 基础用法 `items` 决定有哪些项,`v-model` 决定哪一项激活。横向排列适合做底部标签栏。 ```vue ``` ### 纵向侧栏 `direction="vertical"` 为默认值,配合 `align` 可以控制整列在容器内的对齐位置。 **演示说明:** 左边 `align="middle"`、右边 `size="small"` + `align="top"`。 ```vue ``` ### 徽标 `badge` 传数字显示在图标右上角,传 `true` 显示小红点;数字超过 `badgeMax` 时显示为 `{badgeMax}+`。 **演示说明:** 第二排把 `badgeMax` 调成 9,120 就变成了 9+。 ```vue ``` ### 尺寸与对齐 `size` 影响图标大小、字号与项宽高;`align` 在横向下取 `left` / `center` / `right`。 ```vue ``` ### 语义色 `color` 决定激活块的底色与 hover 底色。 ```vue ``` ### 禁用与受控激活 单项 `disabled` 只锁住那一项,顶层 `disabled` 锁住全部。激活值指向禁用项或不存在项时,组件会自动回退到第一个可用项并回写。 **演示说明:** 点按钮把 `active` 设成禁用项,状态会被组件改回来。 ```vue 把 active 设为禁用项 当前激活:{{ active }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `string \| number` | `undefined` | 当前激活项的 `name`(`v-model`) | | `items` | `NavItem[]` | `[]` | 导航项数据 | | `direction` | `'vertical' \| 'horizontal'` | `'vertical'` | 排列方向:纵向侧栏 / 横向底栏 | | `align` | `'top' \| 'middle' \| 'bottom' \| 'left' \| 'center' \| 'right'` | `'center'` | 沿主轴对齐;纵向取 `top` / `middle` / `bottom`,横向取 `left` / `center` / `right` | | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 主题色,决定激活实心块与 hover 底色 | | `size` | `'small' \| 'default' \| 'large'` | `'default'` | 尺寸,影响图标大小(20 / 24 / 28)、字号与项宽高 | | `disabled` | `boolean` | `false` | 是否禁用全部导航项 | | `badgeMax` | `number` | `99` | 数字徽标上限,超过时显示为 `{badgeMax}+` | | `ariaLabel` | `string` | `'导航'` | 绑定到 `nav` 元素的 `aria-label` | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: string \| number` | 激活项变化,包含内部回退时的回写 | | `change` | `name: string \| number` | 用户切换激活项(点击或键盘) | ### Slots 无。 ### 类型定义 ```ts export interface NavItem { name: string | number; label: string; icon?: Component; badge?: string | number | boolean; disabled?: boolean; } ``` ## 使用建议 ::: tip `items[].icon` 是组件而不是字符串 `icon` 的类型是 `Component`,组件会被渲染成 `` 并接收当前尺寸。直接用 `MIcon` 需要先把路径绑定进去: ```ts import { defineComponent, h, resolveComponent } from 'vue'; import { mdiHome } from '@mdi/js'; const MIcon = resolveComponent('MIcon'); const HomeIcon = defineComponent({ props: { size: { type: [Number, String], default: 24 } }, setup: (props) => () => h(MIcon, { path: mdiHome, size: props.size }), }); ``` ::: ::: warning 激活值失效会被自动改写 `modelValue` 不存在于 `items` 中,或指向一个被禁用的项时,`watchEffect` 会把激活值改成第一个可用项,并通过 `update:modelValue` 回写。所以外部持有的激活变量会被组件改掉,别把它当成只读的展示值。 ::: ::: warning 徽标的三种取值语义不同 `badge: true` 渲染为小红点(无文字);数字或字符串渲染为文字徽标;`badge` 为 `null` / `false` 时不渲染。数字超过 `badgeMax` 会显示成 `{badgeMax}+`。 ::: ::: tip 方向键随 `direction` 变化 纵向导航用 ↑ / ↓,横向导航用 ← / →,`Home` / `End` 跳到首尾,`Enter` / `Space` 确认。焦点在项之间走 roving tabindex,只有激活项是 `tabindex="0"`。 ::: ## 相关组件 - [Tabs 标签页](/components/tabs):同样是 `items` 驱动 + 共享 `v-model` 的标签栏,适合内容区切换。 - [Icon 图标](/components/icon):导航项的图标来源。 --- # Dialog 对话框 模态对话框用于必须让用户先给出结论才能继续的场景,例如二次确认、表单填写。MDialog 的展开不是简单的居中出现:白色盒子会以触发元素的中心为原点缩放展开,关闭时缩回同一起点,形成"从哪里点开、就收回到哪里"的 Morph 观感。 ## 代码演示 ### 基础用法 `#trigger` 插槽渲染触发内容并留在原位,点击时组件自动记录该元素的位置作为动画起点。 **演示说明:** `#trigger` 是推荐的打开方式,键盘可聚焦、位置自然。 ```vue 打开对话框 正文放在默认插槽,footer 插槽存在时才会渲染底部操作区。 确认状态:{{ confirmed ? '已确认' : '未确认' }} 取消 确定 ``` ### 尺寸 `width` 接受数字(按 px 处理)或任意 CSS 长度字符串;不传时为 420px,并且始终受 `max-width: 100%` 约束。 ```vue {{ option.label }} 数字按 px 处理,纯数字字符串会被补上 px,其他字符串原样作为 CSS 宽度。 ``` ### 圆角 `radius` 会作为 `--m-dialog-radius` 下发给白色盒子,缺省时回退到全局令牌 `var(--m-radius)`(6px)。 ```vue {{ option.label }} 不传 radius 时圆角回退到全局令牌 var(--m-radius)(6px);全屏模式下该值被忽略。 ``` ### Morph 起点 起点有两种来源:点击 `#trigger` 插槽时自动捕获,或用 `anchor` 显式指定任意元素。 **演示说明:** 两种写法共用同一套动画,`#trigger` 只是 `anchor` 的语法糖。 ```vue #trigger 触发 点击 #trigger 插槽时组件会记录该元素的包围盒,展开与收回都复用同一份起点。 显式传入 anchor 后,动画起点改由该元素中心决定,与点击位置无关。 anchor 指定元素 ``` ### 关闭行为 遮罩关闭与 Esc 关闭各有独立开关,用来区分"可以随手关掉"和"必须做出选择"两类对话框。 ```vue 遮罩 / Esc 可关闭 只能点关闭按钮 点击遮罩或按 Esc 都会关闭,并抛出 close 事件。 两个关闭开关都设为 false,遮罩点击与 Esc 都不再关闭,只能点右上角关闭按钮。 {{ log }} ``` ### 全屏 `fullscreen` 铺满视口并从底部向上滑入,此时 `width` 与 `radius` 都失效。 ```vue 全屏打开 fullscreen 下对话框铺满视口并从底部向上滑入,width 与 radius 都会被忽略。 内容超出时只在正文区域内滚动,footer 始终贴在底部。 完成 ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `boolean` | `undefined` | 显示开关,配合 `v-model` 使用 | | `title` | `string` | `undefined` | 标题文本;`title` 插槽有内容时优先用插槽 | | `width` | `string \| number` | `420` | 对话框宽度;数字与纯数字字符串按 px 处理,其他字符串原样作为 CSS 宽度;`fullscreen` 下忽略 | | `radius` | `string` | `undefined` | 卡片圆角,如 `'12px'`;缺省回退全局 `var(--m-radius)`;`fullscreen` 下忽略 | | `closeOnClickOverlay` | `boolean` | `true` | 点击遮罩是否关闭 | | `closeOnPressEscape` | `boolean` | `true` | 按 Esc 是否关闭 | | `appendToBody` | `boolean` | `true` | 是否 Teleport 到 body;关闭后对话框留在原位置,容易被父级 `overflow` 裁剪 | | `showClose` | `boolean` | `true` | 是否显示右上角关闭按钮 | | `fullscreen` | `boolean` | `false` | 全屏模式:铺满视口,从底部向上滑入,忽略 `width` 与 `radius` | | `anchor` | `HTMLElement \| null` | `null` | 动画起点元素:传入后从该元素中心展开;不传则用 `#trigger` 记录的点击元素,都没有时居中展开 | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 关闭请求:点击关闭按钮、遮罩或按 Esc 时抛 false | | `open` | 无 | `modelValue` 变为 `true` 时 | | `close` | 无 | `modelValue` 变为 `false` 时 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `trigger` | 无 | 触发内容,点击后自动记录该元素位置并打开对话框 | | `title` | 无 | 自定义标题;无内容时回退到 `title` prop | | `default` | 无 | 对话框正文 | | `footer` | 无 | 底部操作区;仅当该插槽存在时才渲染 footer | ### 动画与滚动 展开动画由 Web Animations API 直接驱动(`Transition :css="false"`),分两段顺序衔接: 1. **背景成型**:遮罩淡入,白色盒子 surface 从 `scale(0.25)` 放大到 `1` 并同步淡入,时长 `--m-dur-entering`(225ms)× 1.4。盒子的 `transform-origin` 指向起点元素中心。 2. **内容浮现**:正文层只做透明度淡入,起点是背景动画播到 80% 的时刻,时长 `--m-dur-entering` × 0.7,所以视觉上是"先有盒子,再出现文字"。 关闭动画是镜像的:正文先快速淡出(`--m-dur-leaving` × 0.45),随后盒子缩回同一起点并淡出(`--m-dur-leaving` × 1.0)。起点在打开时就被定格(`activeAnchor`),中途点击其他触发按钮不会让关闭动画跳位;只有当离开动画彻底结束才清空。 滚动锁定由组件直接写 `document.body.style.overflow = 'hidden'` 完成,关闭与卸载时恢复为空字符串。 ## 使用建议 ::: tip 优先用 `#trigger` 插槽 `#trigger` 会随内容留在文档流里,位置和鼠标点击点一致,展开动画最自然;只有触发元素不在对话框同级(例如在表格行内、由别的组件渲染)时才需要自己拿 `ref` 传 `anchor`。 ```vue 点我 ... ``` `anchor` 指向的元素在关闭动画结束前被卸载(`isConnected === false`)时会被忽略,动画退化为居中展开,不会报错。 ::: ::: warning 源码未实现焦点陷阱 对话框提供了 `role="dialog"` 与 `aria-modal="true"`,并在 `closeOnPressEscape` 为真时响应 Esc,但没有把焦点限制在对话框内部:打开后焦点不会自动移到对话框,Tab 仍可走到页面其他元素。对焦点管理有要求的场景,请自行在 `open` 事件里聚焦首个可聚焦元素,并在 `close` 后把焦点还给触发元素。 ::: ::: warning 滚动锁没有引用计数 对话框直接改写 `document.body.style.overflow`,没有参与 Drawer / Dropdown 使用的共享滚动锁,也不补偿隐藏滚动条带来的宽度变化。它与抽屉、下拉菜单同时打开时,谁先关闭谁的还原值会生效,可能出现页面仍锁着或提前解锁的情况,业务上尽量避免叠开。 ::: ## 相关组件 - [Drawer 抽屉](/components/drawer):同样需要遮罩与 Esc 关闭,但面板从屏幕边缘滑出,适合承载列表与表单。 - [Dropdown 下拉菜单](/components/dropdown):轻量的浮层,不需要遮罩,点击外部即收起。 - [Toast 轻提示](/components/toast):只做结果反馈,不打断操作,无需用户确认。 --- # Drawer 抽屉 抽屉是贴在屏幕边缘的模态面板,适合承载筛选条件、详情信息、分步表单这类"不离开当前页面"的内容。与对话框相比,它保留了一条与页面相接的边,滑入方向本身就暗示了内容与当前页面的从属关系。 ## 代码演示 ### 基础用法 默认从右侧滑出,宽度 320px;`#trigger` 插槽负责渲染触发内容。 ```vue 打开抽屉 正文放在默认插槽,内容超出高度时只在正文区域内滚动。 {{ applied ? '条件已应用' : '尚未应用条件' }} 取消 应用 ``` ### 展开方向 `placement` 取 `left` / `right` / `top` / `bottom`。左右方向决定宽度,上下方向决定高度。 ```vue {{ option.label }} 四个方向共用同一套结构,只是贴边的位置与滑入方向不同;左右方向决定宽度,上下方向决定高度。 ``` ### 尺寸 `size` 为数字或纯数字字符串时按 px 处理,其他字符串原样作为 CSS 长度。 **演示说明:** 宽度的百分比是相对视口计算的,因为抽屉使用 `fixed` 定位。 ```vue {{ option.label }} size 为数字或纯数字字符串时按 px 处理,其他字符串原样作为 CSS 长度。 ``` ### 标题与底部 `title` 插槽与 `footer` 插槽可以完全替换默认外观,`showClose` 用来去掉右上角的关闭按钮。 ```vue 自定义标题与底部 账户设置 showClose 设为 false 后右上角不再有关闭按钮,关闭只能依赖遮罩、Esc 或底部的自定义按钮。 稍后再说 保存 ``` ### 事件与关闭方式 `open` / `close` 用于同步外部状态,遮罩与 Esc 关闭各有独立开关。 ```vue 打开试试 这条抽屉关闭了 Esc 关闭,请用遮罩点击、右上角按钮或下方按钮。 关闭 {{ log }} ``` ## API ### Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `boolean` | `undefined` | 抽屉开关,配合 `v-model` 使用 | | `placement` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'right'` | 展开方向 | | `size` | `string \| number` | `320` | 左右方向为宽度、上下方向为高度;数字与纯数字字符串按 px 处理,其他字符串原样作为 CSS 长度 | | `title` | `string` | `undefined` | 标题文本;`title` 插槽有内容时优先用插槽 | | `showClose` | `boolean` | `true` | 显示关闭按钮(左右方向在右上角,上下方向在左侧) | | `closeOnClickOverlay` | `boolean` | `true` | 点击遮罩是否关闭 | | `closeOnPressEscape` | `boolean` | `true` | 按 Esc 是否关闭 | | `appendToBody` | `boolean` | `true` | 是否 Teleport 到 body | ### Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 关闭请求 | | `open` | 无 | 抽屉打开时 | | `close` | 无 | 抽屉关闭时 | ### Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `trigger` | 无 | 触发内容,点击后直接打开抽屉(不负责关闭) | | `title` | 无 | 自定义标题;无内容时回退到 `title` prop | | `default` | 无 | 抽屉正文 | | `footer` | 无 | 底部操作区;仅当该插槽存在时才渲染 footer | ### 类型 ```ts export type DrawerPlacement = 'left' | 'right' | 'top' | 'bottom' ``` ## 使用建议 ::: tip `#trigger` 只负责打开 插槽内的点击等价于 `emit('update:modelValue', true)`,想用同一个按钮关掉抽屉请改用受控写法: ```vue 切换抽屉 ... ``` ::: ::: warning 初始值为 true 会立即生效 抽屉内部对 `modelValue` 的监听带 `immediate: true`,所以初始渲染就把 `v-model` 绑成 `true` 时,会立刻抛出 `open` 事件并锁定页面滚动。需要"默认收起"就必须显式初始化为 `false`。 ::: ::: tip SSR / SSG 下请用客户端组件包裹 同一个 `immediate` 侦听在关闭分支里会调用滚动锁,而滚动锁会访问 `document`。因此在服务端渲染(Nuxt、VitePress、`vite-ssg` 等)时直接渲染 `MDrawer` 会抛 `document is not defined`。 解决办法是让抽屉只在客户端挂载 —— 本站的演示就是这么做的: ```vue ... ``` 组件本身的渲染产物不依赖 `document`,所以只要跳过服务端这一次挂载即可,功能不受影响。 ::: ::: warning 上下方向的尺寸含义不同 `size` 是"长度"而不是"宽度":`placement` 为 `top` / `bottom` 时它表示高度,传一个很大的值会把整块屏幕盖住,推荐不超过 `60vh` 一类的可视高度。滚动锁定通过共享的引用计数锁实现,与 Dropdown、Select 等弹层可以安全叠加。 ::: ## 相关组件 - [Dialog 对话框](/components/dialog):需要用户集中注意力做决策时用对话框,从触发元素 Morph 展开。 - [Dropdown 下拉菜单](/components/dropdown):轻量菜单,没有遮罩与滚动锁定。 - [Button 按钮](/components/button):抽屉底部操作区通常由按钮组成。 --- # Dropdown 下拉菜单 下拉菜单把一组次要操作收进一个触发区,避免主界面被按钮塞满。MDropdown 负责定位、开合与键盘导航,MDropdownItem 负责单项的语义(普通项、分隔线、禁用项);两者靠 `#menu` 插槽里的 provide / inject 上下文联动,所以子项点击后菜单会自己收起来。 ## 代码演示 ### 基础菜单 触发内容放在 `#trigger` 插槽,菜单项放在 `#menu` 插槽,默认点击展开。 ```vue 更多操作 编辑 创建副本 移动到… ``` ### 点击命令 每个菜单项用 `command` 携带一个业务标识,点击后父组件抛出 `command` 事件,这里用 `toast.info()` 把收到的值显示出来。 ```vue 执行操作 发布 定时发布 删除 归档(无权限) ``` ### 悬停触发 `trigger="hover"` 时鼠标移入即展开,移出 150ms 后收起;键盘聚焦触发区同样能打开,保证可访问性。 ```vue 悬停展开 概览 成员 设置 最近选择:{{ last }} ``` ### 选中态、分割线与禁用项 组件没有内置的 `selected` 属性,选中态由业务自己用 `command` 记录并用图标 / 颜色表达;分割线用 `divider`,禁用项点击既不抛事件也不收起菜单。 ```vue 切换视图 {{ view.label }} 刷新数据 导出(需要升级套餐) ``` ### 弹出位置 `placement` 支持上下各三个水平对齐位;某侧空间不足时会自动翻转到对侧,水平对齐位保持不变。 **演示说明:** 上方空间不够时面板会自动翻到下方,可以拖动页面把按钮推到视口边缘验证。 ```vue {{ placement }} {{ placement }} 第二项 第三项 ``` ### 自定义面板 `#menu` 插槽接受任意节点,不只限于 `MDropdownItem`;面板宽度、圆角与间距分别由 `width`、`panelRadius`、`offset` 控制。 ```vue 自定义面板 最近的操作 重命名 创建副本 移入回收站 最近选择:{{ last }} ``` ## API ### MDropdown Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `placement` | `'top-start' \| 'top-center' \| 'top-end' \| 'bottom-start' \| 'bottom-center' \| 'bottom-end'` | `'bottom-start'` | 弹出位置;空间不足时自动翻转到对侧 | | `trigger` | `'click' \| 'hover'` | `'click'` | 触发方式:点击或悬停 | | `disabled` | `boolean` | `false` | 是否禁用(禁用后触发区 `pointer-events: none`、透明度 0.38) | | `width` | `string` | `undefined` | 菜单宽度(任意 CSS 长度);不传时 `min-width` 取触发区宽度,内容可继续撑开 | | `offset` | `number` | `4` | 菜单与触发区之间的间距(px) | | `panelRadius` | `string` | `undefined` | 菜单面板圆角(任意 CSS 长度,如 `'8px'`);缺省回退 4px | | `appendToBody` | `boolean` | `true` | 是否 Teleport 到 body,避免被父级 `overflow` / `z-index` 裁剪 | ### MDropdown Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `command` | `value: unknown` | 任一 `MDropdownItem` 被点击时抛出其 `command` 值 | | `open` | 无 | 菜单展开 | | `close` | 无 | 菜单收起 | ### MDropdown Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `trigger` | 无 | 触发内容;未提供时回退到默认插槽 | | `default` | 无 | 作为 `trigger` 的兜底内容 | | `menu` | 无 | 菜单面板内容,放置 `MDropdownItem` | ### MDropdownItem Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `command` | `unknown` | `undefined` | 点击该项时随 `command` 事件抛出的值 | | `disabled` | `boolean` | `false` | 是否禁用;禁用时点击不抛事件、不收起菜单 | | `divider` | `boolean` | `false` | 渲染为分隔线(`role="separator"`),此时不渲染默认插槽内容 | ### MDropdownItem Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `command` | `value: unknown` | 点击该项时抛出自身 `command`;同时在 `MDropdown` 上下文里触发父级 `command` 并收起菜单 | ### MDropdownItem Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 菜单项内容;`divider` 为 `true` 时不渲染 | ### 类型 ```ts export type DropdownPlacement = | 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' export type DropdownTrigger = 'click' | 'hover' ``` ## 使用建议 ::: tip 父子联动是怎么发生的 `MDropdown` 通过 `provide` 向 `#menu` 插槽内的后代注入了两个回调,`MDropdownItem` 用 `inject` 取到它们。一次点击依次发生三件事: 1. `MDropdownItem` 抛出自己的 `command` 事件; 2. 通过上下文调用父级的 `onItemClick`,父级据此抛出 `command` 事件; 3. 通过上下文调用父级的 `close`,菜单自动收起。 所以业务只需要在 `MDropdown` 上监听一次 `command`,用 `value` 做分支即可,不需要在每个菜单项上重复绑定点击处理。 ::: ::: warning 菜单项必须放进 `#menu` 插槽 触发框内的内容会被当作触发区,而只有 `#menu` 插槽里的 `MDropdownItem` 才能注入到父级上下文。把菜单项写在默认插槽里,它会变成触发内容的一部分,既不会渲染成菜单,也不会自动收起。 ::: ::: warning 脱离父组件的子项不会收起菜单 单独使用 `MDropdownItem`(没有外层的 `MDropdown`)时,注入的上下文为空:点击只保留涟漪反馈并抛出自身的 `command` 事件,不会有任何"收起"行为。若在别处复用它,需要自己处理关闭逻辑。 另外,菜单展开时会锁定页面滚动、监听外部 `pointerdown` 自动收起;面板高度固定在 320px 以内并在内容层内部滚动,超长菜单不会把页面撑高。 ::: ## 相关组件 - [Select 选择器](/components/select):同样是弹层 + 列表,但 `MSelect` 负责维护选中值与输入框形态。 - [Dialog 对话框](/components/dialog):需要遮罩、必须打断流程时用对话框而非菜单。 - [Icon 图标](/components/icon):菜单项左侧的图标通常来自 `@mdi/js`。 - [Toast 轻提示](/components/toast):菜单里执行完命令后给一个轻量反馈。 --- # Toast 轻提示 轻提示只通报结果、不打断操作,通常出现在保存成功、请求失败、状态切换之后。它同时提供两种用法:编程式的 `toast` 服务适合在事件回调与请求封装里随手调用;声明式的 `MToast` 组件适合需要跟随 `v-model`、或正文要放复杂节点的场景,两者定位与样式一致。 ## 代码演示 ### 语义类型(编程式) `toast.show` / `success` / `error` / `warning` / `info` 对应五种语义,各自带语义色与图标。 ```vue 成功 失败 警告 信息 show ``` ### 自定义参数 `duration`、`position`、`closable`、`offset`、`title` 都通过第二个参数传入;`duration` 为 `0` 时提示常驻,只能手动关闭。 ```vue 默认配置 标题 + 5 秒 + 右上角 常驻(duration: 0) ``` ### 六个角位 六个角位各自维护一堆提示,互不干扰,同一角位的多条按添加顺序排列。 ```vue {{ position }} ``` ### 关闭单条与清空 `toast.close(id)` 关闭单条,`toast.clearAll()` 清空全部;`toastItems` 是编程式队列的共享响应式数组,可以拿它统计条数或取回 id。 ```vue 新建常驻提示 关闭最后一条 清空全部 当前队列 {{ count }} 条 ``` ### 声明式组件 用 `v-model` 控制显隐,`type`、`position`、`duration`、`closable` 等属性与编程式一致。 **演示说明:** 声明式与编程式的队列互相独立,这条提示不会出现在 toastItems 里。 ```vue 显示声明式提示 正文来自默认插槽,可以放 任意节点;不写插槽时回退到 message 属性。 close 触发次数:{{ closed }} ``` ## API ### toast 方法 | 方法 | 签名 | 说明 | | --- | --- | --- | | `toast.show` | `(message: string, options?: ToastOptions) => void` | 弹出提示,`type` 取 `options.type`,缺省为 `'default'` | | `toast.success` | `(message: string, options?: ToastOptions) => void` | 成功提示,`type` 强制为 `'success'` | | `toast.error` | `(message: string, options?: ToastOptions) => void` | 失败提示,`type` 强制为 `'error'` | | `toast.warning` | `(message: string, options?: ToastOptions) => void` | 警告提示,`type` 强制为 `'warning'` | | `toast.info` | `(message: string, options?: ToastOptions) => void` | 信息提示,`type` 强制为 `'info'` | | `toast.close` | `(id: number) => void` | 按 id 关闭单条 | | `toast.clearAll` | `() => void` | 清空编程式队列中的全部提示 | 四个语义方法会把 `options.type` 覆盖为对应值。等价底层函数 `showToast(message, options?)` 也可以单独导入使用。 ::: warning 方法不返回 id `toast.show` 与 `toast.success` 等方法的返回值是 `void`,拿不到新建提示的 id。需要精确关闭某一条时,从 `toastItems` 里取(例如最后一条的 `id`),或者干脆改用声明式 `MToast` + `v-model`。 ::: ### ToastOptions | 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `type` | `'default' \| 'success' \| 'error' \| 'warning' \| 'info'` | `'default'` | 语义类型;调用 `toast.success` 等方法时被强制覆盖 | | `title` | `string` | `undefined` | 标题 | | `duration` | `number` | `3000` | 自动关闭时长(ms),`0` 表示不自动关闭 | | `closable` | `boolean` | `false` | 是否显示关闭按钮 | | `position` | `'top-right' \| 'top-left' \| 'top-center' \| 'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-center'` | 显示角位 | | `offset` | `number` | `16` | 距视口边缘间距(px) | ### MToast Props | 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `modelValue` | `boolean` | `undefined` | 是否显示,配合 `v-model` 使用 | | `message` | `string` | `undefined` | 提示内容;默认插槽有内容时优先用插槽 | | `title` | `string` | `undefined` | 标题 | | `type` | `'default' \| 'success' \| 'error' \| 'warning' \| 'info'` | `'default'` | 语义类型 | | `duration` | `number` | `3000` | 自动关闭时长(ms),`0` 表示不自动关闭 | | `closable` | `boolean` | `false` | 是否显示关闭按钮 | | `position` | `'top-right' \| 'top-left' \| 'top-center' \| 'bottom-right' \| 'bottom-left' \| 'bottom-center'` | `'bottom-center'` | 显示角位 | | `offset` | `number` | `16` | 距视口边缘间距(px) | ### MToast Events | 事件 | 参数 | 说明 | | --- | --- | --- | | `update:modelValue` | `value: boolean` | 自动超时或点击关闭按钮时置为 `false` | | `close` | 无 | 自动超时或点击关闭按钮时触发 | ### MToast Slots | 插槽 | 参数 | 说明 | | --- | --- | --- | | `default` | 无 | 消息正文;无内容时回退到 `message` prop | ### 导出常量 | 常量 | 值 | 说明 | | --- | --- | --- | | `TOAST_DEFAULT_DURATION` | `3000` | 默认自动关闭时长(ms) | | `TOAST_DEFAULT_POSITION` | `'bottom-center'` | 默认角位 | ```ts export type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' export type ToastPosition = | 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center' ``` ### toastItems 共享状态 `toastItems` 是容器消费的响应式数组,`service` 通过内部的 `addToast` / `removeToast` / `clearToasts` 增删。它只记录编程式提示,声明式 `MToast` 不写入这个数组。 ```ts import { toastItems } from 'miao-design' export interface ToastItem { id: number message: string title?: string type: ToastType duration: number closable: boolean position: ToastPosition offset: number } ``` ## 使用建议 ::: tip 首次调用时才挂载容器 编程式提示的容器是模块级单例:第一次调用 `toast.*` 时才会把 `ToastContainer` 挂到 `document.body` 上,此后所有调用复用同一个容器。所以没有提示时页面上不存在多余的 DOM 节点,也不需要手动引入任何组件。 ::: ::: warning 不要在模块顶层调用 `toast.show()` 内部会创建 DOM 并调用 `render()`,属于浏览器环境操作。SSR 渲染阶段执行模块顶层代码会直接报错,请在事件回调、`onMounted` 或请求返回之后调用;同理,`toastItems` 只做状态读写,在服务端读它是安全的。 声明式 `MToast` 自身会 `Teleport` 到 body,在 SSR 场景下也建议配合 `v-model` 只在客户端展示。 ::: ::: warning 计时与堆叠规则 `duration > 0` 时才会渲染进度条,倒计时在提示显示期间运行,提示关闭或组件卸载都会清理计时器。 同一角位的多条提示按添加顺序堆叠,不同角位互不影响,堆叠间距取该角位第一条的 `offset` —— 也就是说同一角位里后加入的 `offset` 不生效。 `type='error'` 的提示使用 `aria-live="assertive"`,其余为 `polite`,屏幕阅读器会优先播报错误。 ::: ## 相关组件 - [Alert 警告提示](/components/alert):需要常驻页面内的提示条时用它,而不是轻提示。 - [Dialog 对话框](/components/dialog):需要用户确认的关键操作,应当用对话框。 - [Button 按钮](/components/button):轻提示常由按钮点击触发。 --- # v-ripple 涟漪 `v-ripple` 是一条**全局指令**,不是组件。把它挂在任意元素(原生 `div` / `button` 或别的组件)上,按下时就会出现 Material 风格的扩散波纹。它复刻了 MUI TouchRipple 的动效细节:按住常显、松手淡出、触摸延迟出现、键盘聚焦时从中心脉冲。库里的 `MButton`、`MFab`、`MCheckbox` 等组件已经在内部挂好了这条指令。 ## 代码演示 ### 基础用法 不带值即为默认参数,指令会自动给静态定位的宿主补上 `position: relative`。 **演示说明:** 按住不放可以保持波纹;松开或把指针移出元素后波纹淡出。键盘 Tab 聚焦后按 Enter / 空格也会出现居中的脉冲波纹。 ```vue 按住看我 button ``` ### 禁用涟漪 三种关闭方式:绑定 `false`、传 `disabled: true`,或者宿主本身是原生 `disabled` / `aria-disabled="true"`。 **演示说明:** 第三颗按钮没有做任何配置,仅因为它是原生 `disabled` 按钮,波纹就不会出现。 ```vue v-ripple="false" disabled: true 原生 disabled ``` ### 自定义选项 `color`、`duration`、`initialScale`、`center` 四个选项可以组合使用。 **演示说明:** `duration` 同时作用于进入与退出;`initialScale: 0.5` 让波纹从一半大小开始扩散,起点更明显、观感更快。 ```vue {{ host.label }} ``` ### 用在自定义元素与组件上 指令不挑宿主,普通 `div` 也能用;而已经自带涟漪的组件不需要再挂一次。 **演示说明:** `MButton` 内部已经有 `v-ripple`,再挂一次不会出现第二层波纹(容器记录在同一个宿主元素上),但会多出一套事件监听,没有必要。 ```vue 组件自带涟漪 重复挂载 v-ripple 自定义元素 ``` ## API ### 绑定值 RippleOptions | 选项 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `disabled` | `boolean` | `undefined` | 禁用涟漪 | | `color` | `string` | `undefined` | 涟漪颜色;缺省跟随宿主的 `currentColor` | | `duration` | `number` | `550` | 动画时长(ms),进入与退出共用 | | `initialScale` | `number` | `0` | 起始缩放,即从 `initialScale` 扩散到 `1` | | `center` | `boolean` | `false` | 是否从元素中心扩散;`false` 时从点击点扩散 | ```ts export type RippleOptions = | boolean | { disabled?: boolean // 禁用涟漪 color?: string // 涟漪颜色,默认跟随 currentColor duration?: number // 动画时长(ms),默认 550(进入与退出共用) initialScale?: number // 起始缩放,默认 0(即从 0 扩散到 1) center?: boolean // 是否从元素中心扩散,默认 false } ``` 绑定值的几种写法: | 写法 | 含义 | | --- | --- | | `v-ripple` | 使用全部默认参数 | | `v-ripple="false"` | 关闭涟漪 | | `v-ripple="{ duration: 300 }"` | 传入配置对象;`updated` 钩子会同步最新值,不需要重新绑定监听器 | ### 修饰符 无。所有配置都通过绑定值对象传入(`disabled`、`color`、`duration`、`initialScale`、`center`),没有 `.center` 这类修饰符写法。 ### 行为细节 - **定位**:宿主元素不是 `position: static` 时,指令会自动把它设为 `position: relative`,保证涟漪容器以宿主为定位上下文。 - **DOM 结构**:指令会向宿主 `appendChild` 一个 `` 容器,它成为宿主的最后一个子节点,波纹是这个容器的子节点。容器本身是绝对定位(`inset: 0`)且 `pointer-events: none`,不占布局空间,但依赖 `:last-child` 之类的选择器时要留意这个多出来的子节点。 - **触发条件**:宿主为原生 `disabled` 或 `aria-disabled="true"` 时不触发;鼠标只响应主键(`event.button === 0`)。 - **指针行为**:`pointerdown` 按下即出现,波纹直径恰好覆盖元素最远角;按住保持常显,`pointerup` / `pointercancel` / 鼠标 `pointerleave` 后淡出。 - **触摸与笔**:延迟 80ms 出现,用来区分「滚动」与「按下」;期间发生滚动或移动会取消这次波纹。 - **键盘**:`:focus-visible` 或按 Enter / 空格激活时产生居中脉冲波纹,失焦后停止。 - **颜色**:默认取宿主的 `currentColor` 并以约 30% 不透明度呈现,所以深底浅字的按钮会自动得到浅色波纹。 ## 使用建议 ::: tip 深色底上的波纹不用特意调色 波纹默认跟随 `currentColor`,而 `currentColor` 就是宿主文字的颜色。`MButton` 的 `contained` 变体把文字色设成了对比色,所以涟漪天然可见;只有当你想要一个和文字色不同的波纹时才需要传 `color`。 ```vue 深色底上的白色涟漪 ``` ::: ::: tip 给自定义元素补上交互语义 指令只负责视觉反馈,不会给 `div` 加 `tabindex` 或 `role`。如果宿主是可以点击的自定义元素,记得自己补 `role="button"` 与 `tabindex="0"`,否则键盘用户既聚焦不到、也不会有键盘涟漪。 ::: ::: warning 自带涟漪的组件不要再挂一次 `MButton`、`MFab`、`MCheckbox`、`MRadio`、`MSwitch`、`MDropdownItem`、`MSelect` 选项、`MTabs` 的关闭 / 新增按钮等都已内置这条指令,并且各自传了合适的配置(例如 `MCheckbox` 用 `{ center: true }`)。在它们身上再写一次 `v-ripple` 属于重复挂载:容器与状态都记录在同一个宿主元素上,后者会覆盖前者的记录,只是多出一个空的涟漪容器和一套多余的监听,不会有更好的效果。 ::: ::: warning 别把涟漪当点击反馈的全部 涟漪只在指针 / 键盘交互时出现,并且会被 `prefers-reduced-motion` 关掉。真正的状态变化(加载、成功、失败)仍然要用 `loading`、`toast` 这类明确的反馈来表达。 ::: ## 相关组件 - [Button 按钮](/components/button):内置 `v-ripple`,是最常见的涟漪宿主。 - [Fab 悬浮按钮](/components/fab):同样内置涟漪,扩展模式下图标与文字共用一层波纹。
下面的每个预览都由真实组件实时渲染 —— 直接点一下就能玩,进到文档页还能看到源码。
查看全部 26 个组件 →
正文放在默认插槽,footer 插槽存在时才会渲染底部操作区。
确认状态:{{ confirmed ? '已确认' : '未确认' }}
数字按 px 处理,纯数字字符串会被补上 px,其他字符串原样作为 CSS 宽度。
不传 radius 时圆角回退到全局令牌 var(--m-radius)(6px);全屏模式下该值被忽略。
var(--m-radius)
点击 #trigger 插槽时组件会记录该元素的包围盒,展开与收回都复用同一份起点。
显式传入 anchor 后,动画起点改由该元素中心决定,与点击位置无关。
点击遮罩或按 Esc 都会关闭,并抛出 close 事件。
两个关闭开关都设为 false,遮罩点击与 Esc 都不再关闭,只能点右上角关闭按钮。
{{ log }}
fullscreen 下对话框铺满视口并从底部向上滑入,width 与 radius 都会被忽略。
内容超出时只在正文区域内滚动,footer 始终贴在底部。
正文放在默认插槽,内容超出高度时只在正文区域内滚动。
{{ applied ? '条件已应用' : '尚未应用条件' }}
四个方向共用同一套结构,只是贴边的位置与滑入方向不同;左右方向决定宽度,上下方向决定高度。
size 为数字或纯数字字符串时按 px 处理,其他字符串原样作为 CSS 长度。
showClose 设为 false 后右上角不再有关闭按钮,关闭只能依赖遮罩、Esc 或底部的自定义按钮。
这条抽屉关闭了 Esc 关闭,请用遮罩点击、右上角按钮或下方按钮。