@a-drowned-fish/rox-v
一个基于 Vue 3 的组件库,支持按需引入和自动导入。 支持 SSR

📖 文档
完整的 API 文档和在线演示:Documents
安装
npm install @a-drowned-fish/rox-v
# 或
yarn add @a-drowned-fish/rox-v
# 或
pnpm add @a-drowned-fish/rox-v
使用方式
1. 完整引入
import { createApp } from "vue";
import RoxV from "@a-drowned-fish/rox-v";
import "@a-drowned-fish/rox-v/dist/style.css"; // 如果需要样式 (SSR OR SSG 建议引入, 避免页面样式闪烁)
const app = createApp(App);
app.use(RoxV);
2. 按需引入(推荐)
<template>
<Button>点击我</Button>
<InputOtp />
</template>
<script setup lang="ts">
import { Button, InputOtp } from "@a-drowned-fish/rox-v";
</script>
可用组件
组件列表
InputOtp - OTP 输入组件
一个用于输入一次性密码(OTP)的组件,支持自定义长度、样式和间距。
基础用法
<template>
<InputOtp v-model="code" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const code = ref("");
</script>
自定义长度
<template>
<!-- 4位验证码 -->
<InputOtp v-model="code" :length="4" />
<!-- 8位验证码 -->
<InputOtp v-model="code" :length="8" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const code = ref("");
</script>
自定义样式
<template>
<InputOtp v-model="code" item-class="custom-item" active-item-class="custom-active" gap="15px" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const code = ref("");
</script>
<style scoped>
.custom-item {
width: 50px;
height: 60px;
border: 2px solid #ddd;
border-radius: 12px;
font-size: 24px;
}
.custom-active {
border-color: #67c23a;
box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.2);
}
</style>
监听完成事件
<template>
<InputOtp v-model="code" @complete="handleComplete" />
</template>
<script setup lang="ts">
import { ref } from "vue";
const code = ref("");
const handleComplete = (value: string) => {
console.log("验证码输入完成:", value);
// 在这里执行验证逻辑
};
</script>
Props
| 属性 |
说明 |
类型 |
默认值 |
| length |
输入框数量 |
number |
6 |
| itemClass |
每个输入项的自定义类名 |
string |
'' |
| activeItemClass |
激活状态输入项的自定义类名 |
string |
'' |
| gap |
输入项之间的间距 |
string |
'10px' |
| hasFilledItemClass |
具有内容的输入项的类名[active前面选项的类名] |
string |
'active' |
Events
| 事件名 |
说明 |
回调参数 |
| complete |
输入完成时触发(达到指定长度) |
(value: string) |
Popup - 弹出层组件
一个灵活的弹出层组件,支持从不同方向滑入,带有遮罩层和过渡动画效果。
基础用法
<template>
<Button @click="show = true">打开弹窗</Button>
<Popup v-model="show">
<div class="popup-content">
<h3>标题</h3>
<p>这是弹窗内容</p>
<Button @click="show = false">关闭</Button>
</div>
</Popup>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Button, Popup } from "@a-drowned-fish/rox-v";
const show = ref(false);
</script>
<style scoped>
.popup-content {
padding: 20px;
background: white;
border-radius: 12px 12px 0 0;
}
</style>
不同位置
<template>
<!-- 底部弹出(默认) -->
<Popup v-model="showBottom" position="bottom">
<div class="content">底部弹窗</div>
</Popup>
<!-- 顶部弹出 -->
<Popup v-model="showTop" position="top">
<div class="content">顶部弹窗</div>
</Popup>
<!-- 左侧弹出 -->
<Popup v-model="showLeft" position="left">
<div class="content">左侧弹窗</div>
</Popup>
<!-- 右侧弹出 -->
<Popup v-model="showRight" position="right">
<div class="content">右侧弹窗</div>
</Popup>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Popup } from "@a-drowned-fish/rox-v";
const showBottom = ref(false);
const showTop = ref(false);
const showLeft = ref(false);
const showRight = ref(false);
</script>
自定义遮罩层
<template>
<Popup v-model="show" :bg="'rgba(0, 0, 0, 0.7)'" :duration="500" :mask-closable="false">
<div class="content">
<p>点击遮罩层不会关闭</p>
<Button @click="show = false">手动关闭</Button>
</div>
</Popup>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Popup, Button } from "@a-drowned-fish/rox-v";
const show = ref(false);
</script>
自定义挂载节点
<template>
<div id="custom-container">
<Popup v-model="show" to="#custom-container">
<div class="content">挂载到指定容器</div>
</Popup>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Popup } from "@a-drowned-fish/rox-v";
const show = ref(false);
</script>
Props
| 属性 |
说明 |
类型 |
默认值 |
| position |
弹出位置 |
'top' | 'bottom' | 'left' | 'right' |
'bottom' |
| bg |
遮罩层背景色 |
string |
'rgba(0, 0, 0, .5)' |
| duration |
动画持续时间(毫秒) |
number |
300 |
| maskClosable |
点击遮罩层是否关闭 |
boolean |
true |
| to |
teleport 的目标节点 |
string |
'body' |
| top |
弹窗顶部距离父元素顶部的距离 |
string |
'0px' |
| left |
弹窗左侧距离父元素左侧的距离 |
string |
'0px' |
| right |
弹窗右侧距离父元素右侧的距离 |
string |
'0px' |
| bottom |
弹窗底部距离父元素底部的距离 |
string |
'0px' |
| zIndex |
弹窗的 z-index 值 |
number |
50 |
Tab - 标签页组件
一个支持左右滚动的标签页组件,带有底部激活线动画效果,支持自定义样式和插槽。
基础用法
<template>
<Tab v-model="activeIndex" :items="tabs">
<template v-slot="{ item, active }">
<span :class="{ active }">{{ item.label }}</span>
</template>
<template v-slot:panel="{ item }">
<div>{{ item.content }}</div>
</template>
</Tab>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@a-drowned-fish/rox-v";
const activeIndex = ref(0);
const tabs = [
{ label: "标签1", content: "内容1" },
{ label: "标签2", content: "内容2" },
{ label: "标签3", content: "内容3" },
];
</script>
<style scoped>
.active {
color: #409eff;
font-weight: bold;
}
</style>
自定义间距
<template>
<Tab v-model="activeIndex" :items="tabs" gap="20px">
<template #default="{ item }">{{ item.label }}</template>
<template #panel="{ item }">{{ item.content }}</template>
</Tab>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@a-drowned-fish/rox-v";
const activeIndex = ref(0);
const tabs = [
{ label: "标签1", content: "内容1" },
{ label: "标签2", content: "内容2" },
];
</script>
自定义样式
<template>
<Tab
v-model="activeIndex"
:items="tabs"
tab-container-class="custom-tab-container"
tab-item-class="custom-tab-item"
panel-container-class="custom-panel-container"
panel-item-class="custom-panel-item"
active-line-class="custom-active-line"
>
<template #default="{ item, active }">
<span>{{ item.label }}</span>
</template>
<template #panel="{ item }">
<div>{{ item.content }}</div>
</template>
</Tab>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Tab } from "@a-drowned-fish/rox-v";
const activeIndex = ref(0);
const tabs = [
{ label: "标签1", content: "内容1" },
{ label: "标签2", content: "内容2" },
];
</script>
<style scoped>
.custom-tab-item {
padding: 10px 15px;
border-radius: 8px 8px 0 0;
}
.custom-tab-item:hover {
background-color: #f5f7fa;
}
.custom-active-line {
background-color: #409eff;
height: 3px;
}
.custom-panel-container {
border: 1px solid #ebeef5;
border-top: none;
padding: 20px;
}
</style>
Props
| 属性 |
说明 |
类型 |
默认值 |
| v-model |
当前激活的标签索引 |
number |
0 |
| items |
标签数据数组 |
T[] |
[] |
| gap |
标签之间的间距 |
string |
'10px' |
| tabContainerClass |
标签容器的自定义类名 |
string |
'' |
| tabItemClass |
标签项的自定义类名 |
string |
'' |
| topPanelContainerClass |
tab上方容器的自定义类名 |
string |
'' |
| topPanelItemClass |
tab上方容器内每一项的自定义类名 |
string |
'' |
| panelContainerClass |
面板容器的自定义类名 |
string |
'' |
| panelItemClass |
面板项的自定义类名 |
string |
'' |
| activeLineClass |
激活底线的自定义类名 |
string |
'' |
| trigger |
触发方式 |
'click' | 'hover' |
'click' |
| dir |
布局方向 |
'ltr' | 'rtl' |
undefined. 布局方向;ltr 为从左到右,rtl 为从右到左 |
| animate |
是否启用动画 |
boolean |
true slide 效果 |
Slots
| 插槽名 |
说明 |
参数 |
| default |
标签项的自定义内容 |
{ item, index, active } |
| panel |
面板内容的自定义模板 |
{ item, index, active } |
| middle |
介于 default 和 panel中间的内容自定义模板 |
|
CSS 变量
| 变量名 |
说明 |
默认值 |
| --transition-duration |
底线动画持续时间 |
0.15s |
类型定义
interface RoxVTabProps<T extends Record<string, any>> {
items: T[]; // 标签数据数组
gap?: string; // 标签间距
tabContainerClass?: string; // 标签容器类名
tabItemClass?: string; // 标签项类名
panelContainerClass?: string; // 面板容器类名
panelItemClass?: string; // 面板项类名
activeLineClass?: string; // 激活底线类名
}
Menu - 下拉菜单组件
一个支持多级嵌套的下拉菜单组件,支持点击和悬停触发方式,带有过渡动画效果。
基础用法
<template>
<Menu :items="menuItems" v-model="selectedValue">
<span>点击打开菜单</span>
</Menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Menu } from "@a-drowned-fish/rox-v";
const selectedValue = ref<(string | number)[]>([]);
const menuItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
{ label: "选项3", value: "option3" },
];
</script>
多级菜单
<template>
<Menu :items="menuItems" v-model="selectedValue">
<span>多级菜单</span>
</Menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Menu } from "@a-drowned-fish/rox-v";
const selectedValue = ref<(string | number)[]>([]);
const menuItems = [
{
label: "文件",
value: "file",
children: [
{ label: "新建", value: "new" },
{ label: "打开", value: "open" },
{ label: "保存", value: "save" },
],
},
{
label: "编辑",
value: "edit",
children: [
{ label: "撤销", value: "undo" },
{ label: "重做", value: "redo" },
{
label: "查找",
value: "find",
children: [
{ label: "查找文本", value: "find-text" },
{ label: "替换", value: "replace" },
],
},
],
},
];
</script>
自定义图标
<template>
<Menu
:items="menuItems"
suffix-icon="/icons/arrow-right.svg"
checked-icon="/icons/check.svg"
v-model="selectedValue"
>
<span>带图标的菜单</span>
</Menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Menu } from "@a-drowned-fish/rox-v";
const selectedValue = ref<(string | number)[]>([]);
const menuItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
{
label: "子菜单",
value: "submenu",
children: [{ label: "子选项", value: "sub-option" }],
},
];
</script>
监听事件
<template>
<Menu :items="menuItems" v-model="selectedValue" @open="handleOpen" @close="handleClose">
<span>监听事件</span>
</Menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Menu } from "@a-drowned-fish/rox-v";
const selectedValue = ref<(string | number)[]>([]);
const menuItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
];
const handleOpen = () => {
console.log("菜单已打开");
};
const handleClose = () => {
console.log("菜单已关闭");
};
</script>
自定义样式
<template>
<Menu
:items="menuItems"
v-model="selectedValue"
item-gap="8px"
active-item-class="custom-active"
list-container-class="custom-list"
default-container-class="custom-trigger"
>
<span>自定义样式</span>
</Menu>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Menu } from "@a-drowned-fish/rox-v";
const selectedValue = ref<(string | number)[]>([]);
const menuItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
];
</script>
<style scoped>
.custom-trigger {
background-color: #409eff;
color: white;
border-radius: 4px;
}
.custom-list {
border-radius: 8px;
}
.custom-active {
background-color: #ecf5ff;
color: #409eff;
}
</style>
Props
| 属性 |
说明 |
类型 |
默认值 |
| items |
菜单项数据 |
RoxVMenuOptionProps[] |
[] |
| duration |
过渡动画持续时间(毫秒) |
number |
100 |
| suffixIcon |
子菜单后缀图标 URL |
string |
'' |
| suffixIconClass |
后缀图标的自定义类名 |
string |
'' |
| checkedIcon |
选中项的图标 URL |
string |
'' |
| checkedIconClass |
选中标记图标的自定义类名 |
string |
'' |
| listContainerClass |
菜单列表容器的自定义类名 |
string |
'' |
| subMenuContainerClass |
子菜单容器的自定义类名 |
string |
'' |
| defaultContainerClass |
触发区域容器的自定义类名 |
string |
'' |
| itemContainerClass |
菜单项的自定义类名 |
string |
'' |
| itemGap |
菜单项之间的间距 |
string |
'4px' |
| activeItemClass |
激活菜单项的自定义类名 |
string |
'' |
| v-model |
当前选中的菜单项路径 |
(string | number)[] |
[] |
Events
| 事件名 |
说明 |
回调参数 |
| open |
菜单打开时 |
无 |
| close |
菜单关闭时 |
无 |
Slots
| 插槽名 |
说明 |
| default |
触发区域的自定义内容 |
类型定义
interface RoxVMenuOptionProps {
label: string; // 菜单项显示文本
value: string | number; // 菜单项唯一标识
children?: RoxVMenuOptionProps[]; // 子菜单项(可选)
}
Expose
| 方法名 |
说明 |
返回值类型 |
| getSelectedItem |
获取当前选中项的完整数据对象 |
{ label: string, value: string | number } | undefined |
Toast - 消息提示组件
一个轻量级的消息提示组件,支持多种类型的提示消息,可自定义显示时长和样式。
基础用法
<template>
<Button @click="showToast">显示提示</Button>
<Toaster />
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { toast } = useToast();
const showToast = () => {
toast({
message: "这是一条提示消息",
type: "info",
duration: 3000,
});
};
</script>
不同类型的提示
<template>
<Button @click="showSuccess">成功</Button>
<Button @click="showError">错误</Button>
<Button @click="showInfo">信息</Button>
<Button @click="showWarning">警告</Button>
<Toaster />
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { success, error, info, warning } = useToast();
const showSuccess = () => success("操作成功");
const showError = () => error("操作失败");
const showInfo = () => info("这是一条信息");
const showWarning = () => warning("这是一条警告");
</script>
自定义显示时长
<template>
<Button @click="showToast">显示5秒</Button>
<Toaster />
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { success } = useToast();
const showToast = () => {
success("这条消息将显示5秒", 5000);
};
</script>
手动关闭提示
<template>
<Button @click="showToast">显示提示</Button>
<Button @click="dismissAll">关闭所有</Button>
<Toaster />
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { toast, dismiss } = useToast();
let toastId: string;
const showToast = () => {
toastId = toast({
message: "这条消息需要手动关闭",
type: "info",
duration: 10000, // 设置较长的显示时间
});
};
const dismissAll = () => {
dismiss(); // 关闭所有提示
// 或 dismiss(toastId) 关闭指定提示
};
</script>
自定义图标
<template>
<Button @click="showToast">显示提示</Button>
<Toaster>
<template #success-icon>
<svg class="icon" viewBox="0 0 24 24">
<path
fill="#67c23a"
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
/>
</svg>
</template>
<template #error-icon>
<svg class="icon" viewBox="0 0 24 24">
<path
fill="#f56c6c"
d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"
/>
</svg>
</template>
<template #info-icon>
<svg class="icon" viewBox="0 0 24 24">
<path
fill="#909399"
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</template>
<template #warning-icon>
<svg class="icon" viewBox="0 0 24 24">
<path fill="#e6a23c" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
</svg>
</template>
</Toaster>
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { success, error, info, warning } = useToast();
const showToast = () => {
success("成功提示");
error("错误提示");
info("信息提示");
warning("警告提示");
};
</script>
<style scoped>
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
</style>
自定义样式
<template>
<Button @click="showToast">显示提示</Button>
<Toaster title-class="custom-title" message-class="custom-message" />
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { success } = useToast();
const showToast = () => {
success("自定义样式的提示消息");
};
</script>
<style scoped>
:deep(.custom-title) {
justify-content: flex-start;
padding: 12px 16px;
}
:deep(.custom-message) {
font-size: 14px;
line-height: 1.5;
}
</style>
自定义挂载节点
<template>
<div id="toast-container">
<Button @click="showToast">显示提示</Button>
<Toaster to="#toast-container" />
</div>
</template>
<script setup lang="ts">
import { useToast } from "@a-drowned-fish/rox-v";
import { Toaster } from "@a-drowned-fish/rox-v";
const { success } = useToast();
const showToast = () => {
success("提示消息将显示在指定容器中");
};
</script>
Toaster Props
| 属性 |
说明 |
类型 |
默认值 |
| to |
teleport 的目标节点 |
string |
'body' |
| bg |
提示背景颜色 |
string |
'#303133' |
| titleClass |
标题容器的自定义类名 |
string |
'' |
| messageClass |
消息内容的自定义类名 |
string |
'' |
| toastClass |
提示容器的自定义类名 |
string |
'' |
useToast 方法
| 方法名 |
说明 |
参数类型 |
返回值 |
| toast |
创建一个自定义提示 |
ToastOptions |
string |
| success |
创建成功类型提示 |
(msg, dur?) |
string |
| error |
创建错误类型提示 |
(msg, dur?) |
string |
| info |
创建信息类型提示 |
(msg, dur?) |
string |
| warning |
创建警告类型提示 |
(msg, dur?) |
string |
| dismiss |
关闭提示(可指定ID) |
(id?: string) |
void |
ToastOptions 类型定义
interface ToastOptions {
message?: string; // 提示消息内容
duration?: number; // 显示时长(毫秒),默认 3000
type?: ToastType; // 提示类型:'success' | 'error' | 'info' | 'warning'
}
Toaster Slots
| 插槽名 |
说明 |
| success-icon |
成功提示的自定义图标 |
| error-icon |
错误提示的自定义图标 |
| info-icon |
信息提示的自定义图标 |
| warning-icon |
警告提示的自定义图标 |
SliderCaptcha - 滑块验证码组件
一个用于验证用户身份的滑块验证码组件,支持自定义背景图和滑块图,提供实时位置追踪功能。
基础用法
<template>
<SliderCaptcha
:background="bgImage"
:block="blockImage"
:width="300"
:block-top="80"
@success="onSuccess"
@fail="onFail"
@change="onChange"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { SliderCaptcha } from "@a-drowned-fish/rox-v";
const bgImage = ref("https://example.com/bg.jpg");
const blockImage = ref("https://example.com/block.png");
function onSuccess() {
console.log("验证成功");
}
function onFail() {
console.log("验证失败");
}
function onChange(tracks) {
console.log("滑块位置变化:", tracks);
}
</script>
带验证函数的用法
<template>
<SliderCaptcha
:background="bgImage"
:block="blockImage"
:width="400"
:verify="verifyCaptcha"
@success="onSuccess"
@fail="onFail"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { SliderCaptcha } from "@a-drowned-fish/rox-v";
const bgImage = ref("https://example.com/bg.jpg");
const blockImage = ref("https://example.com/block.png");
async function verifyCaptcha(position) {
// 发送验证请求到后端
const response = await fetch("/api/verify-captcha", {
method: "POST",
body: JSON.stringify(position),
});
return response.ok;
}
function onSuccess() {
console.log("验证成功");
}
function onFail() {
console.log("验证失败");
}
</script>
SliderCaptcha Props
| 属性 |
说明 |
类型 |
默认值 |
| background |
背景图地址 |
string |
- |
| block |
滑块缺口图地址 |
string |
- |
| width |
容器宽度(像素) |
number |
- |
| blockTop |
滑块图距离顶部的距离(原图像素) |
number |
0 |
| verify |
验证函数,返回 boolean 或者 undefined 或者 null 或者 void 或者 Promise 或者 Promise 或者 Promise 或者 Promise |
(option: RoxVSliderCaptchaTrackItem) => boolean | undefined | null | void | Promise<boolean> | Promise<undefined> | Promise<null> | Promise<void> |
- |
| trackBlockBg |
滑块轨道背景颜色 |
string |
'#f5f5f5' |
| trackBg |
滑块轨道进度条背景颜色 |
string |
'#c5c5c5' |
- 注:
- 验证函数返回true时,代表验证成功,返回false时,代表验证失败。 会触发对应的 success 、 fail 事件
- 验证函数返回undefined、null、void时,不会触发事件
SliderCaptcha Events
| 事件名 |
说明 |
参数类型 |
| success |
验证成功时触发 |
- |
| fail |
验证失败时触发 |
- |
| change |
滑块移动时实时触发 |
RoxVSliderCaptchaTrackItem[] |
RoxVSliderCaptchaTrackItem 类型定义
interface RoxVSliderCaptchaTrackItem {
x: number; // 缺口图在原图上的X坐标
t: number; // 时间戳
xPercent: number; // X坐标占原图宽度的百分比
}
SliderCaptcha Slots
| 插槽名 |
说明 |
| default |
自定义滑块内容 |
| verify-success |
自定义验证成功提示 |
| verify-fail |
自定义验证失败提示 |
SliderCaptcha Expose
| 属性/方法 |
说明 |
类型 |
| reset |
重置滑块位置、验证结果 |
() => void |
| tracks |
滑块移动轨迹数组 |
RoxVSliderCaptchaTrackItem[] |
CountDown - 倒计时组件
一个基于剩余秒数的倒计时组件,支持自定义显示格式和初始时间。
Props
| 属性 |
说明 |
类型 |
默认值 |
| v-model |
当前剩余秒数 |
number | undefined |
undefined |
注: 当v-model为number时,倒计时会从该值开始倒计时,倒计时结束时,v-model会自动设置为0。 但是过程中,v-model绑定的数据不会变化
示例:OTP 倒计时
<template>
<div>
<CountDown v-model="remain">
<template #initial>发送验证码</template>
<template #default="{ seconds }">
<span>{{ seconds > 0 ? `剩余${seconds}秒` : "重新发送" }}</span>
</template>
</CountDown>
<Button @click="remain = 9">开始倒计时</Button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { CountDown, Button } from "@a-drowned-fish/rox-v";
const remain = ref<undefined | number>(undefined);
</script>
Select - 下拉选择组件
一个支持多级嵌套的下拉选择组件,支持悬停展开子菜单,带有过渡效果。
基础用法
<template>
<Select :items="selectItems" v-model="selectedValues">
<template #default="{ selected }">
<span>{{ selected?.label || "请选择" }}</span>
</template>
<template #item="{ item, active }">
<span :class="{ active }">{{ item.label }}</span>
</template>
</Select>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Select } from "@a-drowned-fish/rox-v";
const selectedValues = ref<(string | number)[]>([]);
const selectItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
{ label: "选项3", value: "option3" },
];
</script>
<style scoped>
.active {
color: #409eff;
font-weight: bold;
}
</style>
多级选择
<template>
<Select :items="selectItems" v-model="selectedValues">
<template #default="{ selected }">
<span>{{ selected?.label || "请选择" }}</span>
</template>
<template #item="{ item, active }">
<span :class="{ active }">{{ item.label }}</span>
<span v-if="item.children" class="arrow">›</span>
</template>
</Select>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Select } from "@a-drowned-fish/rox-v";
const selectedValues = ref<(string | number)[]>([]);
const selectItems = [
{
label: "菜单1",
value: "menu1",
children: [
{ label: "子选项1-1", value: "sub1-1" },
{ label: "子选项1-2", value: "sub1-2" },
],
},
{
label: "菜单2",
value: "menu2",
children: [
{
label: "子菜单2-1",
value: "sub2-1",
children: [
{ label: "孙选项2-1-1", value: "grand2-1-1" },
{ label: "孙选项2-1-2", value: "grand2-1-2" },
],
},
{ label: "子选项2-2", value: "sub2-2" },
],
},
];
</script>
<style scoped>
.arrow {
float: right;
color: #999;
}
</style>
自定义样式
<template>
<Select :items="selectItems" v-model="selectedValues" item-gap="8px" list-container-class-name="custom-list">
<template #default="{ selected }">
<span class="custom-trigger">{{ selected?.label || "请选择" }}</span>
</template>
<template #item="{ item, active }">
<span :class="{ active }">{{ item.label }}</span>
</template>
</Select>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Select } from "@a-drowned-fish/rox-v";
const selectedValues = ref<(string | number)[]>([]);
const selectItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
];
</script>
<style scoped>
.custom-trigger {
padding: 8px 16px;
background: #f5f7fa;
border-radius: 4px;
}
.custom-list {
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
</style>
Props
| 属性 |
说明 |
类型 |
默认值 |
| items |
选择项数据数组 |
RoxVSelectOptionProps[] |
[] |
| itemGap |
菜单项之间的间距 |
string |
'4px' |
| listContainerClassName |
列表容器的自定义类名 |
string |
'' |
| v-model |
当前选中的菜单项路径 |
(string | number)[] |
[] |
Events
| 事件名 |
说明 |
回调参数 |
| open |
菜单打开时 |
无 |
| close |
菜单关闭时 |
无 |
Slots
| 插槽名 |
说明 |
参数 |
| default |
触发区域的自定义内容 |
{ selected } - 选中项数据,包含 label 和 value |
| item |
选项项的自定义内容 |
{ item, index, active } |
Expose
| 方法名 |
说明 |
返回值类型 |
| getSelectedItem |
获取当前选中项的完整数据对象 |
{ label: string, value: string | number } | undefined |
手动获取选中项
<template>
<Select ref="selectRef" :items="selectItems" v-model="selectedValues">
<template #default="{ selected }">
<span>{{ selected?.label || "请选择" }}</span>
</template>
<template #item="{ item, active }">
<span :class="{ active }">{{ item.label }}</span>
</template>
</Select>
<Button @click="handleGetSelected">获取选中项</Button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Select, Button } from "@a-drowned-fish/rox-v";
const selectRef = ref();
const selectedValues = ref<(string | number)[]>([]);
const selectItems = [
{ label: "选项1", value: "option1" },
{ label: "选项2", value: "option2" },
];
const handleGetSelected = () => {
const item = selectRef.value?.getSelectedItem();
if (item) {
console.log("选中项:", item.label, item.value);
} else {
console.log("未选中任何项");
}
};
</script>
类型定义
interface RoxVSelectOptionProps {
label: string; // 显示文本
value: string | number; // 唯一标识
children?: RoxVSelectOptionProps[]; // 子选项(可选)
[key: string]: any; // 其他自定义属性
}
Panel - 面板切换组件
一个支持横向滑动切换的面板组件,支持 RTL 布局方向,常用于步骤指示器等场景。
基础用法
<template>
<Panel :items="panels" v-model="activeIndex">
<template #default="{ item, index, active }">
<div :class="['panel-content', { active }]">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</div>
</template>
</Panel>
<Button @click="activeIndex++" :disabled="activeIndex >= panels.length - 1"> 下一页 </Button>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Panel, Button } from "@a-drowned-fish/rox-v";
const activeIndex = ref(0);
const panels = [
{ title: "步骤1", description: "第一步的内容描述" },
{ title: "步骤2", description: "第二步的内容描述" },
{ title: "步骤3", description: "第三步的内容描述" },
];
</script>
<style scoped>
.panel-content {
padding: 20px;
min-height: 200px;
}
.panel-content.active {
background: #f5f7fa;
}
</style>
RTL 布局
<template>
<Panel :items="panels" v-model="activeIndex" dir="rtl">
<template #default="{ item }">
<div>{{ item.title }}</div>
</template>
</Panel>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Panel } from "@a-drowned-fish/rox-v";
const activeIndex = ref(0);
const panels = [{ title: "第一步" }, { title: "第二步" }, { title: "第三步" }];
</script>
Props
| 属性 |
说明 |
类型 |
默认值 |
| items |
面板数据数组 |
T[] |
[] |
| dir |
布局方向 |
'ltr' | 'rtl' | 'auto' |
undefined |
| v-model |
当前索引 |
number |
0 |
Slots
| 插槽名 |
说明 |
参数 |
| default |
面板内容自定义模板 |
{ item, index, active } |
License
MIT