version: v4.7.4

This commit is contained in:
XiaoDaiGua-Ray 2024-03-29 16:25:09 +08:00
parent 03628890cb
commit a0f7763778
47 changed files with 1648 additions and 234 deletions

View File

@ -25,6 +25,7 @@
"domtoimage",
"EDITMSG",
"iife",
"internalkey",
"linebreak",
"macarons",
"menutag",

View File

@ -1,5 +1,118 @@
# CHANGE LOG
## 4.7.4
对于 `RTable`, `RForm`, `RChart` 组件都新增了对应的 `useTable`, `useForm`, `useChart` 方法;让你在业务开发中抛弃注册 `ref` 实例方式调用组件方法。
补充拓展了 `useModal` 方法,支持 `dad`, `fullscreen` 等拓展配置项。
```ts
import { useTable, useForm } from '@/components'
const [registerTable, { getTableInstance }] = useTable()
const [registerForm, { getFormInstance }] = useForm()
// 做点什么...
```
## Feats
- 更新 `vite` 版本至 `5.2.6`
- `useDevice` 方法支持自定义 `media` 配置项,用于配置自定义媒体查询尺寸
- `RTable` 组件
- 新增 `tool` 配置项,配置是否显示工具栏
- 优化工具栏渲染逻辑
- 新增 `useTable` 方法,用于便捷调用表格方法
> 该方法比起常见的 `ref` 注册,然后 `tableRef.value.xxx` 的方法获取表格方法更为简洁一点。但是也值得注意的是,需要手动调用一次 `register` 方法,否则会报错;还有值得注意的是,需要注意表格方法的调用时机,需要等待表格注册完成后才能正常调用。如果需要在 `Parent Create` 阶段调用,可以尝试 `nextTick` 包裹一层。
```tsx
import { RTable } from '@/components'
import { useTable } from '@/components'
defineComponent({
setup() {
const [
register,
{ getTableInstance, clearFilters, clearSorter, scrollTo, filters, sort },
] = useTable()
const columns = [
{
title: 'No',
key: 'no',
},
{
title: 'Title',
key: 'title',
},
]
const data = [
{
no: 1,
title: 'title',
},
]
return {
register,
getTableInstance,
clearFilters,
clearSorter,
scrollTo,
filters,
sort,
columns,
data,
}
},
render() {
const { columns, data } = this
const { register } = this
return (
<RTable columns={columns} data={data} register={register.bind(this)} />
)
},
})
```
- `RForm` 组件
- 新增组件,所有行为、方法与 `NForm` 保持一致
- `useForm` 方法,使用方法与 `useTable` 几乎一致
- `canUseDom`, `isBrowser` 方法统一为函数导出
- `RModal` 组件新增 `useModal` 方法
- 新增 `useModal` 方法,允许拓展配置 `dad`, `fullscreen` 等配置项。但是由于 `useModal` 生成组件的特殊性,不支持 `memo` 属性配置,其余配置项维持一致
> 该方法在当前版本存在一个 bug`preset = card` 时,不能正确的显示 content查看相应的 [issues](https://github.com/tusen-ai/naive-ui/issues/5746)。
- 重写部分代码,优化组件逻辑,补全 `ts` 类型
- `RChart`
- 新增 `useChart` 方法,使用方法与 `useTable` 几乎一致
- 新增 `usePagination` 方法与 `usePagination.spec` 单元测试模块
```ts
import { usePagination } from '@/hooks'
const {
updatePage,
updatePageSize,
getItemCount,
setItemCount,
getPage,
setPage,
getPageSize,
setPageSize,
getPagination,
getCallback,
} = usePagination(
() => {
// do something...
},
{
// ...options
},
)
```
## 4.7.3
补全 `hooks` 包下的单测模块。

View File

@ -0,0 +1,71 @@
import { usePagination } from '../../src/hooks/web/usePagination'
describe('usePagination', () => {
let count = 0
const defaultOptions = {
itemCount: 200,
page: 1,
pageSize: 10,
}
const {
getItemCount,
getCallback,
getPage,
getPageSize,
getPagination,
setItemCount,
setPage,
setPageSize,
} = usePagination(() => {
count++
}, defaultOptions)
it('should get current itemCount', () => {
setItemCount(200)
expect(getItemCount()).toBe(200)
setItemCount(100)
expect(getItemCount()).toBe(100)
})
it('should get current page', () => {
setPage(1)
expect(getPage()).toBe(1)
})
it('should get current pageSize', () => {
setPageSize(10)
expect(getPageSize()).toBe(10)
})
it('should get current pagination', () => {
setItemCount(200)
expect(getPagination()).toMatchObject(defaultOptions)
})
it('should update count when page or pageSize changes', () => {
count = 0
setPage(2)
expect(count).toBe(1)
setPageSize(20)
expect(count).toBe(2)
})
it('should get callback', () => {
count = 0
getCallback()
expect(count).toBe(1)
})
})

View File

@ -12,4 +12,5 @@ const canUseDom = () => {
window.document.createElement
)
}
export default canUseDom

View File

@ -5,9 +5,11 @@
*
* true false
*/
const isBrowser = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
const isBrowser = () =>
!!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
export default isBrowser

View File

