Compare commits

..

4 Commits
v5.2.1 ... main

Author SHA1 Message Date
XiaoDaiGua-Ray
49af61a339 version: v5.2.2 2025-08-09 19:04:29 +08:00
XiaoDaiGua-Ray
4bce5f7713 feat: 修改版本信息 2025-06-26 17:50:40 +08:00
XiaoDaiGua-Ray
34c20d4be7 fix: 修复一些问题 2025-06-26 17:48:07 +08:00
XiaoDaiGua-Ray
c28c353f7d version: v5.2.1 2025-06-26 17:45:31 +08:00
26 changed files with 2386 additions and 1200 deletions

View File

@ -22,6 +22,7 @@
"cSpell.words": [
"baomitu",
"bezier",
"Cascader",
"Clickoutside",
"codabar",
"commitmsg",
@ -44,6 +45,7 @@
"siderbar",
"snapline",
"stylelint",
"unocss",
"WUJIE",
"zlevel"
]

View File

@ -1,3 +1,38 @@
## 5.2.2
## Feats
- `RForm` 组件相关
- 新增 `submitWhenEnter` 配置项,允许在按下回车键时自动触发表单的校验,如果校验成功则会自动触发 `onFinish` 事件
- 新增 `onFinish` 配置项,允许在表单校验成功后自动触发的事件
- 新增 `autocomplete` 配置项,允许配置表单的自动完成功能,默认配置为 `off`
- 新增 `loading` 配置项,允许配置表单的加载状态
- 新增 `loadingDescription` 配置项,允许配置表单的加载状态的描述
- `useForm` 相关
- 新增 `validateTargetField` 方法,允许验证指定表单项的规则
- 初始化方法现在支持传入函数,允许动态获取表单的初始化值与规则
- `formModel` 方法现在会默认联合 `Recordable` 类型,获取初始化类型中未获取到的类型时,默认推到为 `any` 类型
- 新增了 `formConditionRef` 属性,现在可以在内部解构获取一个 `ref` 包裹的响应式初始化表单对象值
- 新增了 `updateFormCondition` 方法,允许更新表单的值,该方法会覆盖初始化值
- 更新依赖为主流版本
- 新增 `unocss` 原子化样式库,但是不推荐全量使用,仅作为一些简单的样式片段使用,否则在调试的时候将会是灾难
> 新增 `unocss` 后,在使用 `ProTable` 组件的流体高度最外层父元素配置时,可以便捷的配置 `h-full` 即可。
## 5.2.1
## Feats
- `RTablePro` 组件相关
- 新增 `runAsyncTableRequest` 方法,与 `runTableRequest` 方法功能一致,但是返回 `Promise` 对象
- 现在不允许使用 `useTemplateRef` 方法注册 `dom` 模板引用,约定强制使用 `useTablePro` 方法的 `register` 方法注册 `hook` 使用相关方法
- `useTablePro` 方法新增 `getTableProConfig` 方法,与 `useTable` 方法的 `getTableConfig` 方法功能一致,获取 `RTablePro` 组件额外注入配置
- `useTable` 方法新增 `getTableConfig` 方法,获取 `RTable` 组件额外注入配置
- 更新包为主流版本
- `vue-router` 因为在 [4.4.1](https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#441-2024-07-31) 版本中有破坏性的更新,所以在 `jsx` 函数式组件使用 `this.$route`, `this.$router` 会提示类型报错,所以现在强制约定需要使用 `useRoute`, `useRouter` 方法显示的声明与使用
- 更新 `naive-ui` 版本至 `2.42.0`
- 更新 `vue` 版本至 `3.5.17`
- `useForm` 方法新增 `reset` 方法,允许重置表单值,该方法依赖 `useForm` 方法的初始化 `formModel` 参数,所以请确保初始化 `formModel` 参数
## 5.2.0
一些破坏性更新,请谨慎更新。

View File

@ -1,7 +1,7 @@
{
"name": "ray-template",
"private": false,
"version": "5.1.0",
"version": "5.2.2",
"type": "module",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0",
@ -35,67 +35,66 @@
"dependencies": {
"@logicflow/core": "2.0.10",
"@logicflow/extension": "2.0.14",
"@vueuse/core": "^12.4.0",
"axios": "^1.7.9",
"@vueuse/core": "^13.1.0",
"axios": "^1.9.0",
"clipboard": "^2.0.11",
"crypto-js": "4.2.0",
"currency.js": "^2.0.4",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"html-to-image": "1.11.11",
"html-to-image": "1.11.13",
"interactjs": "1.10.27",
"jsbarcode": "3.11.6",
"lodash-es": "^4.17.21",
"mockjs": "1.1.0",
"naive-ui": "^2.41.0",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"naive-ui": "^2.42.0",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"print-js": "^1.6.0",
"vue": "^3.5.16",
"vue": "^3.5.17",
"vue-demi": "0.14.10",
"vue-hooks-plus": "2.2.3",
"vue-hooks-plus": "2.4.0",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.0",
"vue3-next-qrcode": "2.0.10"
"vue-router": "^4.5.1",
"vue3-next-qrcode": "3.0.2"
},
"devDependencies": {
"@amap/amap-jsapi-types": "0.0.15",
"@ant-design/icons-vue": "7.0.1",
"@commitlint/cli": "19.3.0",
"@commitlint/config-conventional": "19.2.2",
"@commitlint/cli": "19.7.1",
"@commitlint/config-conventional": "19.7.1",
"@eslint/js": "9.28.0",
"@interactjs/types": "1.10.27",
"@intlify/unplugin-vue-i18n": "4.0.0",
"@types/crypto-js": "4.2.2",
"@types/jsbarcode": "3.11.4",
"@types/lodash-es": "4.17.12",
"@types/mockjs": "1.0.10",
"@types/three": "0.171.0",
"@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/parser": "8.20.0",
"@vitejs/plugin-vue": "5.2.1",
"@vitejs/plugin-vue-jsx": "4.1.1",
"@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.24.0",
"@vitejs/plugin-vue": "5.2.3",
"@vitejs/plugin-vue-jsx": "4.1.2",
"@vitest/ui": "2.1.8",
"@vue/eslint-config-prettier": "10.1.0",
"@vue/eslint-config-typescript": "14.2.0",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.20",
"autoprefixer": "10.4.21",
"depcheck": "1.4.7",
"eslint": "9.18.0",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-prettier": "5.2.2",
"eslint": "9.20.1",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-prettier": "5.2.6",
"eslint-plugin-vue": "9.32.0",
"globals": "15.14.0",
"happy-dom": "16.6.0",
"globals": "16.0.0",
"happy-dom": "17.1.0",
"husky": "8.0.3",
"lint-staged": "15.3.0",
"postcss": "8.5.1",
"lint-staged": "15.4.3",
"postcss": "8.5.4",
"postcss-px-to-viewport-8-with-include": "1.2.2",
"prettier": "3.4.2",
"prettier": "3.5.3",
"rollup-plugin-gzip": "4.0.1",
"sass": "1.83.4",
"sass": "1.86.3",
"svg-sprite-loader": "6.0.11",
"typescript": "5.6.3",
"unplugin-auto-import": "19.0.0",
"typescript": "5.8.3",
"unocss": "66.3.3",
"unplugin-auto-import": "19.1.2",
"unplugin-vue-components": "0.28.0",
"vite": "6.3.5",
"vite-bundle-analyzer": "0.16.0",
@ -107,7 +106,8 @@
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "5.1.0",
"vitest": "2.1.8",
"vue-tsc": "2.2.0"
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.2.8"
},
"description": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->",
"main": "index.ts",

2732
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,41 @@
import { NForm } from 'naive-ui'
import { NForm, NSpin } from 'naive-ui'
import props from './props'
import { call } from '@/utils'
import { call, unrefElement } from '@/utils'
import { useTemplateRef } from 'vue'
import { useEventListener } from '@vueuse/core'
import type { RFormInst } from './types'
import type { FormProps } from 'naive-ui'
import type { ShallowRef } from 'vue'
export default defineComponent({
name: 'RForm',
props,
setup(props, { expose }) {
const formRef = useTemplateRef<RFormInst>('formRef')
const currentSubmitFn = computed(() => props.onFinish ?? Promise.resolve)
const bindKeydownListener = (e: KeyboardEvent) => {
const keyCode = e.code
if (keyCode === 'Enter') {
e.stopPropagation()
e.preventDefault()
formRef.value?.validate().then(currentSubmitFn.value)
}
}
if (props.submitWhenEnter) {
useEventListener(
formRef as unknown as ShallowRef<HTMLElement>,
'keydown',
bindKeydownListener,
{
capture: true,
},
)
}
onMounted(() => {
// 主动调用 register 方法,满足 useForm 方法正常调用
@ -20,6 +44,16 @@ export default defineComponent({
if (onRegister && formRef.value) {
call(onRegister, formRef.value)
}
if (formRef.value) {
const formElement = unrefElement(
formRef.value as unknown as HTMLFormElement,
)
if (formElement) {
formElement.autocomplete = props.autocomplete
}
}
})
expose()
@ -30,13 +64,22 @@ export default defineComponent({
},
render() {
const { $attrs, $props, $slots } = this
const { loading, loadingDescription, ...restProps } = $props
return (
<NForm {...$attrs} {...($props as FormProps)} ref="formRef">
{{
...$slots,
<NSpin
show={loading}
description={loadingDescription}
style={{
height: 'auto',
}}
</NForm>
>
<NForm {...$attrs} {...restProps} ref="formRef">
{{
...$slots,
}}
</NForm>
</NSpin>
)
},
})

View File

@ -40,10 +40,11 @@ const useForm = <
T extends Recordable = Recordable,
R extends RFormRules = RFormRules,
>(
model?: T,
rules?: R,
model?: T | (() => T),
rules?: R | (() => R),
) => {
const formRef = shallowRef<RFormInst>()
const formModelRef = ref<T>()
const register = (inst: RFormInst) => {
if (inst) {
@ -61,6 +62,15 @@ const useForm = <
return formRef.value
}
// 初始化 formModelRef 的值,根据 model 的类型进行初始化
const initialFormModel = () => {
if (typeof model === 'function') {
formModelRef.value = model() ?? ({} as T)
} else {
formModelRef.value = cloneDeep(model) ?? ({} as T)
}
}
/**
*
* @description
@ -86,10 +96,39 @@ const useForm = <
*
* @description
*
*
* useForm model
*
* vue
* Form
*
* 5.2.2 formConditionRef ref
* hook
*
* @example
*
* interface FormModel {
* name: string | null
* age: number | null
* }
*
* const [register, { formModel }] = useForm<FormModel>({
* name: null,
* age: null,
* })
*
* const formModelRef = ref(formModel())
*
* const reset = () => {
* formModelRef.value = formModel()
* }
*/
const formModel = () => cloneDeep(model) || ({} as T)
const formModel = (): T & Recordable => {
if (typeof model === 'function') {
return model()
}
return cloneDeep(model) || ({} as T)
}
/**
*
@ -98,7 +137,103 @@ const useForm = <
*
* useForm rules
*/
const formRules = () => cloneDeep(rules) || ({} as R)
const formRules = () => {
if (typeof rules === 'function') {
return rules()
}
return cloneDeep(rules) || ({} as R)
}
/**
*
* @param values
*
* @warning
* undefined
* reset 使 null
*
* @description
* useForm
*
*
*
* useForm
*
*/
const reset = <Values extends T = T>(values?: Values & Recordable) => {
formModelRef.value = Object.assign(
formModelRef.value as T,
formModel(),
values,
)
restoreValidation()
}
/**
*
* @param key key
*
* @see https://www.naiveui.com/zh-CN/dark/components/form#partially-apply-rules.vue
*
* @description
*
*
* rules key
*
*
* @example
* const [register, { validateTargetField }] = useForm(
* {
* name: null,
* },
* {
* name: {
* required: true,
* message: 'name is required',
* trigger: ['blur', 'change'],
* type: 'string',
* key: 'name',
* },
* },
* )
*
* validateTargetField('name')
*/
const validateTargetField = (key: string) => {
if (!key || typeof key !== 'string') {
throw new Error(
`[useForm-validateTargetField]: except key is string, but got ${typeof key}.`,
)
}
return validate(void 0, (rules) => {
return rules?.key === key
})
}
/**
*
* @description
*
* 使
*
* @example
* const [register, { updateFormCondition }] = useForm(
* {
* name: null,
* },
* )
*
* updateFormCondition({
* name: 'John',
* })
*/
const updateFormCondition = (values: T & Recordable) => {
formModelRef.value = Object.assign(formModelRef.value as T, values)
}
initialFormModel()
return [
register,
@ -108,6 +243,10 @@ const useForm = <
restoreValidation,
formModel,
formRules,
reset,
validateTargetField,
formConditionRef: formModelRef as Ref<T>,
updateFormCondition,
},
] as const
}

View File

@ -1,10 +1,75 @@
import { formProps } from 'naive-ui'
import { omit } from 'lodash-es'
import type { MaybeArray } from '@/types'
import type { MaybeArray, AnyFC } from '@/types'
import type { RFormInst } from './types'
const props = {
...formProps,
...omit(formProps, ['onSubmit']),
/**
*
* @description
*
*
* @default false
*/
loading: {
type: Boolean,
default: false,
},
/**
*
* @description
*
*
* @default undefined
*/
loadingDescription: {
type: String,
default: void 0,
},
/**
*
* @description
*
*
* @default 'off'
*/
autocomplete: {
type: String as PropType<AutoFillBase>,
default: 'off',
},
/**
*
* @description
* onFinish
* onFinish 使
*
*
*
*
* Enter
* NSelect, NInput
*
*
* @default false
*/
submitWhenEnter: {
type: Boolean,
default: false,
},
/**
*
* @description
* submitWhenEnter true
* submitWhenEnter 使
*
* @default null
*/
onFinish: {
type: Function as PropType<AnyFC>,
default: null,
},
/**
*
* @description

View File

@ -149,9 +149,23 @@ const useTable = () => {
}
}
/**
*
* @description
*
*
* @see https://www.naiveui.com/zh-CN/dark/components/data-table#DataTable-Methods
*/
const filter = (filters: FilterState | null) =>
getTableInstance().filter.call(null, filters)
/**
*
* @description
* Table
*/
const getTableConfig = () => extraRef.value
return [
register,
{
@ -165,7 +179,7 @@ const useTable = () => {
sort,
print,
filter,
config: extraRef,
getTableConfig,
},
] as const
}

View File

@ -31,8 +31,9 @@ import type { TransitionProps } from './types'
/**
*
* 使用宏编译模式时可以使用 defineOptions 声明组件选项
* 常用方法即是声明该组件的 name inheritAttrs 等属性
* @description
* 使用宏编译模式时可以使用 defineOptions 声明组件选项
* 常用方法即是声明该组件的 name inheritAttrs 等属性
*/
defineOptions({
name: 'RTransitionComponent',

View File

@ -5,6 +5,26 @@ import collapseGridProps from '../../base/RCollapseGrid/src/props'
import type { GridProps } from 'naive-ui'
export const collapseProps = Object.assign({}, formProps, {
...collapseGridProps,
open: {
type: Boolean,
default: true,
},
cols: {
type: [Number, String] as PropType<GridProps['cols']>,
default: '4 xs:1 s:2 m:2 l:4 xl:4 2xl:6',
},
bordered: {
type: Boolean,
default: true,
},
responsive: {
type: String as PropType<GridProps['responsive']>,
default: 'screen',
},
})
/**
*
* @description
@ -13,25 +33,7 @@ import type { GridProps } from 'naive-ui'
*/
export default defineComponent({
name: 'RCollapse',
props: Object.assign({}, formProps, {
...collapseGridProps,
open: {
type: Boolean,
default: true,
},
cols: {
type: [Number, String] as PropType<GridProps['cols']>,
default: '4 xs:1 s:2 m:2 l:4 xl:4 2xl:6',
},
bordered: {
type: Boolean,
default: true,
},
responsive: {
type: String as PropType<GridProps['responsive']>,
default: 'screen',
},
}),
props: collapseProps,
render() {
const { $slots, $props } = this
const { labelPlacement, showFeedback, ...rest } = $props

View File

@ -3,9 +3,23 @@ import tableProProps from './src/props'
import { useTablePro } from './src/hooks/useTablePro'
import type { ExtractPropTypes } from 'vue'
import type {
BasePagination,
TablePagination,
FormatRangeTime,
TableRequestConfig,
TableProFieldNames,
} from './src/types'
type TableProProps = ExtractPropTypes<typeof tableProProps>
export type { TableProProps }
export type {
TableProProps,
BasePagination,
TablePagination,
FormatRangeTime,
TableRequestConfig,
TableProFieldNames,
}
export { RTablePro, useTablePro, tableProProps }

View File

@ -7,11 +7,13 @@ import { usePagination } from '@/hooks'
import { omit } from 'lodash-es'
import type { TablePagination, TableRequestConfig, TableProInst } from './types'
import type { RTableInst } from '../../..'
export default defineComponent({
name: 'RTablePro',
props,
setup(props) {
setup(props, ctx) {
const { expose } = ctx
const [register, tableFns] = useTable()
const [
paginationRef,
@ -92,9 +94,9 @@ export default defineComponent({
return requestParams
}
// 会重置 pagination 的请求,默认会重置
const runResetPaginationRequest = (
extraConfig?: TableRequestConfig,
// 同步执行 request 请求,允许重置 pagination 请求,返回 Promise 对象
const runResetPaginationRequest: TableProInst['runTableRequest'] = (
extraConfig,
reset = true,
) => {
if (reset) {
@ -106,6 +108,20 @@ export default defineComponent({
tableRequestRef.value?.(requestParams)
}
// 异步执行 request 请求,允许重置 pagination 请求,返回 Promise 对象
const runResetPaginationRequestAsync: TableProInst['runAsyncTableRequest'] =
(extraConfig, reset = true) => {
return new Promise((resolve, reject) => {
try {
runResetPaginationRequest(extraConfig, reset)
resolve(void 0)
} catch (e) {
reject(e)
}
})
}
watchEffect(() => {
setItemCount(props.paginationCount)
setCallback(() => {
@ -124,14 +140,16 @@ export default defineComponent({
if (onRegister) {
call(onRegister, {
...tableFns,
...(tableFns as unknown as RTableInst),
getTablePagination: update,
runTableRequest: runResetPaginationRequest,
runAsyncTableRequest: runResetPaginationRequestAsync,
getCurrentTableRequestParams: combineRequestParams,
resetTablePagination: resetPagination,
} as unknown as TableProInst)
})
}
})
expose()
return {
register,

View File

@ -195,6 +195,29 @@ export const useTablePro = () => {
const resetTablePagination = () =>
getTableProInstance().resetTablePagination.call(null)
/**
*
* @param extraConfig
* @param reset
*
* @description
*
*/
const runAsyncTableRequest = <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => getTableProInstance().runAsyncTableRequest.call(null, extraConfig, reset)
/**
*
* @description
* TablePro
*/
const getTableProConfig = () => getTableProInstance().config
return [
register,
{
@ -211,6 +234,8 @@ export const useTablePro = () => {
print,
getCurrentTableRequestParams,
resetTablePagination,
runAsyncTableRequest,
getTableProConfig,
},
] as const
}

View File

@ -4,9 +4,9 @@ import { omit } from 'lodash-es'
import type { PropType } from 'vue'
import type {
TableProInst,
TablePagination,
TableRequestConfig,
PaginationPrefix,
TablePaginationUpdate,
} from './types'
import type { AnyFC } from '@/types'
@ -67,7 +67,7 @@ const props = {
* @default undefined
*/
onTablePaginationUpdate: {
type: Function as PropType<(pagination: TablePagination) => void>,
type: Function as PropType<TablePaginationUpdate>,
},
/**
*

View File

@ -17,16 +17,20 @@ export type FormatRangeTime = {
target: [string | number, string | number]
}
export interface BasePagination {
page: number
pageSize: number
itemCount: number
}
/**
*
* @description
* Pagination
*/
export interface TablePagination {
page: number
pageSize: number
itemCount: number
}
export type TablePagination = BasePagination
export type TablePaginationUpdate = (pagination: TablePagination) => void
export type PaginationPrefix = UsePaginationOptions['prefix']
@ -100,6 +104,30 @@ export interface TableProInst extends Omit<RTableInst, 'getTableInstance'> {
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => void
/**
*
* @param extraConfig
* @param reset
*
* @description
*
*
* @example
* const [register, { runAsyncTableRequest }] = useTablePro()
*
* // 重置分页请求
* runAsyncTableRequest(void 0, true)
* runAsyncTableRequest()
* // 不重置分页请求
* runAsyncTableRequest(void 0, false)
*/
runAsyncTableRequest: <
T extends Recordable,
ExcludeParams extends keyof T = keyof T,
>(
extraConfig?: TableRequestConfig<T, ExcludeParams>,
reset?: boolean,
) => Promise<void>
/**
*
* @param extraConfig
@ -132,3 +160,24 @@ export interface TableProInst extends Omit<RTableInst, 'getTableInstance'> {
*/
resetTablePagination: () => void
}
export interface TableProFieldNames {
/**
*
* @description
*
*/
page: string
/**
*
* @description
*
*/
pageSize: string
/**
*
* @description
*
*/
itemCount: string
}

View File

@ -41,15 +41,16 @@ const throttleDirective: CustomDirectiveFC<
throttleFunction = throttle(func, wait, Object.assign({}, options))
useEventListener(el, trigger, throttleFunction)
cleanup = useEventListener(el, trigger, throttleFunction)
},
beforeUnmount: () => {
if (throttleFunction) {
throttleFunction.cancel()
cleanup?.()
}
throttleFunction = null
cleanup?.()
},
}
}

View File

@ -5,6 +5,7 @@ import './app-components/provider/provider.scss' // 初始化 provider 包注入
import 'vue3-next-qrcode/es/style.css' // vue3-next-qrcode 样式
import 'virtual:svg-icons-register' // vite-plugin-svg-icons 脚本,启用 svg 雪碧图
import 'virtual:uno.css'
import { setupRouter } from './router'
import { setupStore } from './store'

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type RecordKey = string | number | symbol
export type RecordKey = string | number | symbol
/**
*

View File

@ -4,7 +4,7 @@ import type { useRoute, useRouter } from 'vue-router'
// 为 vue 添加一些自定义属性
// https://cn.vuejs.org/guide/typescript/options-api#augmenting-global-properties
declare module 'vue' {
declare module '*.vue' {
interface ComponentCustomProperties {
$router: ReturnType<typeof useRouter>
$route: ReturnType<typeof useRoute>

View File

@ -9,11 +9,21 @@ import {
NButton,
NRadio,
NRadioGroup,
NCard,
NText,
NSwitch,
} from 'naive-ui'
import { useForm, useModal } from '@/components'
import { useForm } from '@/components'
import { useHookPlusRequest } from '@/axios'
import type { RFormRules } from '@/components'
interface FormModel {
name: string | null
age: number | null
gender: string | null
date: Date | null
remark: string | null
}
export default defineComponent({
name: 'RFormDemo',
@ -21,8 +31,15 @@ export default defineComponent({
// 使用以下 hooks 的时候,应该注意调用时机
const [
register,
{ getFormInstance, validate, restoreValidation, formModel, formRules },
] = useForm(
{
validate,
restoreValidation,
formRules,
reset,
validateTargetField,
formConditionRef,
},
] = useForm<FormModel>(
{
name: null,
age: null,
@ -35,6 +52,7 @@ export default defineComponent({
required: true,
message: '请输入姓名',
trigger: ['blur', 'change'],
key: 'name',
},
date: {
required: true,
@ -56,93 +74,125 @@ export default defineComponent({
},
)
/**
*
* @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())
const formLoading = ref(false)
const { run: runHookPlusRequest } = useHookPlusRequest(
() => {
return new Promise((resolve, reject) => {
validate()
.then(() => {
formLoading.value = true
setTimeout(() => {
window.$message.success('校验成功')
resolve(true)
}, 500)
})
.catch(reject)
.finally(() => {
formLoading.value = false
})
})
},
{
manual: true,
},
)
return {
register,
rules,
condition,
formConditionRef,
restoreValidation,
formModel,
validate,
formRules,
validateTargetField,
reset,
formLoading,
runHookPlusRequest,
}
},
render() {
const { rules } = this
const { register, restoreValidation, formModel, validate } = this
const { formConditionRef } = this
const {
register,
restoreValidation,
formRules,
validateTargetField,
reset,
runHookPlusRequest,
} = 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()
restoreValidation()
}}
>
</NButton>
<NButton type="warning" onClick={restoreValidation.bind(this)}>
</NButton>
<NButton type="primary" onClick={() => validate()}>
</NButton>
</NFlex>
</NFormItemGi>
</NGrid>
</RForm>
<NCard
title={() => (
<NFlex align="center">
<NText>useForm </NText>
<NSwitch v-model:value={this.formLoading} />
</NFlex>
)}
>
{{
default: () => (
<RForm
onRegister={register}
rules={formRules()}
model={formConditionRef}
submitWhenEnter
onFinish={() => {
window.$message.success('表单提交成功')
}}
loading={this.formLoading}
>
<NGrid cols={24} xGap={24}>
<NFormItemGi label="姓名" path="name" span={12}>
<NInput v-model:value={formConditionRef.name} />
</NFormItemGi>
<NFormItemGi label="年龄" path="age" span={12}>
<NInputNumber
v-model:value={formConditionRef.age}
showButton={false}
/>
</NFormItemGi>
<NFormItemGi label="出生日期" path="date" span={12}>
<NDatePicker v-model:value={formConditionRef.date} />
</NFormItemGi>
<NFormItemGi label="性别" path="gender" span={12}>
<NRadioGroup v-model:value={formConditionRef.gender}>
<NRadio value="girl"></NRadio>
<NRadio value="man"></NRadio>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi label="备注信息" span={24}>
<NInput
type="textarea"
v-model:value={formConditionRef.remark}
/>
</NFormItemGi>
<NFormItemGi span={24}>
<NFlex>
<NButton type="info" onClick={() => reset()}>
</NButton>
<NButton type="warning" onClick={restoreValidation}>
</NButton>
<NButton
type="primary"
onClick={() => validateTargetField('name')}
>
</NButton>
<NButton type="primary" onClick={runHookPlusRequest}>
</NButton>
</NFlex>
</NFormItemGi>
</NGrid>
</RForm>
),
'header-extra': () => '输入表单的时候,试试按下 Enter 键',
}}
</NCard>
)
},
})

View File

@ -2,11 +2,20 @@ import { NCard, NFlex } from 'naive-ui'
const RouterDemoDetail = defineComponent({
name: 'RouterDemoDetail',
setup() {
const route = useRoute()
return {
route,
}
},
render() {
const { route } = this
return (
<NFlex>
<NCard title={(this.$route.query.name as string) || 'hello'}>
{this.$route.query.name}
<NCard title={(route.query.name as string) || 'hello'}>
{route.query.name}
</NCard>
<NCard title="平层路由详情页面"></NCard>
<NCard title="TIP">

View File

@ -19,20 +19,19 @@ import { uuid } from '@/utils'
import { useHookPlusRequest } from '@/axios'
import Mock from 'mockjs'
import dayjs from 'dayjs'
import { useTablePro } from '@/components'
import { useCheckedRowKeys } from '@/components'
import { useCheckedRowKeys, useTablePro, useForm } from '@/components'
import { useDayjs } from '@/hooks'
import type { DataTableColumns } from 'naive-ui'
type RowData = {
key: number | string
name: string
name: string | null
age: number
address: string
tags: string[]
remark: string
status: string
status: string | null
statusText: string
signTimeEnd: number
signTimeStart: number
@ -100,6 +99,11 @@ export default defineComponent({
setup() {
const { format } = useDayjs()
const [register, { formModel, reset }] = useForm<ParamsRef>({
RangeTime: null,
name: null,
status: null,
})
/**
*
* @description
@ -109,7 +113,13 @@ export default defineComponent({
*/
const [
tableProRegister,
{ runTableRequest, getCurrentTableRequestParams, print, downloadCsv },
{
runTableRequest,
getCurrentTableRequestParams,
print,
downloadCsv,
runAsyncTableRequest,
},
] = useTablePro()
// 表格数据
const tableDataRef = ref<RowData[]>([])
@ -163,7 +173,7 @@ export default defineComponent({
// 表格分页数据
const itemCountRef = ref(0)
// 查询条件
const conditionRef = ref<ParamsRef>({})
const conditionRef = ref<ParamsRef>(formModel())
// 缓存模拟数据,不用关心
const mockPersonList = ref<RowData[]>(
(() => {
@ -227,7 +237,7 @@ export default defineComponent({
const defaultLength = mockPersonList.value.length
if (name) {
list = list.filter((curr) => curr.name.includes(name))
list = list.filter((curr) => curr.name!.includes(name))
}
if (status) {
@ -294,6 +304,8 @@ export default defineComponent({
clearAll,
collapseRef,
selectKey,
register,
reset,
}
},
render() {
@ -315,11 +327,13 @@ export default defineComponent({
clearKey,
clearAll,
selectKey,
register,
reset,
} = this
return (
<NFlex vertical>
<RCollapse open={this.collapseRef} bordered>
<RCollapse open={this.collapseRef} onRegister={register}>
{{
default: () => (
<>
@ -356,6 +370,7 @@ export default defineComponent({
),
action: () => (
<NFlex>
<NButton onClick={() => reset(this.conditionRef)}></NButton>
<NButton
type="primary"
onClick={() => runTableRequest()}

5
uno.config.ts Normal file
View File

@ -0,0 +1,5 @@
import { defineConfig } from 'unocss'
export default defineConfig({
safelist: ['w-full', 'h-full'],
})

View File

@ -86,6 +86,9 @@
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true
"useTemplateRef": true,
"Slot": true,
"Slots": true,
"NFormItem": true
}
}

View File

@ -7,6 +7,7 @@
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const NFormItem: typeof import('naive-ui')['NFormItem']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
@ -83,6 +84,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

View File

@ -13,6 +13,7 @@ import viteEslint from 'vite-plugin-eslint'
import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import unpluginViteComponents from 'unplugin-vue-components/vite'
import unoCSS from 'unocss/vite'
import { cdn as viteCDNPlugin } from 'vite-plugin-cdn2'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
@ -212,6 +213,7 @@ function baseOptions(mode: string): PluginOption[] {
inject: 'body-last',
customDomId: '__svg__icons__dom__',
}),
unoCSS(),
]
}