allinssl/frontend/packages/vue/naive-ui/README.md

958 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Dynamic Components
基于 Vue 3 和 Naive UI 的动态表单和表格组件库,提供灵活的配置式 UI 开发方案。
## 目录
- [安装](#安装)
- [快速开始](#快速开始)
- [核心功能](#核心功能)
- [组件和 Hooks](#组件和-hooks)
- [useForm](#useform)
- [useTable](#usetable)
- [useTabs](#usetabs)
- [FormDesigner](#formdesigner)
- [FormBuilder](#formbuilder)
## 安装
```bash
# 使用 npm
npm install dynamic-components
# 使用 yarn
yarn add dynamic-components
# 使用 pnpm
pnpm add dynamic-components
```
## 快速开始
### 基础依赖
本组件库基于 Vue 3 和 Naive UI 构建,请确保您的项目中已安装以下依赖:
```json
{
"dependencies": {
"@vicons/tabler": "^0.13.0",
"@vueuse/core": "^10.7.0",
"naive-ui": "^2.37.3",
"vue": "^3.4.0"
}
}
```
### 基本用法
```vue
<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用于创建动态表单。
#### 基本用法
```js
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<any> | 否 | 表单提交请求函数 |
| immediate | boolean | 否 | 是否立即执行请求,默认为 false |
#### 返回值
| 属性 | 类型 | 说明 |
| ------------- | ------------------------ | ------------ |
| loading | Ref<boolean> | 加载状态 |
| formData | Ref<Record<string, any>> | 表单数据 |
| formRef | Ref<FormInst \| null> | 表单实例引用 |
| submit | () => Promise<any> | 提交方法 |
| reset | () => void | 重置方法 |
| validate | () => Promise<boolean> | 验证方法 |
| 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 | 自定义渲染 | - |
#### 栅格布局
```js
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',
},
],
},
],
},
]
```
#### 自定义插槽
```vue
<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。
#### 基本用法
```js
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<TableResponse> | 是 | 数据请求函数 |
| defaultParams | Ref<TableRequestParams> | 否 | 默认请求参数 |
| immediate | boolean | 否 | 是否立即执行请求,默认为 true |
#### 返回值
| 属性 | 类型 | 说明 |
| -------------- | ------------------- | ------------ |
| loading | Ref<boolean> | 加载状态 |
| data | Ref<T[]> | 表格数据 |
| params | TableRequestParams | 查询参数 |
| refresh | () => Promise<void> | 刷新方法 |
| reset | () => Promise<void> | 重置方法 |
| tableRef | Ref<any> | 表格引用 |
| TableComponent | () => JSX.Element | 表格渲染组件 |
### useTabs
`useTabs` 是一个用于管理标签页的 Hook特别适合基于路由的多标签页应用。
#### 基本用法
```vue
<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` 是一个可视化表单设计器组件,支持拖拽设计表单。
#### 基本用法
```vue
<template>
<FormDesigner />
</template>
<script setup>
import { FormDesigner } from '@baota/naive-ui/hooks'
</script>
```
#### 功能特点
- 支持拖拽添加表单组件
- 支持编辑组件属性
- 支持导入/导出表单配置
- 实时预览表单效果
### FormBuilder
`FormBuilder` 是一个简化版的表单构建器组件,适合快速创建简单表单。
#### 基本用法
```vue
<template>
<FormBuilder />
</template>
<script setup>
import { FormBuilder } from '@baota/naive-ui/hooks'
</script>
```
#### 功能特点
- 拖拽式表单构建
- 支持导出表单配置
- 简单直观的操作界面
## 类型定义
组件库提供了完整的 TypeScript 类型定义,可以在开发时获得良好的类型提示。
```ts
import type {
FormConfig,
FormItemConfig,
FormElement,
UseFormOptions,
FormInstance,
TableRequestParams,
TableResponse,
UseTableOptions,
TableInstance,
} from '@baota/naive-ui/hooks'
```
## 示例
### 完整的表单示例
```vue
<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>
```
### 完整的表格示例
```vue
<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 来帮助改进这个组件库。
## 许可证
[ISC](LICENSE)
# @baota/naive-ui
基于 Naive UI 的扩展库,提供一系列增强功能和实用工具。
## 主要功能
- 统一的主题管理
- 扩展的 hooks 工具集
- 支持组件内和组件外使用的 API
## 安装
```bash
# npm
npm install @baota/naive-ui
# yarn
yarn add @baota/naive-ui
# pnpm
pnpm add @baota/naive-ui
```
## Message 消息提示
### 组件内使用 useMessage
在 Vue 组件内,可以直接使用 `useMessage` hook
```tsx
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 实例:
```ts
// 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 方法:
```ts
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()
```
## 自定义主题
可以通过传入配置来自定义主题:
```ts
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 响应结果:
```ts
// API响应格式
interface ApiResponse {
status: boolean
message: string
}
// 自动根据status显示成功或失败消息
message.request(apiResponse)
```