mirror of https://github.com/allinssl/allinssl
![]() |
||
---|---|---|
.. | ||
docs | ||
src | ||
types | ||
README.md | ||
eslint.config.js | ||
index.html | ||
package.json | ||
postcss.config.js | ||
prettier.config.js | ||
tailwind.config.js | ||
tsconfig.json | ||
tsconfig.node.json | ||
vite.config.ts |
README.md
Dynamic Components
基于 Vue 3 和 Naive UI 的动态表单和表格组件库,提供灵活的配置式 UI 开发方案。
目录
安装
# 使用 npm
npm install dynamic-components
# 使用 yarn
yarn add dynamic-components
# 使用 pnpm
pnpm add dynamic-components
快速开始
基础依赖
本组件库基于 Vue 3 和 Naive UI 构建,请确保您的项目中已安装以下依赖:
{
"dependencies": {
"@vicons/tabler": "^0.13.0",
"@vueuse/core": "^10.7.0",
"naive-ui": "^2.37.3",
"vue": "^3.4.0"
}
}
基本用法
<template>
<div>
<component :is="form.FormComponent" />
<n-button @click="handleSubmit">提交</n-button>
</div>
</template>
<script setup>
import { useForm } from '@baota/naive-ui/hooks'
import { NButton } from 'naive-ui'
// 定义表单配置
const formConfig = [
{
type: 'formItem',
label: '用户名',
children: [
{
type: 'input',
field: 'username',
placeholder: '请输入用户名',
},
],
},
{
type: 'formItem',
label: '密码',
children: [
{
type: 'input',
field: 'password',
type: 'password',
placeholder: '请输入密码',
},
],
},
]
// 创建表单实例
const form = useForm({
config: formConfig,
defaultValues: {
username: '',
password: '',
},
})
// 提交表单
const handleSubmit = async () => {
const valid = await form.validate()
if (valid) {
console.log('表单数据:', form.formData.value)
}
}
</script>
核心功能
- 配置式表单:通过 JSON 配置快速构建复杂表单
- 动态表格:支持自定义列、排序、筛选等功能
- 表单设计器:可视化拖拽设计表单
- 标签页管理:简化多标签页应用开发
组件和 Hooks
useForm
useForm
是一个强大的表单 Hook,用于创建动态表单。
基本用法
import { useForm } from '@baota/naive-ui/hooks'
const form = useForm({
config: [
{
type: 'formItem',
label: '姓名',
children: [
{
type: 'input',
field: 'name',
},
],
},
],
defaultValues: {
name: '',
},
requestFn: async (data) => {
// 提交表单数据的请求函数
return await api.submitForm(data)
},
})
参数说明
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
config | FormConfig | 是 | 表单配置 |
defaultValues | Record<string, any> | 否 | 表单默认值 |
requestFn | (data: any) => Promise | 否 | 表单提交请求函数 |
immediate | boolean | 否 | 是否立即执行请求,默认为 false |
返回值
属性 | 类型 | 说明 |
---|---|---|
loading | Ref | 加载状态 |
formData | Ref<Record<string, any>> | 表单数据 |
formRef | Ref<FormInst | null> | 表单实例引用 |
submit | () => Promise | 提交方法 |
reset | () => void | 重置方法 |
validate | () => Promise | 验证方法 |
FormComponent | () => JSX.Element | 表单渲染组件 |
支持的表单元素
类型 | 说明 | 对应 Naive UI 组件 |
---|---|---|
input | 输入框 | NInput |
inputNumber | 数字输入框 | NInputNumber |
inputGroup | 输入框组 | NInputGroup |
select | 选择器 | NSelect |
radio | 单选框组 | NRadioGroup |
radioButton | 单选按钮 | NRadioButton |
checkbox | 复选框组 | NCheckboxGroup |
switch | 开关 | NSwitch |
datepicker | 日期选择器 | NDatePicker |
timepicker | 时间选择器 | NTimePicker |
colorPicker | 颜色选择器 | NColorPicker |
slider | 滑块 | NSlider |
rate | 评分 | NRate |
transfer | 穿梭框 | NTransfer |
mention | 提及 | NMention |
dynamicInput | 动态输入 | NDynamicInput |
dynamicTags | 动态标签 | NDynamicTags |
autoComplete | 自动完成 | NAutoComplete |
cascader | 级联选择 | NCascader |
treeSelect | 树选择 | NTreeSelect |
upload | 上传 | NUpload |
uploadDragger | 拖拽上传 | NUploadDragger |
slot | 插槽 | - |
render | 自定义渲染 | - |
栅格布局
const formConfig = [
{
type: 'grid',
cols: 24,
xGap: 12,
children: [
{
type: 'formItemGi',
label: '姓名',
span: 12,
children: [
{
type: 'input',
field: 'firstName',
},
],
},
{
type: 'formItemGi',
label: '姓氏',
span: 12,
children: [
{
type: 'input',
field: 'lastName',
},
],
},
],
},
]
自定义插槽
<template>
<component :is="form.FormComponent">{{
customSlot,
}}</component>
</template>
<script setup>
import { useForm } from '@baota/naive-ui/hooks'
const formConfig = [
{
type: 'formItem',
label: '自定义内容',
children: [
{
type: 'slot',
slot: 'customSlot',
},
],
},
]
const formSlots = {
customSlot: (formData, formRef) => <div>这是自定义插槽内容</div>,
}
const form = useForm({
config: formConfig,
})
</script>
useTable
useTable
是一个用于创建动态表格的 Hook。
基本用法
import { useTable } from '@baota/naive-ui/hooks'
import { h } from 'vue'
// 定义表格列
const columns = [
{
title: 'ID',
key: 'id',
},
{
title: '姓名',
key: 'name',
},
{
title: '操作',
key: 'actions',
render: (row) =>
h('div', [
h('button', { onClick: () => handleEdit(row) }, '编辑'),
h('button', { onClick: () => handleDelete(row) }, '删除'),
]),
},
]
// 创建表格实例
const table = useTable({
columns,
requestFn: async (params) => {
// 获取表格数据的请求函数
const res = await api.getList(params)
return {
list: res.data.list,
}
},
defaultParams: {
page: 1,
pageSize: 10,
},
})
参数说明
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
columns | DataTableColumns | 是 | 表格列配置 |
requestFn | (params: TableRequestParams) => Promise | 是 | 数据请求函数 |
defaultParams | Ref | 否 | 默认请求参数 |
immediate | boolean | 否 | 是否立即执行请求,默认为 true |
返回值
属性 | 类型 | 说明 |
---|---|---|
loading | Ref | 加载状态 |
data | Ref<T[]> | 表格数据 |
params | TableRequestParams | 查询参数 |
refresh | () => Promise | 刷新方法 |
reset | () => Promise | 重置方法 |
tableRef | Ref | 表格引用 |
TableComponent | () => JSX.Element | 表格渲染组件 |
useTabs
useTabs
是一个用于管理标签页的 Hook,特别适合基于路由的多标签页应用。
基本用法
<template>
<component :is="tabs.TabsComponent" />
</template>
<script setup>
import { useTabs } from '@baota/naive-ui/hooks'
const tabs = useTabs({
defaultToFirst: true,
})
</script>
参数说明
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
defaultToFirst | boolean | 否 | 是否在初始化时自动选中第一个标签,默认为 true |
返回值
属性 | 类型 | 说明 |
---|---|---|
activeKey | string | 当前激活的标签值 |
childRoutes | RouteRecordRaw[] | 子路由列表 |
handleTabChange | (key: string) => void | 切换标签页方法 |
TabsComponent | () => JSX.Element | 标签页渲染组件 |
FormDesigner
FormDesigner
是一个可视化表单设计器组件,支持拖拽设计表单。
基本用法
<template>
<FormDesigner />
</template>
<script setup>
import { FormDesigner } from '@baota/naive-ui/hooks'
</script>
功能特点
- 支持拖拽添加表单组件
- 支持编辑组件属性
- 支持导入/导出表单配置
- 实时预览表单效果
FormBuilder
FormBuilder
是一个简化版的表单构建器组件,适合快速创建简单表单。
基本用法
<template>
<FormBuilder />
</template>
<script setup>
import { FormBuilder } from '@baota/naive-ui/hooks'
</script>
功能特点
- 拖拽式表单构建
- 支持导出表单配置
- 简单直观的操作界面
类型定义
组件库提供了完整的 TypeScript 类型定义,可以在开发时获得良好的类型提示。
import type {
FormConfig,
FormItemConfig,
FormElement,
UseFormOptions,
FormInstance,
TableRequestParams,
TableResponse,
UseTableOptions,
TableInstance,
} from '@baota/naive-ui/hooks'
示例
完整的表单示例
<template>
<div>
<component :is="form.FormComponent" />
<n-button type="primary" @click="handleSubmit" :loading="form.loading.value"> 提交 </n-button>
</div>
</template>
<script setup>
import { useForm } from '@baota/naive-ui/hooks'
import { NButton } from 'naive-ui'
import { ref } from 'vue'
// 表单配置
const formConfig = [
{
type: 'grid',
cols: 24,
xGap: 12,
children: [
{
type: 'formItemGi',
label: '姓名',
span: 12,
required: true,
children: [
{
type: 'input',
field: 'name',
placeholder: '请输入姓名',
},
],
},
{
type: 'formItemGi',
label: '年龄',
span: 12,
children: [
{
type: 'inputNumber',
field: 'age',
placeholder: '请输入年龄',
min: 0,
max: 120,
},
],
},
],
},
{
type: 'formItem',
label: '性别',
required: true,
children: [
{
type: 'radio',
field: 'gender',
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
{ label: '其他', value: 'other' },
],
},
],
},
{
type: 'formItem',
label: '兴趣爱好',
children: [
{
type: 'checkbox',
field: 'hobbies',
options: [
{ label: '阅读', value: 'reading' },
{ label: '音乐', value: 'music' },
{ label: '运动', value: 'sports' },
{ label: '旅行', value: 'travel' },
{ label: '编程', value: 'coding' },
],
},
],
},
{
type: 'formItem',
label: '出生日期',
children: [
{
type: 'datepicker',
field: 'birthday',
type: 'date',
},
],
},
{
type: 'formItem',
label: '个人简介',
children: [
{
type: 'input',
field: 'bio',
type: 'textarea',
placeholder: '请输入个人简介',
maxLength: 500,
showCount: true,
},
],
},
]
// 默认值
const defaultValues = ref({
name: '',
age: 18,
gender: 'male',
hobbies: [],
birthday: null,
bio: '',
})
// 创建表单实例
const form = useForm({
config: formConfig,
defaultValues,
requestFn: async (data) => {
// 模拟提交请求
return new Promise((resolve) => {
setTimeout(() => {
console.log('提交的数据:', data)
resolve({ success: true })
}, 1000)
})
},
})
// 提交表单
const handleSubmit = async () => {
const result = await form.submit()
if (result?.success) {
// 提交成功后的处理
console.log('提交成功')
}
}
</script>
完整的表格示例
<template>
<div>
<n-card title="用户列表">
<!-- 搜索表单 -->
<component :is="searchForm.FormComponent" />
<div class="mb-4">
<n-button type="primary" @click="handleSearch">搜索</n-button>
<n-button @click="searchForm.reset" class="ml-2">重置</n-button>
</div>
<!-- 表格 -->
<component :is="table.TableComponent" />
</n-card>
</div>
</template>
<script setup>
import { useForm, useTable } from '@baota/naive-ui/hooks'
import { NButton, NCard, useMessage } from 'naive-ui'
import { h, ref } from 'vue'
const message = useMessage()
// 搜索表单配置
const searchFormConfig = [
{
type: 'grid',
cols: 24,
xGap: 12,
children: [
{
type: 'formItemGi',
label: '用户名',
span: 8,
children: [
{
type: 'input',
field: 'username',
placeholder: '请输入用户名',
},
],
},
{
type: 'formItemGi',
label: '状态',
span: 8,
children: [
{
type: 'select',
field: 'status',
placeholder: '请选择状态',
options: [
{ label: '全部', value: '' },
{ label: '启用', value: 'active' },
{ label: '禁用', value: 'inactive' },
],
},
],
},
{
type: 'formItemGi',
label: '注册时间',
span: 8,
children: [
{
type: 'datepicker',
field: 'registerDate',
type: 'daterange',
clearable: true,
},
],
},
],
},
]
// 搜索表单实例
const searchForm = useForm({
config: searchFormConfig,
defaultValues: {
username: '',
status: '',
registerDate: null,
},
})
// 表格列配置
const columns = [
{
title: 'ID',
key: 'id',
sorter: true,
},
{
title: '用户名',
key: 'username',
},
{
title: '邮箱',
key: 'email',
},
{
title: '状态',
key: 'status',
render: (row) => {
const statusMap = {
active: { text: '启用', type: 'success' },
inactive: { text: '禁用', type: 'error' },
}
const status = statusMap[row.status] || { text: '未知', type: 'default' }
return h('n-tag', { type: status.type }, { default: () => status.text })
},
},
{
title: '注册时间',
key: 'registerDate',
sorter: true,
},
{
title: '操作',
key: 'actions',
render: (row) =>
h('div', [
h(
'n-button',
{
size: 'small',
type: 'primary',
onClick: () => handleEdit(row),
},
{ default: () => '编辑' },
),
h(
'n-button',
{
size: 'small',
type: 'error',
style: 'margin-left: 8px',
onClick: () => handleDelete(row),
},
{ default: () => '删除' },
),
]),
},
]
// 模拟请求函数
const mockRequestFn = async (params) => {
// 模拟网络请求
return new Promise((resolve) => {
setTimeout(() => {
// 模拟数据
const mockData = [
{ id: 1, username: 'user1', email: 'user1@example.com', status: 'active', registerDate: '2023-01-15' },
{ id: 2, username: 'user2', email: 'user2@example.com', status: 'inactive', registerDate: '2023-02-20' },
{ id: 3, username: 'user3', email: 'user3@example.com', status: 'active', registerDate: '2023-03-10' },
]
// 简单过滤
let filteredData = [...mockData]
if (params.username) {
filteredData = filteredData.filter((item) =>
item.username.toLowerCase().includes(params.username.toLowerCase()),
)
}
if (params.status) {
filteredData = filteredData.filter((item) => item.status === params.status)
}
resolve({
list: filteredData,
total: filteredData.length,
})
}, 500)
})
}
// 表格默认参数
const defaultParams = ref({
page: 1,
pageSize: 10,
})
// 创建表格实例
const table = useTable({
columns,
requestFn: mockRequestFn,
defaultParams,
pagination: {
pageSize: 10,
},
bordered: true,
})
// 搜索处理
const handleSearch = async () => {
// 合并搜索表单数据到表格参数
Object.assign(table.params, searchForm.formData.value)
await table.refresh()
}
// 编辑处理
const handleEdit = (row) => {
message.info(`编辑用户: ${row.username}`)
}
// 删除处理
const handleDelete = (row) => {
message.info(`删除用户: ${row.username}`)
}
</script>
贡献
欢迎提交 Issue 或 Pull Request 来帮助改进这个组件库。
许可证
@baota/naive-ui
基于 Naive UI 的扩展库,提供一系列增强功能和实用工具。
主要功能
- 统一的主题管理
- 扩展的 hooks 工具集
- 支持组件内和组件外使用的 API
安装
# npm
npm install @baota/naive-ui
# yarn
yarn add @baota/naive-ui
# pnpm
pnpm add @baota/naive-ui
Message 消息提示
组件内使用 useMessage
在 Vue 组件内,可以直接使用 useMessage
hook:
import { defineComponent } from 'vue'
import { useMessage } from '@baota/naive-ui/hooks'
export default defineComponent({
setup() {
const message = useMessage()
// 基本使用
const showMessage = () => {
message.success('操作成功')
message.error('操作失败')
message.warning('警告提示')
message.info('信息提示')
}
// 处理请求结果
const handleApiResponse = (response) => {
message.request(response) // 自动根据 status 判断显示成功或失败消息
}
return { showMessage, handleApiResponse }
},
})
非组件环境使用 createAllApi
在非组件环境(如工具函数、API 请求拦截器等)中,可以使用 createAllApi
创建全局可用的 API 实例:
// api/index.ts
import { createAllApi } from '@baota/naive-ui/hooks'
// 创建全局API实例
export const globalApi = createAllApi()
// 在任何地方使用
export function handleApiResponse(response) {
// 自动根据 status 判断显示成功或失败消息
globalApi.message.request(response)
}
// 也可以直接调用特定类型的消息
export function showSuccessMessage(content: string) {
globalApi.message.success(content)
}
整合 createDiscreteApi 的完整方案
createAllApi
整合了 Naive UI 的 createDiscreteApi
,包含了 message、notification、dialog 和 loadingBar 功能,并且扩展了 request 方法:
import { createAllApi } from '@baota/naive-ui/hooks'
// 创建全局API实例
const { message, notification, dialog, loadingBar } = createAllApi()
// 使用 message
message.success('操作成功')
message.error('操作失败')
message.request({ status: true, message: '请求成功' })
// 使用 dialog
dialog.info({
title: '提示',
content: '这是一个对话框',
})
dialog.request({ status: false, message: '操作失败' })
// 使用 notification
notification.success({
title: '成功',
content: '操作已完成',
})
// 使用 loadingBar
loadingBar.start()
// 操作完成后
loadingBar.finish()
自定义主题
可以通过传入配置来自定义主题:
import { createAllApi } from '@baota/naive-ui/hooks'
import { darkTheme } from 'naive-ui'
// 使用暗色主题
const api = createAllApi({
configProviderProps: {
theme: darkTheme,
},
})
常见问题
为什么需要同时支持 useMessage 和 createAllApi?
useMessage
适合在组件内使用,可以访问组件上下文createAllApi
适用于非组件环境,如工具函数、API 请求拦截器等
message.request 方法是什么?
这是我们扩展的便捷方法,用于统一处理 API 响应结果:
// API响应格式
interface ApiResponse {
status: boolean
message: string
}
// 自动根据status显示成功或失败消息
message.request(apiResponse)