@ -1,7 +1,7 @@
{
"name": "ray-template",
"private": false,
"version": "4.7.3",
"version": "4.7.4",
"type": "module",
"engines": {
"node": "^18.0.0 || >=20.0.0",
@ -96,7 +96,7 @@
"typescript": "^5.2.2",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.1.6",
"vite": "^5.2.6",
"vite-bundle-analyzer": "0.8.1",
"vite-plugin-cdn2": "1.1.0",
"vite-plugin-compression": "^0.5.1",

72
pnpm-lock.yaml generated
View File

@ -108,10 +108,10 @@ devDependencies:
version: 6.21.0(eslint@8.57.0)(typescript@5.2.2)
'@vitejs/plugin-vue':
specifier: ^5.0.4
version: 5.0.4(vite@5.2.2)(vue@3.4.21)
version: 5.0.4(vite@5.2.6)(vue@3.4.21)
'@vitejs/plugin-vue-jsx':
specifier: ^3.1.0
version: 3.1.0(vite@5.2.2)(vue@3.4.21)
version: 3.1.0(vite@5.2.6)(vue@3.4.21)
'@vitest/ui':
specifier: 1.4.0
version: 1.4.0(vitest@1.4.0)
@ -191,8 +191,8 @@ devDependencies:
specifier: ^0.26.0
version: 0.26.0(vue@3.4.21)
vite:
specifier: ^5.1.6
version: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
specifier: ^5.2.6
version: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
vite-bundle-analyzer:
specifier: 0.8.1
version: 0.8.1
@ -201,31 +201,31 @@ devDependencies:
version: 1.1.0
vite-plugin-compression:
specifier: ^0.5.1
version: 0.5.1(vite@5.2.2)
version: 0.5.1(vite@5.2.6)
vite-plugin-ejs:
specifier: ^1.7.0
version: 1.7.0(vite@5.2.2)
version: 1.7.0(vite@5.2.6)
vite-plugin-eslint:
specifier: 1.8.1
version: 1.8.1(eslint@8.57.0)(vite@5.2.2)
version: 1.8.1(eslint@8.57.0)(vite@5.2.6)
vite-plugin-imp:
specifier: ^2.4.0
version: 2.4.0(vite@5.2.2)
version: 2.4.0(vite@5.2.6)
vite-plugin-inspect:
specifier: ^0.8.3
version: 0.8.3(vite@5.2.2)
version: 0.8.3(vite@5.2.6)
vite-plugin-mock-dev-server:
specifier: 1.4.7
version: 1.4.7(vite@5.2.2)
version: 1.4.7(vite@5.2.6)
vite-plugin-svg-icons:
specifier: ^2.0.1
version: 2.0.1(vite@5.2.2)
version: 2.0.1(vite@5.2.6)
vite-svg-loader:
specifier: ^4.0.0
version: 4.0.0
vite-tsconfig-paths:
specifier: 4.3.2
version: 4.3.2(typescript@5.2.2)(vite@5.2.2)
version: 4.3.2(typescript@5.2.2)(vite@5.2.6)
vitest:
specifier: 1.4.0
version: 1.4.0(@types/node@20.5.1)(@vitest/ui@1.4.0)(happy-dom@14.3.1)(sass@1.71.1)
@ -1816,7 +1816,7 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
/@vitejs/plugin-vue-jsx@3.1.0(vite@5.2.2)(vue@3.4.21):
/@vitejs/plugin-vue-jsx@3.1.0(vite@5.2.6)(vue@3.4.21):
resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
@ -1826,20 +1826,20 @@ packages:
'@babel/core': 7.24.1
'@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.24.1)
'@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.1)
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
vue: 3.4.21(typescript@5.2.2)
transitivePeerDependencies:
- supports-color
dev: true
/@vitejs/plugin-vue@5.0.4(vite@5.2.2)(vue@3.4.21):
/@vitejs/plugin-vue@5.0.4(vite@5.2.6)(vue@3.4.21):
resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vue: ^3.2.25
dependencies:
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
vue: 3.4.21(typescript@5.2.2)
dev: true
@ -7886,7 +7886,7 @@ packages:
debug: 4.3.4
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- '@types/node'
- less
@ -7912,7 +7912,7 @@ packages:
- supports-color
dev: true
/vite-plugin-compression@0.5.1(vite@5.2.2):
/vite-plugin-compression@0.5.1(vite@5.2.6):
resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==}
peerDependencies:
vite: '>=2.0.0'
@ -7920,21 +7920,21 @@ packages:
chalk: 4.1.2
debug: 4.3.4
fs-extra: 10.1.0
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- supports-color
dev: true
/vite-plugin-ejs@1.7.0(vite@5.2.2):
/vite-plugin-ejs@1.7.0(vite@5.2.6):
resolution: {integrity: sha512-JNP3zQDC4mSbfoJ3G73s5mmZITD8NGjUmLkq4swxyahy/W0xuokK9U9IJGXw7KCggq6UucT6hJ0p+tQrNtqTZw==}
peerDependencies:
vite: '>=5.0.0'
dependencies:
ejs: 3.1.9
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
dev: true
/vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@5.2.2):
/vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@5.2.6):
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
peerDependencies:
eslint: '>=7'
@ -7944,10 +7944,10 @@ packages:
'@types/eslint': 8.56.6
eslint: 8.57.0
rollup: 2.79.1
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
dev: true
/vite-plugin-imp@2.4.0(vite@5.2.2):
/vite-plugin-imp@2.4.0(vite@5.2.6):
resolution: {integrity: sha512-L/6/nvOw+MyNh4UxAlCZHsmKd5MitmHamqqAWB15sbUgVIEz/OQ8jpKr6kkQU0eA/AIe8fkCVbQBlP81ajrqWg==}
peerDependencies:
vite: '>= 2.0.0-beta.5'
@ -7959,12 +7959,12 @@ packages:
chalk: 4.1.2
param-case: 3.0.4
pascal-case: 3.1.2
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- supports-color
dev: true
/vite-plugin-inspect@0.8.3(vite@5.2.2):
/vite-plugin-inspect@0.8.3(vite@5.2.6):
resolution: {integrity: sha512-SBVzOIdP/kwe6hjkt7LSW4D0+REqqe58AumcnCfRNw4Kt3mbS9pEBkch+nupu2PBxv2tQi69EQHQ1ZA1vgB/Og==}
engines: {node: '>=14'}
peerDependencies:
@ -7983,13 +7983,13 @@ packages:
perfect-debounce: 1.0.0
picocolors: 1.0.0
sirv: 2.0.4
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- rollup
- supports-color
dev: true
/vite-plugin-mock-dev-server@1.4.7(vite@5.2.2):
/vite-plugin-mock-dev-server@1.4.7(vite@5.2.6):
resolution: {integrity: sha512-vGNW423fkmMibf0BfYL89n2n4tNKDt51d6Ee14gC1LlLiJAp6jabJBPsjWgU+uMgtp68+1uBb5F1qTlqdAhnoQ==}
engines: {node: ^16 || ^18 || >= 20}
peerDependencies:
@ -8011,7 +8011,7 @@ packages:
mime-types: 2.1.35
path-to-regexp: 6.2.1
picocolors: 1.0.0
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
ws: 8.16.0
transitivePeerDependencies:
- bufferutil
@ -8020,7 +8020,7 @@ packages:
- utf-8-validate
dev: true
/vite-plugin-svg-icons@2.0.1(vite@5.2.2):
/vite-plugin-svg-icons@2.0.1(vite@5.2.6):
resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==}
peerDependencies:
vite: '>=2.0.0'
@ -8033,7 +8033,7 @@ packages:
pathe: 0.2.0
svg-baker: 1.7.0
svgo: 2.8.0
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- supports-color
dev: true
@ -8045,7 +8045,7 @@ packages:
svgo: 3.1.0
dev: true
/vite-tsconfig-paths@4.3.2(typescript@5.2.2)(vite@5.2.2):
/vite-tsconfig-paths@4.3.2(typescript@5.2.2)(vite@5.2.6):
resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==}
peerDependencies:
vite: '*'
@ -8056,14 +8056,14 @@ packages:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.2.2)
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/vite@5.2.2(@types/node@20.5.1)(sass@1.71.1):
resolution: {integrity: sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==}
/vite@5.2.6(@types/node@20.5.1)(sass@1.71.1):
resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@ -8144,7 +8144,7 @@ packages:
strip-literal: 2.0.0
tinybench: 2.6.0
tinypool: 0.8.2
vite: 5.2.2(@types/node@20.5.1)(sass@1.71.1)
vite: 5.2.6(@types/node@20.5.1)(sass@1.71.1)
vite-node: 1.4.0(@types/node@20.5.1)(sass@1.71.1)
why-is-node-running: 2.2.2
transitivePeerDependencies:

View File

