From efd4bb19628c08f2926236794f31b9e7e1d6403d Mon Sep 17 00:00:00 2001 From: Wisdom Date: Tue, 16 Apr 2024 18:24:38 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=AD=20=20feat:=20use=20drawer=20hook?= =?UTF-8?q?=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- components.d.ts | 2 + package.json | 1 + pnpm-lock.yaml | 72 +++++++++++++++++ src/NaiveProvider.vue | 25 +++--- src/components/Drawer/README.md | 36 +++++++++ src/components/Drawer/context.ts | 4 + src/components/Drawer/environment.tsx | 111 +++++++++++++++++++++++++ src/components/Drawer/hook.ts | 6 ++ src/components/Drawer/index.ts | 3 + src/components/Drawer/provider.tsx | 65 +++++++++++++++ src/components/Drawer/type.d.ts | 13 +++ src/modules/Result/pages/overview.vue | 112 +++++++++++++++++++++----- src/types/global.d.ts | 1 + uno.config.ts | 4 +- vite.config.ts | 3 + 16 files changed, 432 insertions(+), 35 deletions(-) create mode 100644 src/components/Drawer/README.md create mode 100644 src/components/Drawer/context.ts create mode 100644 src/components/Drawer/environment.tsx create mode 100644 src/components/Drawer/hook.ts create mode 100644 src/components/Drawer/index.ts create mode 100644 src/components/Drawer/provider.tsx create mode 100644 src/components/Drawer/type.d.ts diff --git a/README.md b/README.md index 0ed5dd1..867cc2b 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ -🏄‍♂️ A Starter template built on Vite 5.x + TypeScript + Vue 3.4 + Naive UI + Pinia + UnoCSS + Unplugin Auto Import. +🏄‍♂️ A Starter template built on Vite 5.x + TypeScript + TSX + Vue 3.4 + Naive UI + Pinia + UnoCSS + Unplugin Auto Import. -一个简洁的 Vite5 + Vue3.4 + TypeScript + ESLint(v9) 的 B 端后台原型 Naive UI 模板框架,内置 Pinia 模块化管理代码、路由鉴权、UnoCSS 暗黑模式、Unplugin 自动导入等, 开箱即用, 注重快速高效搭建实际业务场景, 持续更新最新技术栈 🎊 +一个简洁的 Vite5 + Vue3.4 + TypeScript + TSX + ESLint(v9) 的 B 端后台原型 Naive UI 模板框架,内置 Pinia 模块化管理代码、路由鉴权、UnoCSS 暗黑模式、Unplugin 自动导入等, 开箱即用, 注重快速高效搭建实际业务场景, 持续更新最新技术栈 🎊 [🔥 Live Demo 在线体验](https://pdsuwwz.github.io/vite-naive-template) @@ -33,7 +33,7 @@ ## 🎉 Features -* 支持 __Vite 5 + Vue 3.4 + TypeScript__ +* 支持 __Vite 5 + Vue 3.4 + TypeScript + TSX__ * UI 框架: __Naive UI 2.x__ * 状态管理: __Pinia__ * 单元测试框架: __Vitest__ @@ -42,7 +42,8 @@ * 内置 __ESlint__ 和 __Stylelint__, 可在此基础上扩充你想要的 Lint 配置规范 * 内置封装了一个**可能比较好用的** Axios , 需要时配合 Pinia Actions 一起食用 * 封装了 \ 组件, 可直接使用 IconFont 图标 -* 服务式 service 挂载全局对象 [**window.$ModalXxxx**](https://github.com/pdsuwwz/vite-naive-template/blob/main/src/NaiveProvider.vue#L4-L7) 插件, 更方便的插件调用方式 +* 简化了 naive-ui 库中[抽屉 drawer](https://www.naiveui.com/zh-CN/os-theme/components/drawer) 的创建过程, 支持全局调用 [window.$ModalDrawer.create](./src/components/Drawer/README.md) 方法管理多个抽屉 +* 服务式 service 挂载全局对象 [**window.$ModalXxxx**](https://github.com/pdsuwwz/vite-naive-template/blob/main/src/NaiveProvider.vue#L6-L10) 插件, 更方便的插件调用方式 * 路由鉴权已帮你封装好,同时配合 Nprogress, 只需要修改 [permission.ts](./src/router/permission.ts) 文件即可 * 自带一个模块化的组件开发环境,可按照 modules 目录解耦页面组件、路由组件、样式等文件 * 高度封装但不失灵活,内部抽象出了一个完整的业务流程供你参考,涉及三个核心页面:登录、列表和明细 diff --git a/components.d.ts b/components.d.ts index 9676046..515aa5b 100644 --- a/components.d.ts +++ b/components.d.ts @@ -19,6 +19,7 @@ declare module 'vue' { LayoutView: typeof import('./src/components/Layout/LayoutView.vue')['default'] NavBar: typeof import('./src/components/Navigation/NavBar.vue')['default'] NButton: typeof import('naive-ui')['NButton'] + NCard: typeof import('naive-ui')['NCard'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDatePicker: typeof import('naive-ui')['NDatePicker'] NDialogProvider: typeof import('naive-ui')['NDialogProvider'] @@ -30,6 +31,7 @@ declare module 'vue' { NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] + NSpace: typeof import('naive-ui')['NSpace'] NSpin: typeof import('naive-ui')['NSpin'] NSwitch: typeof import('naive-ui')['NSwitch'] NTooltip: typeof import('naive-ui')['NTooltip'] diff --git a/package.json b/package.json index a11556e..b8083db 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@typescript-eslint/eslint-plugin": "^7.5.0", "@typescript-eslint/parser": "^7.5.0", "@vitejs/plugin-vue": "^5.0.4", + "@vitejs/plugin-vue-jsx": "^3.1.0", "@vitest/coverage-v8": "^1.4.0", "@vue/compiler-sfc": "^3.4.21", "@vue/test-utils": "2.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6818d3..45ec4bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,9 @@ devDependencies: '@vitejs/plugin-vue': specifier: ^5.0.4 version: 5.0.4(vite@5.2.7)(vue@3.4.21) + '@vitejs/plugin-vue-jsx': + specifier: ^3.1.0 + version: 3.1.0(vite@5.2.7)(vue@3.4.21) '@vitest/coverage-v8': specifier: ^1.4.0 version: 1.4.0(vitest@1.4.0) @@ -369,6 +372,13 @@ packages: '@babel/types': 7.24.0 dev: true + /@babel/helper-module-imports@7.22.15: + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.0 + dev: true + /@babel/helper-module-imports@7.24.3: resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} engines: {node: '>=6.9.0'} @@ -2494,6 +2504,22 @@ packages: vue: 3.4.21(typescript@5.4.3) dev: false + /@vitejs/plugin-vue-jsx@3.1.0(vite@5.2.7)(vue@3.4.21): + resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 || ^5.0.0 + vue: ^3.0.0 + dependencies: + '@babel/core': 7.24.3 + '@babel/plugin-transform-typescript': 7.24.1(@babel/core@7.24.3) + '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.3) + vite: 5.2.7(@types/node@20.12.3)(sass@1.72.0) + vue: 3.4.21(typescript@5.4.3) + transitivePeerDependencies: + - supports-color + dev: true + /@vitejs/plugin-vue@5.0.4(vite@5.2.7)(vue@3.4.21): resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2568,6 +2594,47 @@ packages: pretty-format: 29.7.0 dev: true + /@vue/babel-helper-vue-transform-on@1.2.2: + resolution: {integrity: sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==} + dev: true + + /@vue/babel-plugin-jsx@1.2.2(@babel/core@7.24.3): + resolution: {integrity: sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/core': 7.24.3 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.3) + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 + '@vue/babel-helper-vue-transform-on': 1.2.2 + '@vue/babel-plugin-resolve-type': 1.2.2(@babel/core@7.24.3) + camelcase: 6.3.0 + html-tags: 3.3.1 + svg-tags: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@vue/babel-plugin-resolve-type@1.2.2(@babel/core@7.24.3): + resolution: {integrity: sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/core': 7.24.3 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/parser': 7.24.1 + '@vue/compiler-sfc': 3.4.21 + dev: true + /@vue/compiler-core@3.4.21: resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==} dependencies: @@ -2988,6 +3055,11 @@ packages: engines: {node: '>=6'} dev: true + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + /caniuse-lite@1.0.30001605: resolution: {integrity: sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==} dev: true diff --git a/src/NaiveProvider.vue b/src/NaiveProvider.vue index 30805f2..b2cd86a 100644 --- a/src/NaiveProvider.vue +++ b/src/NaiveProvider.vue @@ -1,10 +1,13 @@ diff --git a/src/components/Drawer/README.md b/src/components/Drawer/README.md new file mode 100644 index 0000000..0ea571b --- /dev/null +++ b/src/components/Drawer/README.md @@ -0,0 +1,36 @@ +## $ModalDrawer 示例用法 + +```tsx +const inst = window.$ModalDrawer.create({ + blockScroll: false, + title: '测试标题', + placement: 'left', + width: '70vw', + render: () => { + return
+

点击遮罩/右上角关闭按钮触发 onAsyncMaskClick

+ inst.hide()}>直接关闭 +
+ }, + renderFooter: () => { + return + inst.hide()} + > + 直接关闭 + + + }, + onAsyncMaskClick () { + // 异步处理 + const loading = window.$ModalMessage.loading('异步关闭中') + return new Promise((resolve) => { + setTimeout(() => { + loading.destroy() + resolve() + }, 1000) + }) + } +}) +``` \ No newline at end of file diff --git a/src/components/Drawer/context.ts b/src/components/Drawer/context.ts new file mode 100644 index 0000000..21a5555 --- /dev/null +++ b/src/components/Drawer/context.ts @@ -0,0 +1,4 @@ +import type { InjectionKey } from 'vue' +import { DrawerApiInjection } from './type' + +export const drawerProviderInjectionKey: InjectionKey = Symbol() diff --git a/src/components/Drawer/environment.tsx b/src/components/Drawer/environment.tsx new file mode 100644 index 0000000..bd471f1 --- /dev/null +++ b/src/components/Drawer/environment.tsx @@ -0,0 +1,111 @@ +import { + type DrawerProps, + NDrawer, + NDrawerContent, + drawerProps +} from 'naive-ui' + +interface DrawerExtraTypes { + /** + * 抽屉标题 + */ + title: string + + /** + * 抽屉内容渲染 VNode + * @example render: () => h(NButton) + */ + render?: () => VNode + + /** + * 抽屉底部内容渲染 VNode + * @example renderFooter: () => h(NButton) + */ + renderFooter?: () => VNode + + /** + * 点击遮罩/右上角关闭按钮触发的异步关闭回调 + */ + onAsyncMaskClick?: () => Promise | void +} + +const propsDrawerExtraTypes = { + internalKey: { + type: String, + default: '' + }, + title: { + type: String, + default: '' + }, + render: { + type: Function as PropType<() => VNode>, + default() { + return () =>
+ } + }, + renderFooter: { + type: Function as PropType<() => VNode> + }, + onAsyncMaskClick: { + type: Function + }, + onInternalAfterLeave: { + type: Function, + required: true + } +} + + +export type DrawerCreateOptions = DrawerProps & DrawerExtraTypes + +export const DrawerEnvironment = defineComponent({ + name: 'DrawerEnvironment', + props: { + ...drawerProps, + ...propsDrawerExtraTypes + }, + setup(props) { + + const visible = ref(false) + setTimeout(() => { + visible.value = true + }) + + const hideDrawer = async () => { + visible.value = false + Promise.resolve(() => { + props.onInternalAfterLeave!(props.internalKey) + }) + } + + return { + visible, + hideDrawer + } + }, + render() { + + return { + await this.onAsyncMaskClick?.() + this.hideDrawer() + }} + > + + {{ + default: () => this.render(), + footer: this.renderFooter + ? () => this.renderFooter?.() + : undefined + }} + + + } +}) diff --git a/src/components/Drawer/hook.ts b/src/components/Drawer/hook.ts new file mode 100644 index 0000000..1cd635c --- /dev/null +++ b/src/components/Drawer/hook.ts @@ -0,0 +1,6 @@ +import { drawerProviderInjectionKey } from './context' + +export function useDrawer() { + const drawerInstance = inject(drawerProviderInjectionKey) + return drawerInstance! +} diff --git a/src/components/Drawer/index.ts b/src/components/Drawer/index.ts new file mode 100644 index 0000000..0c136d5 --- /dev/null +++ b/src/components/Drawer/index.ts @@ -0,0 +1,3 @@ +export * from './context' +export * from './hook' +export * from './provider' diff --git a/src/components/Drawer/provider.tsx b/src/components/Drawer/provider.tsx new file mode 100644 index 0000000..6310e04 --- /dev/null +++ b/src/components/Drawer/provider.tsx @@ -0,0 +1,65 @@ +import { drawerProviderInjectionKey } from './context' +import { type DrawerCreateOptions, DrawerEnvironment } from './environment' + +export type InstanceRefType = InstanceType + +export const DrawerProvider = defineComponent({ + setup() { + const drawerList = ref>([]) + const drawerInstRefs: Record = {} + + const create = (options = {} as DrawerCreateOptions) => { + const id = uuidv4() + drawerList.value.push({ + ...options, + id + }) + return { + hide: () => { + drawerInstRefs[id]?.hideDrawer() + } + } + } + + provide(drawerProviderInjectionKey, { + create + }) + + const handleAfterLeave = async (key: string) => { + drawerList.value.splice( + drawerList.value.findIndex((v) => v.id === key), + 1 + ) + } + + return { + drawerList, + drawerInstRefs, + handleAfterLeave + } + }, + render() { + return <> + { + this.drawerList.map((drawerProps) => { + return { + if (!inst) { + delete this.drawerInstRefs[drawerProps.id] + } else { + this.drawerInstRefs[drawerProps.id] = inst + } + } + ) as any} + {...drawerProps} + /> + }) + } + {this.$slots.default?.()} + + } +}) diff --git a/src/components/Drawer/type.d.ts b/src/components/Drawer/type.d.ts new file mode 100644 index 0000000..6106a08 --- /dev/null +++ b/src/components/Drawer/type.d.ts @@ -0,0 +1,13 @@ +import { DrawerCreateOptions } from './environment' + +export interface DrawerInst { + /** + * 直接关闭当前抽屉 + */ + hide: () => void +} + +export interface DrawerApiInjection { + create: (options: DrawerCreateOptions) => DrawerInst +} + diff --git a/src/modules/Result/pages/overview.vue b/src/modules/Result/pages/overview.vue index 303b2ea..fcf3c7b 100644 --- a/src/modules/Result/pages/overview.vue +++ b/src/modules/Result/pages/overview.vue @@ -3,38 +3,110 @@ flex-content :title="overviewData.title" > -
+
+ + 测试 window.$ModalDrawer + {{ overviewData.content }}
-