Appearance
AI 使用指南
本页为每个组件提供一段可直接复制的 Vue SFC,从 x-next 根入口导入。把这些示例交给 AI 编程助手作为上下文,它生成的代码就会使用真实的 props 与事件,而不是照搬其他组件库的属性名。
每节开头链接到对应的组件文档,那里有可交互的在线示例和完整 API 表;本页代码聚焦用法要点。随包分发的 node_modules/x-next/AI_USAGE.md 是本页的完整版本,可直接提供给 AI 工具读取。
安装与初始化
sh
npm install x-next应用需已有 Vue 3、TypeScript 和 Vue SFC 构建支持。按需具名导入无需安装全局插件;在 main.ts 引入一次样式:
ts
import { createApp } from 'vue';
import App from './App.vue';
import 'x-next/dist/style.css';
createApp(App).mount('#app');下方各节使用局部具名导入。需要全局注册时可另用 app.use(XNext)(默认导出),它会注册全部组件。根入口的 Dialog 是命令式服务,模板组件是 DialogComponent;MessageBox 使用服务函数。
接入时遵循这些约定:
- 原生属性直接传:
id、name、aria-*等原生属性不在组件的 props 类型里,需要时用属性对象v-bind传入;组件 props 和事件照常直接绑定。 - 保持受控值稳定:
v-model要初始化为明确的布尔值、字符串、数字或数组。非受控初始化用defaultValue/defaultChecked;把受控值改成undefined可能切换到内部状态。清空日期、时间统一保存''。 - 选择正确的弹层宿主:Select、DatePicker、TimePicker、Popover、Dropdown 用
popupContainer;Dialog、Message、MessageBox、Notification、Popconfirm 用renderTo。容器须已挂载;局部容器要留意定位、滚动和裁剪,Teleport 到body的弹层不继承原局部容器的主题变量。 - 优先用公开主题变量:参考样式与主题。暗色容器上的 alias 链会重新声明;
themeColor写入的内联 primary alias 有覆盖边界,按主题文档设置相关公开 alias。 - 约束业务全局 CSS:reset 用
:where(...)降低优先级并限定作用域;宽泛的原生元素选择器可能覆盖组件样式。自定义内容用插槽和公开属性,不依赖内部结构类名。 - 清理页面创建的服务:保留句柄,在路由页面卸载时调用
close();KeepAlive 页面可在停用时清理。Dialog / MessageBox 的命令式关闭不触发确认、取消回调,业务取消逻辑需自行调用;Message / Notification 的关闭会触发各自的onClose。
ConfigProvider
局部品牌色用 themeColor,它一次生成色阶并重绑定组件别名;不必逐个覆盖 Button、Switch 的颜色。将弹层宿主放在同一主题子树内,Teleport 后仍能继承。Select 默认跟随祖先滚动;要滚动关闭可传 triggerProps.scrollToClose。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, ConfigProvider, Select, Switch } from 'x-next';
const brand = ref('#2f6feb');
const dark = ref(false);
const value = ref('day');
const overlayHost = ref<HTMLElement>();
const switchAttrs = { 'aria-label': '局部暗色' };
const selectAttrs = { 'aria-label': '统计粒度' };
const options = [
{ label: '按天', value: 'day' },
{ label: '按周', value: 'week' },
];
</script>
<template>
<ConfigProvider :theme-color="brand" :dark="dark" class="brand-scope">
<Button type="primary" @click="brand = brand === '#2f6feb' ? '#0f766e' : '#2f6feb'">
切换品牌色
</Button>
<Switch v-model="dark" v-bind="switchAttrs" />
<Select
v-if="overlayHost"
v-model="value"
:options="options"
:popup-container="overlayHost"
v-bind="selectAttrs"
style="width: 160px"
/>
<div ref="overlayHost" class="brand-overlay" />
</ConfigProvider>
</template>
<style scoped>
.brand-scope {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
padding: 16px;
color: var(--x-color-text-primary);
background: var(--x-color-bg-surface);
}
.brand-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 2000;
}
</style>已有主题容器时,也可调用公开 applyThemeColor(seed, { target, dark });切换暗色后需重新调用。只改后代的几条 semantic CSS 变量不会重算祖先 alias;:tag="null" 的 ConfigProvider 没有承载局部主题的 DOM。
Button
组件文档与 live demo。type 默认 'default',视觉主按钮用 'primary';htmlType 默认 'button',原生提交用 'submit'。独立使用时尺寸回退为 'medium',disabled / loading 默认关闭。
click 载荷为 MouseEvent,禁用或加载中不会触发。default、prefix、suffix、icon 插槽可组合内容;异步业务自行维护 loading。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button } from 'x-next';
import 'x-next/dist/style.css';
const loading = ref(false);
const result = ref('尚未保存');
async function save(_event: MouseEvent) {
loading.value = true;
try {
await Promise.resolve();
result.value = '保存完成';
} finally { loading.value = false; }
}
</script>
<template>
<Button type="primary" html-type="button" :loading="loading" @click="save">保存</Button>
<p role="status">{{ result }}</p>
</template>Checkbox
组件文档与 live demo。使用普通 v-model;模型支持 boolean 或 (string | number | boolean)[],数组模式配合 value。defaultChecked、disabled、indeterminate 默认 false;半选状态不会替代模型值。
change(value, event) 返回新值和原生 Event。默认插槽是文案,checkbox 插槽提供 checked / disabled。id、name、required、form、tabindex、autofocus、autocomplete 和 aria-* 传给原生复选框。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Checkbox } from 'x-next';
import 'x-next/dist/style.css';
const accepted = ref(false);
const inputAttrs = { id: 'accept-terms', name: 'terms', required: true };
const feedback = ref('尚未选择');
function changed(value: boolean | (string | number | boolean)[], event: Event) {
feedback.value = `${value === true ? '已同意' : '未同意'}(${event.type})`;
}
</script>
<template>
<Checkbox v-model="accepted" v-bind="inputAttrs" @change="changed">
我已阅读使用说明
</Checkbox>
<p role="status">{{ feedback }}</p>
</template>Radio
组件文档与 live demo。Radio / RadioGroup 的模型和选项值支持 string | number | boolean,保留值类型;布尔选项写 :value="true"。单个 Radio 的 value 默认 true,组的非受控默认值为 ''。
组默认 type='radio'、direction='horizontal'、disabled=false;change(value, event) 返回选项值和 Event。Radio 默认插槽是文案,组也支持 options 和 label / radio 插槽。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Radio, RadioGroup } from 'x-next';
import 'x-next/dist/style.css';
const enabled = ref<string | number | boolean>(true);
const controlAttrs = { name: 'delivery-mode', 'aria-label': '是否开启通知' };
const feedback = ref('当前开启');
function changed(value: string | number | boolean, _event: Event) {
feedback.value = value === true ? '当前开启' : '当前关闭';
}
</script>
<template>
<RadioGroup v-model="enabled" v-bind="controlAttrs" @change="changed">
<Radio :value="true">开启</Radio>
<Radio :value="false">关闭</Radio>
</RadioGroup>
<p role="status">{{ feedback }}</p>
</template>Select
组件文档与 live demo。v-model 返回选项的 value;单选通常为标量,多选为数组,也支持对象值。multiple=false、allowClear=false;单选搜索默认关闭,多选默认开启。options 使用可变数组,常用字段是 label、value、disabled。
change(value) 返回新值,clear(event) 返回 Event;单选清空通常为 '',多选为 [],已选禁用项可能保留。v-model:popup-visible 控制弹层,option / empty / header / footer 等插槽见组件文档。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Select } from 'x-next';
import 'x-next/dist/style.css';
const city = ref('');
const options = [
{ label: '杭州', value: 'hangzhou' },
{ label: '上海', value: 'shanghai' },
{ label: '暂未开放', value: 'pending', disabled: true },
];
const feedback = ref('尚未选择');
function changed(value: unknown) {
feedback.value = typeof value === 'string' && value ? `城市代码:${value}` : '已清空';
}
</script>
<template>
<Select v-model="city" :options="options" allow-search allow-clear placeholder="选择城市" @change="changed" />
<p role="status">{{ feedback }}</p>
</template>Slider
组件文档与 live demo。普通模型为 number,range 模式为 [number, number]。默认 min=0、max=100、step=1、range=false、disabled=false、showTooltip=true;范围端点按大小排序并按步长归一化。
change(value) 表示值变化,change-end(value) 表示一次拖拽、点击、键盘或输入提交结束,载荷均为 number | [number, number]。marks 是数值键到文字的映射,formatTooltip 可格式化提示。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Slider } from 'x-next';
import 'x-next/dist/style.css';
const volume = ref(40);
const controlAttrs = { 'aria-label': '音量' };
const committed = ref('尚未调整');
function finish(value: number | [number, number]) {
committed.value = typeof value === 'number' ? `已提交音量 ${value}` : value.join(' ~ ');
}
</script>
<template>
<div style="max-width: 320px">
<Slider v-model="volume" :step="10" :marks="{ 0: '静音', 100: '最大' }" v-bind="controlAttrs" @change-end="finish" />
<p role="status">当前 {{ volume }};{{ committed }}</p>
</div>
</template>Rate
组件文档与 live demo。数值模型默认非受控值为 0,count=5;allowHalf、allowClear、readonly、disabled 默认关闭。启用半星后步长为 0.5,值限制在有效数量内;没有 size 属性。
change(value) 返回 number,hover-change(value) 返回 number | undefined。character 插槽提供 index、value、count、disabled;tooltips 为提示数组。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Rate } from 'x-next';
import 'x-next/dist/style.css';
const score = ref(3.5);
const controlAttrs = { 'aria-label': '服务评分' };
const feedback = ref('请评分');
function changed(value: number) { feedback.value = value ? `评分 ${value}` : '已清除评分'; }
</script>
<template>
<Rate v-model="score" allow-half allow-clear v-bind="controlAttrs" @change="changed" />
<p role="status">{{ feedback }}</p>
</template>Switch
组件文档与 live demo。普通 v-model 支持 boolean | string | number;checkedValue=true、uncheckedValue=false、defaultChecked=false,默认 type='circle'。disabled / loading 阻止切换。
change(value, event) 返回新值和 Event;beforeChange(newValue) 可返回布尔值或 Promise,false 或拒绝阻止切换。checked / unchecked 插槽及 checkedText / uncheckedText 显示两态文案,line 类型不显示文案。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Switch } from 'x-next';
import 'x-next/dist/style.css';
const enabled = ref(false);
const controlAttrs = { 'aria-label': '通知开关' };
const feedback = ref('通知关闭');
function changed(value: boolean | string | number, _event: Event) {
feedback.value = value === true ? '通知开启' : '通知关闭';
}
</script>
<template>
<Switch v-model="enabled" checked-text="开" unchecked-text="关" v-bind="controlAttrs" @change="changed" />
<p role="status">{{ feedback }}</p>
</template>DatePicker
组件文档与 live demo。默认 type='date'、allowClear=true、showToday=true、showTime=false、position='bl'。format 控制显示,valueFormat 控制输出;本例使用 'YYYY-MM-DD' 字符串。公开 DatePickerModelValue 还包含数字、Date、数组和 undefined。
change(value, date, dateString) 为提交值,select 是面板选择;后两个参数声明为 unknown。清空返回 undefined,本例归一化为 '' 保持受控。支持 cell / extra / prefix / suffix-icon 插槽;带时间选择需确认后提交。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { DatePicker, type DatePickerModelValue } from 'x-next';
import 'x-next/dist/style.css';
const date = ref('');
const feedback = ref('尚未选择日期');
function update(value: DatePickerModelValue) { date.value = typeof value === 'string' ? value : ''; }
function changed(value: DatePickerModelValue, _date: unknown, _text: unknown) {
update(value);
feedback.value = date.value ? `日期:${date.value}` : '已清空日期';
}
</script>
<template>
<DatePicker :model-value="date" value-format="YYYY-MM-DD" placeholder="选择日期"
@update:model-value="update" @change="changed" />
<p role="status">{{ feedback }}</p>
</template>TimePicker
组件文档与 live demo。默认 type='time'、allowClear=true、disableConfirm=false,默认格式为 'HH:mm:ss'。format 同时决定显示与输出;step 用 { hour, minute, second },各步长默认 1。
change(timeString, time) 是确认值变化,select 是面板选择;字符串载荷为 string | (string | undefined)[] | undefined,第二参数为 unknown。清空归一化为 '';支持 prefix / suffix-icon / extra 插槽及 disabledHours 等限制。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { TimePicker } from 'x-next';
import 'x-next/dist/style.css';
const time = ref('');
const feedback = ref('尚未选择时间');
function update(value: unknown) { time.value = typeof value === 'string' ? value : ''; }
function changed(value: string | (string | undefined)[] | undefined, _time: unknown) {
update(value);
feedback.value = time.value ? `时间:${time.value}` : '已清空时间';
}
</script>
<template>
<TimePicker :model-value="time" format="HH:mm" :step="{ minute: 15 }" placeholder="选择时间"
@update:model-value="update" @change="changed" />
<p role="status">{{ feedback }}</p>
</template>Alert
组件文档与 live demo。用 v-model:visible 控制显示;defaultVisible=true、type='info'、showIcon=true、closable=false。常用类型为 info、success、warning、error、normal。
close(event) 返回 MouseEvent,after-close 在关闭动画结束后触发且无参数。默认插槽放正文,title / icon / action / close-element 插槽可自定义对应内容。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Alert, Button } from 'x-next';
import 'x-next/dist/style.css';
const visible = ref(true);
const feedback = ref('提示显示中');
function closed(_event: MouseEvent) { feedback.value = '已点击关闭'; }
function restore() { visible.value = true; feedback.value = '提示显示中'; }
</script>
<template>
<Alert v-model:visible="visible" type="warning" title="检查待提交内容" closable @close="closed">
提交前请核对所选日期。
</Alert>
<Button @click="restore">重新显示</Button>
<p role="status">{{ feedback }}</p>
</template>Dialog
组件文档与 live demo。从根入口导入 DialogComponent as XDialog,普通 v-model 为布尔值且默认 false。renderTo='body'、mask=true、maskToClose=true、escToClose=true、destroyOnClosed=true;width 支持数字或 CSS 长度。
title、默认正文、footer 是公开插槽,默认不生成业务按钮。close(action, event?) 的 action 为 'cancel' | 'ok',closed 表示关闭动画结束;直接改模型不会调用业务取消逻辑。根入口服务 Dialog(options) 返回 close() / update(options) 句柄。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, DialogComponent as XDialog } from 'x-next';
import 'x-next/dist/style.css';
const visible = ref(false);
const feedback = ref('尚未确认');
function confirm() { feedback.value = '已确认'; visible.value = false; }
function cancel() { feedback.value = '已取消'; visible.value = false; }
</script>
<template>
<Button @click="visible = true">打开对话框</Button>
<XDialog v-model="visible" title="确认安排" :width="420">
<p>核对安排后确认。</p>
<template #footer>
<Button @click="cancel">取消</Button>
<Button type="primary" @click="confirm">确认</Button>
</template>
</XDialog>
<p role="status">{{ feedback }}</p>
</template>Message
组件文档与 live demo。Message(options) 或 .success() / .error() / .loading() 等显示提示。默认 type='info'、duration=3000 毫秒、position='top'、showClose=false;duration=0 持续显示,内容用 message 或 content。
返回句柄只有 close();同一容器和位置下再次传相同 id 更新消息,没有句柄 update()。onClose(id?) 在自动或手动关闭时触发;自定义内容可用 VNode / 渲染函数。页面只清理自己持有的句柄。
vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { Button, Message } from 'x-next';
import 'x-next/dist/style.css';
const feedback = ref('尚未显示提示');
let handle: ReturnType<typeof Message> | undefined;
function show() {
handle?.close();
handle = Message({ message: '保存完成', type: 'success', duration: 0, showClose: true,
onClose: () => { feedback.value = '提示已关闭'; } });
feedback.value = '提示显示中';
}
onBeforeUnmount(() => handle?.close());
</script>
<template>
<Button @click="show">显示提示</Button>
<Button @click="handle?.close()">关闭提示</Button>
<p role="status">{{ feedback }}</p>
</template>MessageBox
组件文档与 live demo。调用 MessageBox(options),默认类型为 'success'、宽度 360、居中、显示遮罩和关闭按钮、点击遮罩不关闭。title / content 支持文字、VNode 或渲染函数;footer=false 隐藏页脚。
返回 close() / update(options) 句柄,结果通过 onOk(event) / onCancel(event) 回调处理。beforeOnOk / beforeOnCancel 返回 boolean | Promise<boolean>,必须返回 true 才放行;异步业务放在前置钩子。它不是 Promise,没有 confirm();.warning(title, content) 等返回的 .ok() / .cancel() 用于注册链式回调。
vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { Button, MessageBox } from 'x-next';
import 'x-next/dist/style.css';
const feedback = ref('尚未确认');
let handle: ReturnType<typeof MessageBox> | undefined;
function ask() {
handle?.close();
feedback.value = '等待确认';
handle = MessageBox({ title: '确认保存?', content: '确认后保存当前安排。', type: 'warning',
beforeOnOk: () => true,
onOk: (_event: Event) => { feedback.value = '已确认保存'; },
onCancel: (_event: Event) => { feedback.value = '用户已取消'; } });
}
function close() { handle?.close(); feedback.value = '已主动关闭'; }
onBeforeUnmount(() => handle?.close());
</script>
<template>
<Button @click="ask">请求确认</Button>
<Button @click="close">主动关闭</Button>
<p role="status">{{ feedback }}</p>
</template>Notification
组件文档与 live demo。Notification(options) 或 .success() 等显示通知。服务默认 type='info'、duration=3000 毫秒、position='top-right'、offset=20、showClose=true;四角位置可选,duration=0 持续显示。
title 和 message / content 是内容,footer / icon 可用 VNode 或渲染函数。返回句柄只有 close();同一容器与位置下复用 id 更新。onClick() 无参数,onClose(id?) 接收标识;.remove(id) / .clear(position?) 是服务级方法。
vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { Button, Notification } from 'x-next';
import 'x-next/dist/style.css';
const feedback = ref('尚未显示通知');
let handle: ReturnType<typeof Notification> | undefined;
function show() {
handle?.close();
handle = Notification({ title: '任务完成', message: '结果已准备好。', type: 'success', duration: 0,
onClick: () => { feedback.value = '已点击通知'; },
onClose: () => { feedback.value = '通知已关闭'; } });
feedback.value = '通知显示中';
}
onBeforeUnmount(() => handle?.close());
</script>
<template>
<Button @click="show">显示通知</Button>
<Button @click="handle?.close()">关闭通知</Button>
<p role="status">{{ feedback }}</p>
</template>Popconfirm
组件文档与 live demo。普通布尔 v-model 控制显示,defaultVisible=false、type='danger'、position='top'、width=150,点击默认插槽触发。content / icon 插槽可自定义确认内容;按钮文字用 okText / cancelText。
ok(event) / cancel(event) 返回 Event,change(visible) 返回布尔值。onBeforeOk / onBeforeCancel 支持布尔、void 或 Promise,返回 false 可阻止关闭;异步钩子可自动等待,普通 ok 监听器不会自动阻止关闭。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Popconfirm } from 'x-next';
import 'x-next/dist/style.css';
const visible = ref(false);
const feedback = ref('记录保留中');
function remove(_event: Event) { feedback.value = '已确认删除'; }
function cancel(_event: Event) { feedback.value = '已取消删除'; }
</script>
<template>
<Popconfirm v-model="visible" content="删除这条记录?" ok-text="删除" cancel-text="保留"
@ok="remove" @cancel="cancel">
<Button status="danger">删除记录</Button>
</Popconfirm>
<p role="status">{{ feedback }}</p>
</template>Popover
组件文档与 live demo。使用 v-model:popup-visible;默认 trigger='click'、position='bottom'、showArrow=false、defaultPopupVisible=false、unmountOnClose=true。默认插槽是触发器,title / content 插槽放浮层内容。
popup-visible-change(visible) 返回布尔值,show / hide 无参数。点击触发器或外部默认可以关闭;popupContainer 和 renderToBody 决定宿主,局部主题下按实际挂载位置核对样式。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Popover } from 'x-next';
import 'x-next/dist/style.css';
const visible = ref(false);
const feedback = ref('说明未打开');
function changed(value: boolean) { feedback.value = value ? '说明已打开' : '说明已关闭'; }
</script>
<template>
<Popover v-model:popup-visible="visible" title="安排说明" show-arrow @popup-visible-change="changed">
<Button>查看说明</Button>
<template #content>
<p>时间以页面所选值为准。</p>
<Button @click="visible = false">关闭说明</Button>
</template>
</Popover>
<p role="status">{{ feedback }}</p>
</template>Progress
组件文档与 live demo。percent 为 0 到 1 的比例,默认 0,不是 0 到 100;超出范围会裁剪,非有限值显示为 0。默认 type='line'、showText=true、animation=false,steps>0 使用分段模式。
无模型和业务 change 事件,由业务更新 percent。status 为 normal、success、warning、danger,未设置时达到 1 自动为 success;text 插槽提供 { percent }。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Progress } from 'x-next';
import 'x-next/dist/style.css';
const percent = ref(0.25);
function advance() { percent.value = Math.min(1, percent.value + 0.25); }
</script>
<template>
<div style="max-width: 320px">
<Progress :percent="percent">
<template #text="scope">已完成 {{ Math.round(scope.percent * 100) }}%</template>
</Progress>
<Button :disabled="percent >= 1" @click="advance">推进任务</Button>
</div>
</template>Dropdown
组件文档与 live demo。使用 v-model:popup-visible,默认 trigger='click'、position='bottom'、hideOnSelect=true。根入口导出 DropdownOption、DropdownGroup、DropdownSubmenu;菜单内容放 content 插槽,另有 empty / footer 插槽。
select(value, event) 返回 string | number | Record<string, any> | undefined 和 Event,popup-visible-change 返回布尔值。选项支持 disabled;字符搜索 typeahead、自定义控件的通用键盘代理及 Tab 自动关闭仍有边界,不能据方向键导航推断这些能力已实现。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Dropdown, DropdownOption } from 'x-next';
import 'x-next/dist/style.css';
const visible = ref(false);
const feedback = ref('尚未选择操作');
function select(value: unknown, _event: Event) {
feedback.value = value === 'copy' ? '已选择复制' : '已选择下载';
}
</script>
<template>
<Dropdown v-model:popup-visible="visible" @select="select">
<Button>更多操作</Button>
<template #content>
<DropdownOption value="copy">复制</DropdownOption>
<DropdownOption value="download">下载</DropdownOption>
<DropdownOption value="archive" disabled>归档暂不可用</DropdownOption>
</template>
</Dropdown>
<p role="status">{{ feedback }}</p>
</template>Pagination
组件文档与 live demo。total 必填且表示记录总数。使用 v-model:current 和 v-model:page-size;默认非受控值为页码 1、每页 10,showTotal / showPageSize / showJumper 默认关闭,autoAdjust=true。
change(current) 和 page-size-change(pageSize) 各返回一个数字;不会请求数据。页码按总数裁剪,每页条数应为正数。total 插槽提供总数,page-item 提供页码;改变条数后的业务重新查询需自行实现。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Pagination } from 'x-next';
import 'x-next/dist/style.css';
const current = ref(1);
const pageSize = ref(10);
const feedback = ref('当前第 1 页');
function changed(page: number) { feedback.value = `当前第 ${page} 页`; }
function resized(size: number) { current.value = 1; feedback.value = `每页 ${size} 条,返回第一页`; }
</script>
<template>
<Pagination v-model:current="current" v-model:page-size="pageSize" :total="95" show-total show-page-size
:page-size-options="[10, 20, 50]" @change="changed" @page-size-change="resized" />
<p role="status">{{ feedback }};每页 {{ pageSize }} 条</p>
</template>Steps
组件文档与 live demo。子组件的公开名称是 Step。使用 v-model:current,序号从 1 开始,defaultCurrent=1、status='process'、type='default'、direction='horizontal';点击切换需开启 changeable,默认关闭。
change(step, event) 返回步骤号和 Event。Step 支持 title、description、disabled、status;node / icon 插槽提供步骤与状态,默认 / description 插槽可替换文案。业务完成状态由业务自身维护。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Steps, Step } from 'x-next';
import 'x-next/dist/style.css';
const current = ref(1);
const feedback = ref('正在填写');
function changed(step: number, _event: Event) { feedback.value = `已切换到第 ${step} 步`; }
</script>
<template>
<Steps v-model:current="current" changeable @change="changed">
<Step title="填写" description="准备安排" />
<Step title="核对" description="检查内容" />
<Step title="完成" description="提交结果" />
</Steps>
<p role="status">{{ feedback }}</p>
</template>Tabs
组件文档与 live demo。使用 v-model:active-key,类型为 string | number;TabPane 用 Vue 的 key 标识页签,键类型须与模型一致。默认 type='line'、position='top'、trigger='click'、lazyLoad=false、destroyOnHide=false。
change(key) 返回键值,tab-click(key, event) 还返回 Event。禁用配置放在 TabPane;TabPane 默认插槽放正文,title 插槽自定义标题,Tabs 的 extra 放额外内容。可编辑模式的 add / delete 事件需要业务维护页签数据。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Tabs, TabPane } from 'x-next';
import 'x-next/dist/style.css';
const active = ref<string | number>('overview');
const feedback = ref('当前概览');
function changed(key: string | number) { feedback.value = `当前页签:${key}`; }
</script>
<template>
<Tabs v-model:active-key="active" @change="changed">
<TabPane key="overview" title="概览">查看当前安排。</TabPane>
<TabPane key="history" title="历史">查看已完成记录。</TabPane>
<TabPane key="restricted" title="未开放" disabled>此内容暂不可选。</TabPane>
</Tabs>
<p role="status">{{ feedback }}</p>
</template>Empty
组件文档与 live demo。description 未传时使用语言包默认文案,null 或 '' 隐藏描述;imgSrc 自定义图片,image 插槽优先于图片地址。默认插槽替代描述,可放业务操作按钮。
没有模型或业务事件;刷新、创建等动作由插槽中的业务控件处理。显式提供描述、图片或插槽时采用调用方配置;裸 Empty 才会交由外层 ConfigProvider 的 empty 插槽接管。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Empty } from 'x-next';
import 'x-next/dist/style.css';
const refreshed = ref(false);
function refresh() { refreshed.value = true; }
</script>
<template>
<Empty>
<p>{{ refreshed ? '刷新完成,仍无记录' : '暂无记录' }}</p>
<Button @click="refresh">刷新记录</Button>
</Empty>
</template>Upload
组件文档与 live demo。action 是上传地址,也可用 customRequest 接管请求;两者都没提供时组件不会发起上传。name 是 FormData 字段名,默认 'file';method 默认 'post',withCredentials 默认 false。
接入已有上传方法的推荐写法:customRequest 返回 Promise 时组件自动接管状态,resolve 即成功(结果写入 file.response,可用 responseUrlKey 提取 url,字符串支持 data.url 嵌套路径)、reject 即失败并出现重试入口,无需手动调用 onSuccess / onError。axios、flyio、fetch 或任何返回 Promise 的方法都适用。返回值是 fetch Response 时组件读取响应体;非 2xx 按失败处理,error.status 为状态码。需要进度与取消时,把 option 的 signal(取消 / 移除 / 卸载时触发)与 onProgress(0 ~ 1)透传给底层请求。返回 { abort } 并手动回调的旧写法仍然支持。
列表默认非受控,用 defaultFileList 给初值;需要外部驱动时用 v-model:file-list,列表项以 uid 为稳定标识。listType 取 'text' | 'picture' | 'picture-card'。limit 为数量上限,超出会触发 exceed,超出部分不进入列表;beforeUpload 返回 false 拦截、返回新 File 替换,支持 Promise。
change({ file, fileList }) 覆盖加入、进度、成功、失败和移除;success / error / remove / progress 是更细粒度的补充。autoUpload=false 时文件先以 init 入列,再用组件 submit() 上传、abort() 取消、retry() 重试。图片卡场景可加 image-preview 用内置 ImagePreview 看图。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Upload, type UploadFile } from 'x-next';
import 'x-next/dist/style.css';
const fileList = ref<UploadFile[]>([]);
const status = ref('尚未选择文件');
// 项目里已封装好的上传方法:返回 Promise 即可,组件自动显示成功与失败
function uploadAttachment(params: { file: File; orderId: string }) {
const form = new FormData();
form.append('file', params.file);
form.append('orderId', params.orderId);
return fetch('/api/upload', { method: 'POST', body: form }).then((response) => {
if (!response.ok) throw new Error(`上传失败:${response.status}`);
return response.json() as Promise<{ data: { url: string } }>;
});
}
function changed(info: { file: UploadFile; fileList: UploadFile[] }) {
status.value = `${info.file.name}:${info.file.status ?? 'unknown'}`;
}
function beforeUpload(file: File) {
// 超过 2MB 直接拦截,文件不会进入列表
return file.size <= 2 * 1024 * 1024;
}
</script>
<template>
<Upload
v-model:file-list="fileList"
:custom-request="({ file }) => uploadAttachment({ file, orderId: 'SO-20261003' })"
response-url-key="data.url"
name="file"
multiple
list-type="picture"
accept="image/*"
:limit="5"
:before-upload="beforeUpload"
@change="changed"
/>
<p role="status">{{ status }}</p>
</template>BarChart
组件文档与 live demo。按天 / 小时聚合的柱状趋势图:categories 是类目轴标签,series 是数据系列({ name, data, color }),两者按下标对应。单系列就是普通柱状图;多系列默认并排,加 stack 变成堆叠并显示合计。轻量迷你走势用 TrendChart,需要坐标轴与读数用本组件。
数值轴自动取 1/2/5 步长的整洁刻度(所以刻度行数可能少于 grid.horizontalLinesNumber);传 max 可锁定上界。loading 显示加载遮罩;数据全为 0 时显示空态,可用 empty 插槽替换。null / 负数按 0 处理且不画柱体。
tooltip 默认开启,legend 开启后点击图例可隐藏系列并触发 legendChange;点击柱体触发 barClick,命中具体柱体时带 seriesIndex / value,未命中时为 null。valueFormatter 统一控制提示、图例与数值轴的数字格式。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { BarChart } from 'x-next';
import 'x-next/dist/style.css';
const categories = ref(['09-28', '09-29', '09-30', '10-01', '10-02']);
const series = ref([
{ name: '浏览量', data: [86, 142, 98, 210, 176] },
{ name: '访客数', data: [52, 88, 61, 120, 96] },
]);
const picked = ref('尚未选择');
// 原生属性(含 aria-*)用对象 v-bind 传递,组件 props 与事件仍直接绑定
const chartAttrs = { 'aria-label': '每日浏览量与访客数' };
function onBarClick(data: { label: string; total: number; value: number | null }) {
picked.value = `${data.label}:合计 ${data.total}`;
}
</script>
<template>
<BarChart
v-bind="chartAttrs"
:categories="categories"
:series="series"
:height="240"
legend
@bar-click="onBarClick"
/>
<p role="status">{{ picked }}</p>
</template>LineChart
组件文档与 live demo。随时间看趋势就用它:categories 是时间轴标签,series 是数据系列({ name, data, color }),按下标对应。多系列默认叠放,加 area 填充、再加 stack 变堆叠面积。按类目比大小用 BarChart,看占比用 PieChart,卡片角落的迷你走势用 TrendChart——完整选择指引见图表总览。
缺测要显式表达:把没有数据的点写成 null,默认会在该处断开折线(不会画成跌到 0);确需连线时传 :connect-nulls="true"。数值轴对纯正数数据保持 0 基线,出现负值才向下扩展;min / max 可锁定区间。smooth 默认开启,show-points 默认 'auto'(点数少时常显)。
tooltip 默认开启,legend 开启后点击图例可隐藏系列并触发 legendChange(数值轴会按剩余可见系列重算);点击触发 lineClick,命中数据点时带 seriesIndex / value。valueFormatter 同时作用于提示与图例,labels.yFormatter 只作用于数值轴。
vue
<script setup lang="ts">
import { ref } from 'vue';
import { LineChart } from 'x-next';
import 'x-next/dist/style.css';
const categories = ref(['09-28', '09-29', '09-30', '10-01', '10-02']);
// 10-01 当天缺测:null 会让折线在该处断开
const series = ref([
{ name: '请求量', data: [820, 932, null, 934, 1290] },
{ name: '错误数', data: [12, 18, null, 9, 22] },
]);
const picked = ref('尚未选择');
function onLineClick(data: { label: string; value: number | null }) {
picked.value = `${data.label}:${data.value ?? '缺测'}`;
}
</script>
<template>
<LineChart
:categories="categories"
:series="series"
:height="280"
area
legend
@line-click="onLineClick"
/>
<p role="status">{{ picked }}</p>
</template>PieChart
组件文档与 live demo。表达占比:data 是 { name, value }[],值按可见分片求和换算角度。innerRadius="62%" 渲染成环形并在中心显示合计(推荐形态,信息密度更高);数字形式的 innerRadius 按 px。负值、0 与非有限数会被跳过,不计入总量。
类别要收敛:饼图超过 7 个分片就难以阅读,先按值排序、把尾部合并成「其他」再传入(下面给出写法)。需要精确比较大小改用 BarChart,需要看随时间的变化改用 LineChart。
legend 开启后图例项展示数值与占比,点击可隐藏分片——剩余分片重新分配角度、中心合计同步更新,并触发 legendChange;点击分片触发 itemClick。center / tooltip / label / legend 插槽可分别自定义。
vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import { PieChart } from 'x-next';
import 'x-next/dist/style.css';
const raw = ref([
{ name: 'search', value: 820 },
{ name: 'read_file', value: 610 },
{ name: 'write_file', value: 430 },
{ name: 'run_shell', value: 320 },
{ name: 'browse', value: 210 },
{ name: 'exec_python', value: 180 },
{ name: 'grep', value: 140 },
]);
const LIMIT = 5;
// 饼图不宜超过 7 个分片:取前 N 项,尾部合并为「其他」
const data = computed(() => {
const sorted = [...raw.value].sort((a, b) => b.value - a.value);
const top = sorted.slice(0, LIMIT);
const rest = sorted.slice(LIMIT).reduce((sum, item) => sum + item.value, 0);
return rest > 0 ? [...top, { name: '其他', value: rest }] : top;
});
const picked = ref('尚未选择');
function onItemClick(item: { name: string; value: number; percent: number; formatPercent: (p: number) => string }) {
picked.value = `${item.name}:${item.value}(${item.formatPercent(item.percent)})`;
}
</script>
<template>
<PieChart
:data="data"
inner-radius="62%"
:height="280"
legend
@item-click="onItemClick"
/>
<p role="status">{{ picked }}</p>
</template>