@ -0,0 +1,114 @@
import type { ECharts } from 'echarts/core'
import type { VoidFC } from '@/types'
/**
*
* @description
* RChart
* 便
*
* @warning
* register 使
* 使 hooks register
* create hook使 nextTick
*
* @example
* defineComponent({
* setup() {
* const [register, { ...Hooks }] = useChart()
*
* return {
* register,
* ...Hooks,
* }
* },
* render() {
* const { register, ...Hooks } = this
*
* return <RChart onRegister={register} />
* },
* })
*/
const useChart = () => {
let echartInst: ECharts
let _dispose: VoidFC
let _render: VoidFC
/**
*
* @param inst echart instance
*
* @description
* echart 使 useChart hook
*/
const register = (inst: ECharts, render: VoidFC, dispose: VoidFC) => {
if (inst) {
echartInst = inst
_dispose = dispose
_render = render
}
}
/**
*
* @description
* echart
*
* onRegister
*
* @example
* const [register, { getChartInstance }] = useChart()
*
* const inst = getChartInstance()
*/
const getChartInstance = () => {
if (!echartInst) {
throw new Error(
'[useChart]: echart instance is not ready yet. if you are using useChart, please make sure you have called register method in onRegister event.',
)
}
return {
dispose: _dispose,
render: _render,
echartInst,
}
}
/**
*
* @description
* chart
* true, false
*/
const isDispose = () =>
!(echartInst && getChartInstance().echartInst.getDom())
/**
*
* @description
* chart
*/
const dispose = () => getChartInstance().dispose.call(null)
/**
*
* @description
* chart
*/
const render = () => getChartInstance().render.call(null)
return [
register,
{
getChartInstance,
isDispose,
dispose,
render,
},
] as const
}
export type UseChartReturn = ReturnType<typeof useChart>
export default useChart

View File

@ -1,11 +1,12 @@
import RChart from './src'
import chartProps from './src/props'
import useChart from './hooks/useChart'
import type { ExtractPublicPropTypes } from 'vue'
import type * as RChartType from './src/types'
import type { UseChartReturn } from './hooks/useChart'
export type ChartProps = ExtractPublicPropTypes<typeof chartProps>
export type { RChartType }
export type { RChartType, UseChartReturn }
export { RChart, chartProps }
export { RChart, chartProps, useChart }

View File

@ -70,14 +70,18 @@ echartThemes.forEach((curr) => {
*
* @example
* <template>
* <RChart :options="options" />
* <RChart :options="options" @register="register" />
* </template>
*
* <script setup>
* import { RChart } from '@/components'
*
* import { useChart } from 'vue'
* import { ref } from 'vue'
*
* const [register, { ...Hooks }] = useChart()
* const options = ref({ ... })
* </script>
*/
export default defineComponent({
name: 'RChart',
@ -348,6 +352,13 @@ export default defineComponent({
// 初始化完成后移除 intersectionObserver 监听
intersectionObserverReturn?.stop()
// 注册 register用于 useChart hook
const { onRegister } = props
if (onRegister && echartInst) {
call(onRegister, echartInst, mount, unmount)
}
}
if (props.intersectionObserver) {

View File

@ -1,3 +1,5 @@
import { loadingOptions } from './utils'
import type * as echarts from 'echarts/core' // echarts 核心模块
import type { PropType, VNode } from 'vue'
import type { MaybeArray } from '@/types'
@ -15,8 +17,7 @@ import type {
RChartDownloadOptions,
} from './types'
import type { CardProps, DropdownProps, DropdownOption } from 'naive-ui'
import { loadingOptions } from './utils'
import type { VoidFC } from '@/types'
const props = {
/**
@ -354,6 +355,20 @@ const props = {
replaceMerge: [],
}),
},
/**
*
* @description
* RChart
* useChart register 使便使 hooks
*
* @default null
*/
onRegister: {
type: [Function, Array] as PropType<
MaybeArray<(chartInst: ECharts, render: VoidFC, dispose: VoidFC) => void>
>,
default: null,
},
}
export default props

View File

@ -0,0 +1,12 @@
import RForm from './src/Form'
import formProps from './src/props'
import useForm from './src/hooks/useForm'
import type * as RFormType from './src/types'
import type { ExtractPublicPropTypes } from 'vue'
import type { UseFormReturn } from './src/hooks/useForm'
export type FormProps = ExtractPublicPropTypes<typeof formProps>
export type { RFormType, UseFormReturn }
export { RForm, formProps, useForm }

View File

@ -0,0 +1,49 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-03-27
*
* @workspace ray-template
*
* @remark
*/
import { NForm } from 'naive-ui'
import props from './props'
import { call } from '@/utils'
import type { RFormInst } from './types'
export default defineComponent({
name: 'RForm',
props,
setup(props) {
const formRef = ref<RFormInst>()
onMounted(() => {
// 主动调用 register 方法,满足 useForm 方法正常调用
const { onRegister } = props
if (onRegister && formRef.value) {
call(onRegister, formRef.value)
}
})
return {
formRef,
}
},
render() {
const { $attrs, $props, $slots } = this
return (
<NForm {...$attrs} {...$props} ref="formRef">
{{
...$slots,
}}
</NForm>
)
},
})

View File

@ -0,0 +1,113 @@
import { cloneDeep } from 'lodash-es'
import type {
RFormInst,
FormValidateCallback,
ShouldRuleBeApplied,
RFormRules,
} from '../types'
/**
*
* @description
* RForm
* 便
*
* @warning
* register 使
* 使 hooks register
* create hook使 nextTick
*
* @example
* defineComponent({
* setup() {
* const [register, { ...Hooks }] = useForm()
*
* return {
* register,
* ...Hooks,
* }
* },
* render() {
* const { register, ...Hooks } = this
*
* return <RForm onRegister={register} />
* },
* })
*/
const useForm = <T extends Record<string, unknown>, R extends RFormRules>(
model?: T,
rules?: R,
) => {
const formRef = ref<RFormInst>()
const register = (inst: RFormInst) => {
if (inst) {
formRef.value = inst
}
}
const getFormInstance = () => {
if (!formRef.value) {
throw new Error(
'[useForm]: form instance is not ready yet. if you are using useForm, please make sure you have called register method in onRegister event.',
)
}
return formRef.value
}
/**
*
* @description
* Promise rejection FormValidationError[]
*
* @see https://www.naiveui.com/zh-CN/dark/components/form#inline.vue
*/
const validate = (
callback?: FormValidateCallback,
shouldRuleBeApplied?: ShouldRuleBeApplied,
) => getFormInstance().validate.call(null, callback, shouldRuleBeApplied)
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/form#Form-Methods
*/
const restoreValidation = () => getFormInstance().restoreValidation.call(null)
/**
*
* @description
*
*
* useForm model
*/
const formModel = () => cloneDeep(model) || ({} as T)
/**
*
* @description
*
*
* useForm rules
*/
const formRules = () => cloneDeep(rules) || ({} as R)
return [
register,
{
getFormInstance,
validate,
restoreValidation,
formModel,
formRules,
},
] as const
}
export type UseFormReturn = ReturnType<typeof useForm>
export default useForm

View File

@ -0,0 +1,24 @@
import { formProps } from 'naive-ui'
import type { MaybeArray } from '@/types'
import type { RFormInst } from './types'
const props = {
...formProps,
/**
*
* @description
* RForm
* useForm register 使便使 hooks
*
* @default null
*/
onRegister: {
type: [Function, Array] as PropType<
MaybeArray<(formInst: RFormInst) => void>
>,
default: null,
},
}
export default props

View File

@ -0,0 +1,13 @@
import type { FormInst, FormItemRule, FormRules } from 'naive-ui'
export type RFormInst = FormInst
type FormValidateParameters = Parameters<FormInst['validate']>
export type FormValidateCallback = FormValidateParameters[0]
export type ShouldRuleBeApplied = FormValidateParameters[1]
export interface RFormRules {
[itemValidatePath: string]: FormItemRule | Array<FormItemRule> | FormRules
}

View File

@ -1,8 +1,12 @@
import RModal from './src/Modal'
import modalProps from './src/props'
import useModal from './src/hooks/useModal'
import type * as RModalType from './src/types'
import type { ExtractPublicPropTypes } from 'vue'
import type { UseModalReturn } from './src/hooks/useModal'
export type ModalProps = ExtractPublicPropTypes<typeof modalProps>
export type { RModalType, UseModalReturn }
export { RModal, modalProps }
export { RModal, modalProps, useModal }

View File

@ -15,8 +15,12 @@ import { NModal } from 'naive-ui'
import props from './props'
import { completeSize, uuid } from '@/utils'
import { useWindowSize } from '@vueuse/core'
import { setupDraggable } from './utils'
import { setupInteract } from './utils'
import {
FULLSCREEN_CARD_TYPE_CLASS,
R_MODAL_CLASS,
CSS_VARS_KEYS,
} from './constant'
import type interact from 'interactjs'
@ -24,11 +28,10 @@ export default defineComponent({
name: 'RModal',
props,
setup(props) {
const { height } = useWindowSize()
const cssVars = computed(() => ({
'--r-modal-width': completeSize(props.width ?? 600),
'--r-modal-card-width': completeSize(props.cardWidth ?? 600),
'--r-modal-dialog-width': completeSize(props.dialogWidth ?? 446),
[CSS_VARS_KEYS['width']]: completeSize(props.width ?? 600),
[CSS_VARS_KEYS['cardWidth']]: completeSize(props.cardWidth ?? 600),
[CSS_VARS_KEYS['dialogWidth']]: completeSize(props.dialogWidth ?? 446),
}))
const uuidEl = uuid()
let intractable: null | ReturnType<typeof interact>
@ -37,32 +40,10 @@ export default defineComponent({
x: 0,
y: 0,
}
/**
*
* card
*/
const isFullscreenCardType = () =>
props.preset === 'card' && props.fullscreen
const setupInteract = () => {
const target = document.getElementById(uuidEl)
if (target) {
setupDraggable(target, props.preset, {
scheduler: (event) => {
const target = event.target
position.x += event.dx
position.y += event.dy
target.style.transform = `translate(${position.x}px, ${position.y}px)`
},
}).then((res) => {
intractable = res
})
}
}
// 当前是否为预设 card 类型并且设置了 fullscreen
const isFullscreenCardType = computed(
() => props.preset === 'card' && props.fullscreen,
)
watch(
() => props.show,
@ -73,10 +54,22 @@ export default defineComponent({
(props.preset === 'card' || props.preset === 'dialog')
) {
nextTick(() => {
setupInteract()
const target = document.getElementById(uuidEl)
if (target) {
setupInteract(target, {
preset: props.preset,
x: position.x,
y: position.y,
dargCallback: (x, y) => {
position.x = x
position.y = y
},
}).then((res) => {
intractable = res
})
}
if (props.memo && target) {
target.style.transform = `translate(${position.x}px, ${position.y}px)`
}
@ -94,24 +87,22 @@ export default defineComponent({
return {
cssVars,
height,
isFullscreenCardType,
uuidEl,
}
},
render() {
const { isFullscreenCardType } = this
const { $props, $slots, $attrs } = this
const { preset, ...$otherProps } = $props
const { cssVars, height, uuidEl } = this
const { cssVars, uuidEl, isFullscreenCardType } = this
return (
<NModal
class={[
'r-modal',
isFullscreenCardType() ? 'r-modal__preset-card--fullscreen' : '',
R_MODAL_CLASS,
isFullscreenCardType ? FULLSCREEN_CARD_TYPE_CLASS : '',
]}
style={[cssVars, isFullscreenCardType() ? `height: ${height}px` : '']}
style={[cssVars, isFullscreenCardType ? `height: 100vh` : '']}
preset={preset}
{...{
id: uuidEl,

View File

@ -0,0 +1,9 @@
export const FULLSCREEN_CARD_TYPE_CLASS = 'r-modal__preset-card--fullscreen'
export const R_MODAL_CLASS = 'r-modal'
export const CSS_VARS_KEYS = {
width: '--r-modal-width',
cardWidth: '--r-modal-card-width',
dialogWidth: '--r-modal-dialog-width',
}

View File

@ -0,0 +1,61 @@
import { useModal as useNaiveModal } from 'naive-ui'
import { setupInteract } from '../utils'
import { queryElements, setStyle, completeSize, setClass } from '@/utils'
import { R_MODAL_CLASS, CSS_VARS_KEYS } from '../constant'
import type { RModalProps } from '../types'
interface UseModalCreateOptions extends Omit<RModalProps, 'memo'> {}
const useModal = () => {
const { create: naiveCreate, destroyAll: naiveDestroyAll } = useNaiveModal()
const create = (options: UseModalCreateOptions) => {
const { preset, dad, fullscreen, width, cardWidth, dialogWidth } = options
const modalReactive = naiveCreate(options)
const { key } = modalReactive
const cssVars = {
[CSS_VARS_KEYS['width']]: completeSize(width ?? 600),
[CSS_VARS_KEYS['cardWidth']]: completeSize(cardWidth ?? 600),
[CSS_VARS_KEYS['dialogWidth']]: completeSize(dialogWidth ?? 446),
}
nextTick(() => {
const [modalElement] =
queryElements<HTMLElement>(`[internalkey="${key}"]`) || []
if (!modalElement) {
return
}
if (dad) {
setupInteract(modalElement, {
preset,
x: 0,
y: 0,
})
}
if (fullscreen && preset === 'card') {
setStyle(modalElement, {
width: '100%',
height: '100vh',
})
}
setStyle(modalElement, cssVars)
setClass(modalElement, R_MODAL_CLASS)
})
return modalReactive
}
return {
create,
destroyAll: naiveDestroyAll,
}
}
export type UseModalReturn = ReturnType<typeof useModal>
export default useModal

View File

@ -38,7 +38,7 @@ const props = {
/**
*
* @description
* preset
* preset
*
* @default 600
*/

View File

@ -0,0 +1,53 @@
import type { ModalOptions as NaiveModalOptions } from 'naive-ui'
export interface RModalProps extends NaiveModalOptions {
/**
*
* @description
*
*
* @default true
*/
memo?: boolean
/**
*
* @description
*
*
* @default false
*/
fullscreen?: boolean
/**
*
* @description
* preset
*
* @default 600
*/
width?: number
/**
*
* @description
* preset card
*
* @default 600
*/
cardWidth?: number
/**
*
* @description
* preset dialog
*
* @default 446
*/
dialogWidth?: number
/**
*
* @description
*
* header
*
* @default false
*/
dad?: boolean
}

View File

@ -1,10 +1,18 @@
import interact from 'interactjs'
import type { ModalProps } from 'naive-ui'
import type { AnyFC } from '@/types'
import type { RModalProps } from './types'
interface SetupDraggableOptions {
scheduler?: AnyFC
scheduler?: (event: Interact.DragEvent) => void
}
interface SetupInteractOptions {
preset: ModalProps['preset']
memo?: RModalProps['memo']
x: number
y: number
dargCallback?: (x: number, y: number, event: Interact.DragEvent) => void
}
/**
@ -12,6 +20,7 @@ interface SetupDraggableOptions {
* @param bindModal modal
* @param preset
*
* @description
*
* card, dialog
*
@ -54,3 +63,39 @@ export const setupDraggable = (
}, 30)
})
}
export const setupInteract = (
target: HTMLElement | string,
options: SetupInteractOptions,
): Promise<ReturnType<typeof interact>> => {
const _target =
typeof target === 'string'
? (document.querySelector(target) as HTMLElement)
: target
return new Promise((resolve, reject) => {
if (_target) {
_target.setAttribute('can-drag', 'true')
const { preset, dargCallback } = options
let { x, y } = options
setupDraggable(_target, preset, {
scheduler: (event) => {
const target = event.target
x += event.dx
y += event.dy
target.style.transform = `translate(${x}px, ${y}px)`
dargCallback?.(x, y, event)
},
}).then((res) => {
resolve(res)
})
} else {
reject()
}
})
}

View File

@ -1,10 +1,12 @@
import RTable from './src/Table'
import tableProps from './src/props'
import useTable from './src/hooks/useTable'
import type * as RTableType from './src/types'
import type { UseTableReturn } from './src/hooks/useTable'
import type { ExtractPublicPropTypes } from 'vue'
export type TableProps = ExtractPublicPropTypes<typeof tableProps>
export type { RTableType }
export type { RTableType, UseTableReturn }
export { RTable, tableProps }
export { RTable, tableProps, useTable }

View File

@ -30,7 +30,7 @@ export default defineComponent({
name: 'RTable',
props,
setup(props, ctx) {
const { expose } = ctx
const { expose, emit } = ctx
const rTableInst = ref<DataTableInst>()
const wrapperRef = ref<HTMLElement>()
@ -161,8 +161,14 @@ export default defineComponent({
* toolOptions toolOptions
*/
const tool = (p: typeof props) => {
const { tool } = p
if (!tool) {
return
}
const renderDefaultToolOptions = () => (
<>
<NFlex align="center">
<Print {...p} />
<Size {...p} onChangeSize={changeTableSize.bind(this)} />
<Fullscreen />
@ -172,25 +178,34 @@ export default defineComponent({
onPopselectChange={popselectChange.bind(this)}
onInitialed={popselectChange.bind(this)}
/>
</>
</NFlex>
)
if (!props.toolOptions) {
return renderDefaultToolOptions
} else {
if (props.coverTool) {
return renderToolOptions
return <NFlex align="center">{renderToolOptions()}</NFlex>
} else {
return () => (
<>
<NFlex align="center">
{renderDefaultToolOptions()}
{renderToolOptions()}
</>
</NFlex>
)
}
}
}
onMounted(() => {
// 主动调用 register 方法,满足 useTable 方法正常调用
const { onRegister } = props
if (onRegister && rTableInst.value) {
call(onRegister, rTableInst.value)
}
})
provide(config.tableKey, {
uuidTable,
uuidWrapper,
@ -242,13 +257,13 @@ export default defineComponent({
default: () => (
<>
<NDataTable
ref="rTableInst"
{...{ id: uuidTable }}
{...$attrs}
{...$props}
{...propsPopselectValue}
rowProps={combineRowProps.bind(this)}
size={privateReactive.size}
ref="rTableInst"
>
{{
...$slots,
@ -273,12 +288,8 @@ export default defineComponent({
header: renderNode(title, {
defaultElement: <div style="display: none;"></div>,
}),
'header-extra': () => (
<NFlex align="center">
{/* eslint-disable @typescript-eslint/no-explicit-any */}
{tool($props as any)}
</NFlex>
),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'header-extra': tool($props as any),
footer: () => $slots.tableFooter?.(),
action: () => $slots.tableAction?.(),
}}

View File

@ -0,0 +1,147 @@
import type {
RTableInst,
CsvOptionsType,
FilterState,
ScrollToOptions,
ColumnKey,
SortOrder,
} from '../types'
/**
*
* @description
* RTable
* 便
*
* @warning
* register 使
* 使 hooks register
* create hook使 nextTick
*
* @example
* defineComponent({
* setup() {
* const [register, { ...Hooks }] = useTable()
*
* return {
* register,
* ...Hooks,
* }
* },
* render() {
* const { register, ...Hooks } = this
*
* return <RTable onRegister={register} />
* },
* })
*/
const useTable = () => {
const tableRef = ref<RTableInst>()
const register = (inst: RTableInst) => {
if (inst) {
tableRef.value = inst
}
}
/**
*
* @description
* RTable
*/
const getTableInstance = () => {
if (!tableRef.value) {
throw new Error(
'[useTable]: table instance is not ready yet. if you are using useTable, please make sure you have called register method in onRegister event.',
)
}
return tableRef.value
}
/**
*
* @description
* filter
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#filter-and-sorter
*/
const clearFilters = () => getTableInstance().clearFilters.call(null)
/**
*
* @description
* sort
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#filter-and-sorter
*/
const clearSorter = () => getTableInstance().clearSorter.call(null)
/**
*
* @description
* CSV
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#export-csv.vue
*/
const downloadCsv = (options?: CsvOptionsType) =>
getTableInstance().downloadCsv.call(null, options)
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#filter-and-sorter
*/
const filters = (filters: FilterState | null) =>
getTableInstance().filters.call(null, filters)
/**
*
* @description
* page
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#DataTable-Methods
*/
const page = (page: number) => getTableInstance().page.call(null, page)
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#DataTable-Methods
*/
const scrollTo: ScrollToOptions = (options) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getTableInstance().scrollTo(options as any)
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#DataTable-Methods
*/
const sort = (columnKey: ColumnKey, order: SortOrder) =>
getTableInstance().sort.call(null, columnKey, order)
return [
register,
{
getTableInstance,
clearFilters,
clearSorter,
downloadCsv,
filters,
page,
scrollTo,
sort,
},
] as const
}
export type UseTableReturn = ReturnType<typeof useTable>
export default useTable

View File

@ -14,101 +14,198 @@ import { dataTableProps } from 'naive-ui'
import type { PropType, VNode } from 'vue'
import type { MaybeArray } from '@/types'
import type { DropdownOption, DataTableColumn } from 'naive-ui'
import type { DownloadCsvTableOptions, PrintTableOptions } from './types'
import type {
DownloadCsvTableOptions,
PrintTableOptions,
RTableInst,
} from './types'
import type { Recordable } from '@/types'
const props = {
...dataTableProps,
/**
*
* @description
*
*
* false toolOptions
*
* @default true
*/
tool: {
type: Boolean,
default: true,
},
/**
*
* @description
*
*
* @default {}
*/
downloadCsvTableOptions: {
/**
*
*
*/
type: Object as PropType<DownloadCsvTableOptions>,
default: () => ({}),
},
/**
*
* @description
* VNode
*
* @default null
*/
title: {
/**
*
*
*
*/
type: [String, Number, Object] as PropType<VNode | string | number>,
default: null,
},
/**
*
* @description
*
*
* @default undefined
*/
toolOptions: {
/** 自定义拓展工具栏 */
type: Array as PropType<(VNode | (() => VNode))[]>,
},
/**
*
* @description
*
*
* @default false
*/
coverTool: {
/** 当 toolOptions 配置时,是否覆盖原工具栏 */
type: Boolean,
default: false,
},
/**
*
* @description
*
*
* NDropdown
*
* @default undefined
*/
contextMenuOptions: {
/**
*
*
* `NDropdown`
*/
type: Array as PropType<DropdownOption[]>,
},
/**
*
* @description
*
*
* false
*
* @default false
*/
disabledContextMenu: {
/**
*
*
* false
*/
type: Boolean,
default: false,
},
/**
*
* @description
*
*
*
*
* @default null
*/
onContextMenuClick: {
/** 右键菜单点击 */
type: [Function, Array] as PropType<
MaybeArray<(key: string | number, option: DropdownOption) => void>
>,
default: null,
},
/**
*
* @description
*
*
*
*
* @default false
*/
wrapperBordered: {
/**
*
*
*
*/
type: Boolean,
default: false,
},
/**
*
* @description
*
*
* dom printDom
*
* @default {}
*/
printTableOptions: {
/**
*
*
*/
type: Object as PropType<PrintTableOptions>,
default: () => ({}),
},
/**
*
* @description
*
*
* v-model:columns onUpdateColumns
*
*
* @default null
*/
onUpdateColumns: {
type: [Function, Array] as PropType<
MaybeArray<(arr: DataTableColumn[]) => void>
>,
default: null,
},
/**
*
* @description
*
*
* v-model:columns onUpdateColumns
*
*
* @default null
*/
'onUpdate:columns': {
type: [Function, Array] as PropType<
MaybeArray<(arr: DataTableColumn[]) => void>
>,
default: null,
},
/**
*
* @description
* Table
*
* 使 rowProps
*
* @default null
*/
onContextmenu: {
/**
*
* Table
* 使 rowProps
*/
type: [Function, Array] as PropType<
MaybeArray<(row: Recordable, index: number, e: MouseEvent) => void>
>,
default: null,
},
/**
*
* @description
* RTable
* useTable register 使便使 hooks
*
* @default null
*/
onRegister: {
type: [Function, Array] as PropType<
MaybeArray<(tableInst: RTableInst) => void>
>,
default: null,
},
}
export default props

View File

@ -42,10 +42,28 @@ export interface C extends DataTableBaseColumn {
children?: C[]
}
export interface RTableInst extends Omit<DataTableInst, 'clearFilter'> {}
export type OverridesTableColumn<T = Recordable> = C | DataTableColumn<T>
export interface TableInst extends Omit<TableProvider, 'wrapperRef'> {
rTableInst: Omit<DataTableInst, 'clearFilter'>
rTableInst: RTableInst
}
export type PropsComponentPopselectKeys = 'striped' | 'bordered'
type DownloadCsvParameters = Parameters<RTableInst['downloadCsv']>
export type CsvOptionsType = DownloadCsvParameters[0]
type FiltersParameters = Parameters<RTableInst['filters']>
export type FilterState = FiltersParameters[0]
export type ScrollToOptions = RTableInst['scrollTo']
type SortParameters = Parameters<RTableInst['sort']>
export type ColumnKey = SortParameters[0]
export type SortOrder = SortParameters[1]

View File

@ -8,6 +8,7 @@ export * from './RMoreDropdown'
export * from './RQRCode'
export * from './RTable'
export * from './RTransitionComponent'
export * from './RForm'
// 导出自定义组件类型
export type * from './RChart/src/types'
@ -16,3 +17,5 @@ export type * from './RIframe/src/types'
export type * from './RQRCode/src/types'
export type * from './RTable/src/types'
export type * from './RTransitionComponent/src/types'
export type * from './RForm/src/types'
export type * from './RModal/src/types'

View File

@ -5,3 +5,4 @@ export * from './useDevice'
export * from './useElementFullscreen'
export * from './useDomToImage'
export * from './usePrint'
export * from './usePagination'

View File

@ -19,7 +19,16 @@ import { watchEffectWithTarget } from '@/utils'
import type { UseWindowSizeOptions } from '@vueuse/core'
export interface UseDeviceOptions extends UseWindowSizeOptions {}
export interface UseDeviceOptions extends UseWindowSizeOptions {
/**
*
* @description
* isTabletOrSmaller
*
* @default 768
*/
media?: number
}
/**
*
@ -40,7 +49,9 @@ export function useDevice(options?: UseDeviceOptions) {
const isTabletOrSmaller = ref(false)
const update = () => {
isTabletOrSmaller.value = width.value <= 768
const { media = 768 } = options ?? {}
isTabletOrSmaller.value = width.value <= media
}
watchEffectWithTarget(update)

View File

@ -0,0 +1,163 @@
import { omit } from '@/utils'
import type { AnyFC } from '@/types'
import type { PaginationProps } from 'naive-ui'
type OmitKeys =
| 'themeOverrides'
| 'theme'
| 'on-update:page'
| 'on-update:page-size'
| 'onUpdatePage'
| 'onUpdatePageSize'
| 'onUpdate:page'
| 'onUpdate:page-size'
export interface UsePaginationOptions extends Omit<PaginationProps, OmitKeys> {}
const defaultOptions: UsePaginationOptions = {
page: 1,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 20, 50, 100],
}
/**
*
* @param callback
* @param options
*
* @description
* 便 hook
*
* @warning
* callback
*/
export const usePagination = <T extends AnyFC>(
callback: T,
options?: UsePaginationOptions,
) => {
if (typeof callback !== 'function') {
throw new Error(
'[usePagination]: callback expected a function, but got ' +
typeof callback,
)
}
const omitOptions = omit(options, [
'on-update:page',
'on-update:page-size',
'onUpdatePage',
'onUpdatePageSize',
'onUpdate:page',
'onUpdate:page-size',
])
const methodsOptions = {
onUpdatePage: (page: number) => {
paginationRef.page = page
callback()
},
onUpdatePageSize: (pageSize: number) => {
paginationRef.pageSize = pageSize
paginationRef.page = 1
callback()
},
}
const paginationRef = reactive<PaginationProps>(
Object.assign({}, defaultOptions, omitOptions, methodsOptions),
)
const updatePage = paginationRef.onUpdatePage as (page: number) => void
const updatePageSize = paginationRef.onUpdatePageSize as (
pageSize: number,
) => void
/**
*
* @description
*
*/
const getItemCount = () => paginationRef.itemCount
/**
*
* @param itemCount
*
* @description
*
*/
const setItemCount = (itemCount: number) => {
paginationRef.itemCount = itemCount
}
/**
*
* @description
*
*/
const getPage = () => paginationRef.page
/**
*
* @param page
*
* @description
*
*
* updatePage
*/
const setPage = (page: number) => {
updatePage(page)
}
/**
*
* @description
*
*/
const getPageSize = () => paginationRef.pageSize
/**
*
* @param pageSize
*
* @description
*
*
* updatePageSize
*/
const setPageSize = (pageSize: number) => {
updatePageSize(pageSize)
}
/**
*
* @description
* RTable
*/
const getPagination = () => paginationRef as UsePaginationOptions
/**
*
* @description
*
*/
const getCallback = callback
return {
updatePage,
updatePageSize,
getItemCount,
setItemCount,
getPage,
setPage,
getPageSize,
setPageSize,
getPagination,
getCallback,
}
}
export type UsePaginationReturn = ReturnType<typeof usePagination>

View File

@ -24,5 +24,6 @@
"TemplateHooks": "Template Api",
"Modal": "Modal",
"ContextMenu": "Right Click Menu",
"CacheDemo": "Cache Utils Demo"
"CacheDemo": "Cache Utils Demo",
"Form": "Form"
}

View File

@ -24,5 +24,6 @@
"TemplateHooks": "模板内置 Api",
"Modal": "模态框",
"ContextMenu": "右键菜单",
"CacheDemo": "缓存工具函数"
"CacheDemo": "缓存工具函数",
"Form": "表单"
}

View File

@ -11,9 +11,6 @@ const cacheDemo: AppRouteRecordRaw = {
i18nKey: t('menu.CacheDemo'),
icon: 'other',
order: 3,
extra: {
label: 'new',
},
},
}

View File

@ -11,6 +11,9 @@ const echart: AppRouteRecordRaw = {
i18nKey: t('menu.Echart'),
icon: 'echart',
order: 1,
extra: {
label: 'useChart',
},
},
}

View File

@ -0,0 +1,20 @@
import { t } from '@/hooks/web/useI18n'
import { LAYOUT } from '@/router/constant'
import type { AppRouteRecordRaw } from '@/router/types'
const form: AppRouteRecordRaw = {
path: '/form',
name: 'FormView',
component: () => import('@/views/demo/form'),
meta: {
i18nKey: t('menu.Form'),
icon: 'other',
order: 2,
extra: {
label: 'useForm',
},
},
}
export default form

View File

@ -12,6 +12,9 @@ const mockDemo: AppRouteRecordRaw = {
icon: 'other',
order: 3,
keepAlive: false,
extra: {
label: 'usePagination',
},
},
}

View File

@ -11,6 +11,9 @@ const table: AppRouteRecordRaw = {
i18nKey: t('menu.Table'),
icon: 'other',
order: 2,
extra: {
label: 'useTable',
},
},
}

View File

@ -136,9 +136,8 @@ export const piniaMenuStore = defineStore(
default: () => label.value,
}),
breadcrumbLabel: label.value,
/** 检查该菜单项是否展示 */
} as AppMenuOption
/** 合并 icon */
/** 合并 icon, extra */
const attr: AppMenuOption = Object.assign({}, route, {
icon: createMenuIcon(option),
extra: createMenuExtra(option),

View File

@ -92,6 +92,8 @@ export type CipherParams = CryptoJS.lib.CipherParams
export type AnyFC<P = any, R = any> = (...args: P[]) => R
export type VoidFC = (...args: any[]) => void
export type PartialCSSStyleDeclaration = Partial<
Record<keyof CSSProperties, string>
>

View File

@ -3,12 +3,25 @@ import './index.scss'
import { NCard, NSwitch, NFlex, NH2, NButton } from 'naive-ui'
import { RChart } from '@/components'
import { useChart } from '@/components'
import type { RChartType } from '@/components'
const Echart = defineComponent({
name: 'REchart',
setup() {
const baseChartRef = ref<RChartType.RChartInst>()
const [register, { getChartInstance, dispose, render, isDispose }] =
useChart()
const [
register2,
{
getChartInstance: getChartInstance2,
dispose: dispose2,
render: render2,
isDispose: isDispose2,
},
] = useChart()
const chartLoading = ref(false)
const chartAria = ref(false)
const state = reactive({
@ -179,15 +192,15 @@ const Echart = defineComponent({
}
const mountChart = () => {
if (!baseChartRef.value?.isDispose()) {
baseChartRef.value?.render()
if (isDispose()) {
render()
} else {
window.$message.warning('图表已经渲染')
}
}
const unmountChart = () => {
baseChartRef.value?.dispose()
dispose()
}
const updateChartOptions = () => {
@ -203,7 +216,6 @@ const Echart = defineComponent({
return {
baseOptions,
baseChartRef,
chartLoading,
handleLoadingShow,
chartAria,
@ -214,9 +226,16 @@ const Echart = defineComponent({
mountChart,
unmountChart,
updateChartOptions,
register,
register2,
dispose2,
render2,
isDispose2,
}
},
render() {
const { register, register2, dispose2, render2, isDispose2 } = this
return (
<div class="echart">
<NCard title="chart 组件">
@ -246,6 +265,9 @@ const Echart = defineComponent({
</h3>
</li>
<li>
<h3>7. useChart </h3>
</li>
</ul>
</NCard>
<NCard title="预设 card 风格图表">
@ -258,8 +280,8 @@ const Echart = defineComponent({
</NFlex>
<div class="chart--container">
<RChart
onRegister={register}
title="周销售量"
ref="baseChartRef"
autoChangeTheme
options={this.baseLineOptions}
showAria={this.chartAria}
@ -268,13 +290,30 @@ const Echart = defineComponent({
</div>
</NCard>
<NCard title="不跟随主题切换的暗色主题可视化图,并且手动指定原始主题色">
<div class="chart--container">
<RChart
autoChangeTheme={false}
theme="default"
options={this.baseOptions}
/>
</div>
<NFlex vertical>
<NFlex>
<NButton
onClick={() => {
if (isDispose2()) {
render2()
} else {
window.$message.warning('图表已经渲染')
}
}}
>
</NButton>
<NButton onClick={dispose2.bind(this)}></NButton>
</NFlex>
<div class="chart--container">
<RChart
onRegister={register2}
autoChangeTheme={false}
theme="default"
options={this.baseOptions}
/>
</div>
</NFlex>
</NCard>
<NCard title="加载动画">
<NSwitch

View File

@ -0,0 +1,157 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-03-27
*
* @workspace ray-template
*
* @remark
*/
import { RForm } from '@/components'
import {
NFormItemGi,
NDatePicker,
NGrid,
NInput,
NInputNumber,
NFlex,
NButton,
NRadio,
NRadioGroup,
} from 'naive-ui'
import { useForm } from '@/components'
import type { RFormRules } from '@/components'
export default defineComponent({
name: 'RFormDemo',
setup() {
// 使用以下 hooks 的时候,应该注意调用时机
const [
register,
{ getFormInstance, validate, restoreValidation, formModel, formRules },
] = useForm(
{
name: null,
age: null,
gender: null,
date: null,
remark: null,
},
{
name: {
required: true,
message: '请输入姓名',
trigger: ['blur', 'change'],
},
date: {
required: true,
message: '请选择日期',
trigger: ['blur', 'change'],
type: 'number',
},
gender: {
required: true,
message: '请选择性别',
trigger: 'change',
},
age: {
required: true,
message: '请输入年龄',
trigger: ['blur', 'change'],
type: 'number',
},
},
)
/**
*
* @description
* 如果待验证数据类型为: number, array type
* 具体可以吃查看: async-validator type
* @see https://github.com/yiminghe/async-validator?tab=readme-ov-file#type
*
* naive ui custom validation
* @see https://www.naiveui.com/zh-CN/dark/components/form#custom-validation.vue
*
* rules useForm
* 使 formRules rules
*/
const rules = ref(formRules())
/**
*
* @description
* useForm
* 使 formModel model
*
* 使 model
*/
const condition = ref(formModel())
return {
register,
rules,
condition,
restoreValidation,
formModel,
validate,
}
},
render() {
const { rules } = this
const { register, restoreValidation, formModel, validate } = this
return (
<RForm onRegister={register} rules={rules} model={this.condition}>
<NGrid cols={24} xGap={24}>
<NFormItemGi label="姓名" path="name" span={12}>
<NInput v-model:value={this.condition.name} />
</NFormItemGi>
<NFormItemGi label="年龄" path="age" span={12}>
<NInputNumber
v-model:value={this.condition.age}
showButton={false}
style="width: 100%"
/>
</NFormItemGi>
<NFormItemGi label="出生日期" path="date" span={12}>
<NDatePicker
v-model:value={this.condition.date}
style="width: 100%"
/>
</NFormItemGi>
<NFormItemGi label="性别" path="gender" span={12}>
<NRadioGroup v-model:value={this.condition.gender}>
<NRadio value="girl"></NRadio>
<NRadio value="man"></NRadio>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi label="备注信息" span={24}>
<NInput type="textarea" v-model:value={this.condition.remark} />
</NFormItemGi>
<NFormItemGi span={24}>
<NFlex justify="flex-end" style="width: 100%">
<NButton
type="info"
onClick={() => {
this.condition = formModel()
}}
>
</NButton>
<NButton type="warning" onClick={restoreValidation.bind(this)}>
</NButton>
<NButton type="primary" onClick={() => validate()}>
</NButton>
</NFlex>
</NFormItemGi>
</NGrid>
</RForm>
)
},
})

View File

@ -14,6 +14,7 @@ import { RCollapseGrid, RTable } from '@/components'
import { useHookPlusRequest } from '@/axios'
import { getPersonList } from '@/api/demo/mock/person'
import { usePagination } from '@/hooks'
import type { Person } from '@/api/demo/mock/person'
@ -86,24 +87,23 @@ const MockDemo = defineComponent({
const condition = reactive({
email: null,
})
const paginationRef = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
pageSizes: [10, 20, 30, 40, 50],
showSizePicker: true,
onUpdatePage: (page: number) => {
paginationRef.page = page
getPerson()
},
onUpdatePageSize: (pageSize: number) => {
paginationRef.pageSize = pageSize
paginationRef.page = 1
getPerson()
},
const {
getPagination,
getPage,
getPageSize,
setItemCount,
getCallback,
setPage,
setPageSize,
} = usePagination(() => {
personFetchRun({
page: getPage(),
pageSize: getPageSize(),
email: condition.email,
})
})
const paginationRef = getPagination()
const {
data: personData,
loading: personLoading,
@ -111,36 +111,26 @@ const MockDemo = defineComponent({
} = useHookPlusRequest(getPersonList, {
defaultParams: [
{
page: paginationRef.page,
pageSize: paginationRef.pageSize,
page: getPage(),
pageSize: getPageSize(),
email: condition.email,
},
],
onSuccess: (res) => {
const { total } = res
paginationRef.itemCount = total
setItemCount(total)
},
})
const getPerson = () => {
const { pageSize, page } = paginationRef
const { email } = condition
personFetchRun({
page,
pageSize,
email,
})
}
return {
personData,
personLoading,
paginationRef,
getPagination,
columns,
...toRefs(condition),
getPerson,
getCallback,
paginationRef,
}
},
render() {
@ -170,7 +160,7 @@ const MockDemo = defineComponent({
</>
),
action: () => (
<NButton type="primary" onClick={this.getPerson.bind(this)}>
<NButton type="primary" onClick={this.getCallback.bind(this)}>
</NButton>
),

View File

@ -12,6 +12,8 @@
import { RModal } from '@/components'
import { NButton, NCard, NFlex } from 'naive-ui'
import { useModal } from '@/components'
export default defineComponent({
name: 'ModalDemo',
setup() {
@ -20,12 +22,36 @@ export default defineComponent({
modal2: false,
modal3: false,
})
const { create } = useModal()
const createCardModal = () => {
create({
title: '卡片模态框',
dad: true,
preset: 'card',
content: '我可以被拖拽的全屏card模态框',
fullscreen: true,
})
}
const createDialogModal = () => {
create({
title: '模态框',
content: '内容',
preset: 'dialog',
dad: true,
})
}
return {
...toRefs(state),
createCardModal,
createDialogModal,
}
},
render() {
const { createCardModal, createDialogModal } = this
return (
<NFlex vertical>
<NCard title="props">
@ -91,6 +117,12 @@ export default defineComponent({
`css variable`
</h4>
</NCard>
<NCard title="重写 useModal">
<NButton onClick={createCardModal.bind(this)}></NButton>
<NButton onClick={createDialogModal.bind(this)}>
</NButton>
</NCard>
</NFlex>
)
},

View File

@ -23,20 +23,29 @@ import {
} from 'naive-ui'
import { RCollapseGrid, RTable, RIcon, RMoreDropdown } from '@/components'
import { uuid } from '@/utils'
import { useTable } from '@/components'
import type { DataTableColumns } from 'naive-ui'
import type { RTableType } from '@/components'
type RowData = {
key: number
key: number | string
name: string
age: number
address: string
tags: string[]
remark: string
}
const TableView = defineComponent({
name: 'TableView',
setup() {
// 使用以下 hooks 的时候,应该注意调用时机
const [
register,
{ getTableInstance, clearFilters, clearSorter, scrollTo, filters, sort },
] = useTable()
const baseColumns = [
{
title: 'Name',
@ -68,7 +77,7 @@ const TableView = defineComponent({
{
title: 'Remark',
key: 'remark',
width: 300,
width: 100,
},
{
title: 'Action',
@ -98,32 +107,7 @@ const TableView = defineComponent({
const actionColumns = ref<DataTableColumns<RowData>>(
[...baseColumns].map((curr) => ({ ...curr, width: 400 })),
)
const tableData = ref([
{
key: 0,
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
remark: '我是一条很长很长的备注',
},
{
key: 1,
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['wow'],
remark: '我是一条很长很长的备注',
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
remark: '我是一条很长很长的备注',
},
])
const tableData = ref<RowData[]>([])
const tableMenuOptions = [
{
label: '编辑',
@ -140,20 +124,38 @@ const TableView = defineComponent({
tableLoading: false,
})
const handleMenuSelect = (key: string | number) => {
const createTableData = () => {
for (let i = 0; i < 20; i++) {
tableData.value.push({
key: uuid(),
name: 'John Brown',
age: i + 20,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
remark: '我是一条很长很长的备注',
})
}
}
const menuSelect = (key: string | number) => {
window.$message.info(`${key}`)
}
createTableData()
return {
...toRefs(state),
tableData,
actionColumns,
baseColumns,
tableMenuOptions,
handleMenuSelect,
menuSelect,
register,
}
},
render() {
const { register } = this
return (
<NFlex vertical>
<NCard title="RTable">
@ -199,7 +201,8 @@ const TableView = defineComponent({
}}
</RCollapseGrid>
<RTable
scrollX={2000}
onRegister={register.bind(this)}
scrollX={1000}
title={
<NFlex align="center">
<span>:</span>
@ -215,7 +218,7 @@ const TableView = defineComponent({
}}
contextMenuOptions={this.tableMenuOptions}
loading={this.tableLoading}
onContextMenuClick={this.handleMenuSelect.bind(this)}
onContextMenuClick={this.menuSelect.bind(this)}
toolOptions={[
<NPopover>
{{

View File

@ -18,6 +18,14 @@ export default defineConfig((configEnv) =>
environment: 'happy-dom',
globals: true,
poolOptions: {
/**
*
* 如此配置是为避免: Module did not self-register...
* issues:
* @see https://github.com/vitest-dev/vitest/issues/740
*
*
*/
threads: {
maxThreads: 1,
minThreads: 0,