diff --git a/packages/devui-vue/devui-cli/commands/build.js b/packages/devui-vue/devui-cli/commands/build.js
index c9f61a08a6..337e15b34d 100644
--- a/packages/devui-vue/devui-cli/commands/build.js
+++ b/packages/devui-vue/devui-cli/commands/build.js
@@ -1,28 +1,28 @@
-const path = require('path')
-const fs = require('fs')
-const fsExtra = require('fs-extra')
-const { defineConfig, build } = require('vite')
-const vue = require('@vitejs/plugin-vue')
-const vueJsx = require('@vitejs/plugin-vue-jsx')
-const nuxtBuild = require('./build-nuxt-auto-import')
+const path = require('path');
+const fs = require('fs');
+const fsExtra = require('fs-extra');
+const { defineConfig, build } = require('vite');
+const vue = require('@vitejs/plugin-vue');
+const vueJsx = require('@vitejs/plugin-vue-jsx');
+const nuxtBuild = require('./build-nuxt-auto-import');
-const entryDir = path.resolve(__dirname, '../../devui')
-const outputDir = path.resolve(__dirname, '../../build')
+const entryDir = path.resolve(__dirname, '../../devui');
+const outputDir = path.resolve(__dirname, '../../build');
const baseConfig = defineConfig({
configFile: false,
publicDir: false,
- plugins: [vue(), vueJsx()]
-})
+ plugins: [vue(), vueJsx()],
+});
const rollupOptions = {
- external: ['vue', 'vue-router'],
+ external: ['vue', 'vue-router', '@vueuse/core'],
output: {
globals: {
- vue: 'Vue'
- }
- }
-}
+ vue: 'Vue',
+ },
+ },
+};
const buildSingle = async (name) => {
await build(
@@ -34,13 +34,13 @@ const buildSingle = async (name) => {
entry: path.resolve(entryDir, name),
name: 'index',
fileName: 'index',
- formats: ['es', 'umd']
+ formats: ['es', 'umd'],
},
- outDir: path.resolve(outputDir, name)
- }
+ outDir: path.resolve(outputDir, name),
+ },
})
- )
-}
+ );
+};
const buildAll = async () => {
await build(
@@ -52,13 +52,13 @@ const buildAll = async () => {
entry: path.resolve(entryDir, 'vue-devui.ts'),
name: 'VueDevui',
fileName: 'vue-devui',
- formats: ['es', 'umd']
+ formats: ['es', 'umd'],
},
- outDir: outputDir
- }
+ outDir: outputDir,
+ },
})
- )
-}
+ );
+};
const createPackageJson = (name) => {
const fileStr = `{
@@ -67,25 +67,25 @@ const createPackageJson = (name) => {
"main": "index.umd.js",
"module": "index.es.js",
"style": "style.css"
-}`
+}`;
- fsExtra.outputFile(path.resolve(outputDir, `${name}/package.json`), fileStr, 'utf-8')
-}
+ fsExtra.outputFile(path.resolve(outputDir, `${name}/package.json`), fileStr, 'utf-8');
+};
exports.build = async () => {
- await buildAll()
+ await buildAll();
const components = fs.readdirSync(entryDir).filter((name) => {
- const componentDir = path.resolve(entryDir, name)
- const isDir = fs.lstatSync(componentDir).isDirectory()
- return isDir && fs.readdirSync(componentDir).includes('index.ts')
- })
+ const componentDir = path.resolve(entryDir, name);
+ const isDir = fs.lstatSync(componentDir).isDirectory();
+ return isDir && fs.readdirSync(componentDir).includes('index.ts');
+ });
for (const name of components) {
- await buildSingle(name)
- createPackageJson(name)
- nuxtBuild.createAutoImportedComponent(name)
+ await buildSingle(name);
+ createPackageJson(name);
+ nuxtBuild.createAutoImportedComponent(name);
}
- nuxtBuild.createNuxtPlugin()
-}
+ nuxtBuild.createNuxtPlugin();
+};
diff --git a/packages/devui-vue/devui/drawer/index.ts b/packages/devui-vue/devui/drawer/index.ts
index c0ac81fb9b..f0b0a21e7d 100644
--- a/packages/devui-vue/devui/drawer/index.ts
+++ b/packages/devui-vue/devui/drawer/index.ts
@@ -1,25 +1,21 @@
-import type { App } from 'vue'
-import Drawer from './src/drawer'
-import DrawerService from './src/drawer-service'
+import type { App } from 'vue';
+import Drawer from './src/drawer';
+import DrawerService from './src/drawer-service';
-Drawer.install = function(app: App): void {
- app.component(Drawer.name, Drawer)
-}
-
-export { Drawer, DrawerService }
+export { Drawer, DrawerService };
// TODO: no-service model exists memory leak
// rest tasks
-// 1. draggable width
-// 2. function of the 1st icon in header-component
-// 3. rest service-model api
+// 1. draggable width
+// 2. function of the 1st icon in header-component
+// 3. rest service-model api
// 4. typescript type of props
export default {
title: 'Drawer 抽屉板',
category: '反馈',
status: '75%',
install(app: App): void {
- app.use(Drawer as any)
- app.config.globalProperties.$drawerService = DrawerService
- }
-}
+ app.component(Drawer.name, Drawer);
+ app.config.globalProperties.$drawerService = new DrawerService();
+ },
+};
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-body.scss b/packages/devui-vue/devui/drawer/src/components/drawer-body.scss
deleted file mode 100644
index 7aaba13f43..0000000000
--- a/packages/devui-vue/devui/drawer/src/components/drawer-body.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-@import '../../../style/devui.scss';
-
-.devui-drawer {
- position: fixed;
- top: 0;
- left: 0;
- height: 100vh;
-}
-
-.devui-overlay-wrapper {
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- top: 0;
- bottom: 0;
- width: 100vw;
-}
-
-.devui-overlay-backdrop {
- position: absolute;
- top: 0;
- left: 0;
- background: $devui-shadow;
- width: 100vw;
- height: 100vh;
-}
-
-.devui-drawer-nav {
- position: absolute;
- top: 0;
- bottom: 0;
- border-radius: $devui-border-radius;
- background: $devui-base-bg;
-}
-
-.devui-drawer-content {
- border-radius: $devui-border-radius;
- overflow: auto;
- box-shadow: $devui-shadow-length-fullscreen-overlay $devui-shadow;
- padding: 20px;
- height: 100vh;
-}
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-body.tsx b/packages/devui-vue/devui/drawer/src/components/drawer-body.tsx
deleted file mode 100644
index 6fba8f93e9..0000000000
--- a/packages/devui-vue/devui/drawer/src/components/drawer-body.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { defineComponent, inject, computed, Transition } from 'vue'
-
-import './drawer-body.scss'
-
-export default defineComponent({
- name: 'DrawerBody',
- setup(props, { slots }) {
- const isFullScreen: any = inject('isFullScreen')
- const closeDrawer: any = inject('closeDrawer')
- const zindex: number = inject('zindex')
- const isCover: boolean = inject('isCover')
- const position: any = inject('position')
- const width: any = inject('width')
- const visible: boolean = inject('visible')
- const backdropCloseable: any = inject('backdropCloseable')
- const destroyOnHide: any = inject('destroyOnHide')
- const showAnimation: any = inject('showAnimation')
-
- const navRight = computed(() => position.value === 'right' ? { 'right': 0 } : { 'left': 0 })
- const navWidth = computed(() => isFullScreen.value ? '100vw' : width.value)
-
- const clickContent = (e) => {
- e.stopPropagation()
- }
-
- const handleDrawerClose = () => {
- if (!backdropCloseable.value) return;
- closeDrawer();
- }
-
- return {
- zindex,
- slots,
- isCover,
- navRight,
- navWidth,
- visible,
- position,
- showAnimation,
- clickContent,
- handleDrawerClose,
- destroyOnHide,
- }
- },
-
- render() {
- const {
- zindex, slots, isCover, navRight, navWidth, showAnimation,
- visible, handleDrawerClose, destroyOnHide, position } = this
-
- if (destroyOnHide.value && !visible) {
- return null
- }
-
- const transitionName = showAnimation ? position : 'none'
-
- return (
-
- {isCover ?
: null}
-
-
-
-
- {slots.default ? slots.default() : null}
-
-
-
-
-
- )
- }
-})
\ No newline at end of file
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-container.tsx b/packages/devui-vue/devui/drawer/src/components/drawer-container.tsx
deleted file mode 100644
index f348d456e2..0000000000
--- a/packages/devui-vue/devui/drawer/src/components/drawer-container.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { defineComponent, inject } from 'vue'
-
-export default defineComponent({
- name: 'DrawerContainer',
- setup() {
- const visible = inject('visible')
- const destroyOnHide = inject('destroyOnHide')
- return { visible, destroyOnHide }
- },
- render() {
- const { visible, destroyOnHide } = this
-
- if (destroyOnHide.value && !visible) {
- return null
- }
- return 内容区域
- }
-})
\ No newline at end of file
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-header.scss b/packages/devui-vue/devui/drawer/src/components/drawer-header.scss
deleted file mode 100644
index ee1776a5d0..0000000000
--- a/packages/devui-vue/devui/drawer/src/components/drawer-header.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-@import '../drawer.scss';
-
-.devui-drawer-header {
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
-
- & .devui-drawer-header-item + .devui-drawer-header-item {
- padding-left: 12px;
- }
-}
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-header.tsx b/packages/devui-vue/devui/drawer/src/components/drawer-header.tsx
deleted file mode 100644
index 9adbe932db..0000000000
--- a/packages/devui-vue/devui/drawer/src/components/drawer-header.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { defineComponent, ref, inject, computed } from 'vue'
-
-import './drawer-header.scss'
-
-export default defineComponent({
- name: 'DrawerHeader',
- emits: ['toggleFullScreen', 'close'],
- setup(props, ctx) {
- const isFullScreen = ref(false)
-
- const visible = inject('visible')
- const destroyOnHide = inject('destroyOnHide')
-
- const fullScreenClassName = computed(() => isFullScreen.value ? 'icon icon-minimize' : 'icon icon-maxmize')
-
- const handleFullScreen = (e) => {
- e.stopPropagation()
- isFullScreen.value = !isFullScreen.value
- ctx.emit('toggleFullScreen')
- }
-
- const handleDrawerClose = () => {
- ctx.emit('close')
- }
-
- return { fullScreenClassName, visible, handleFullScreen, handleDrawerClose, destroyOnHide }
- },
- render() {
- const {
- handleFullScreen, handleDrawerClose, visible,
- fullScreenClassName, destroyOnHide
- } = this
-
- if (destroyOnHide.value && !visible) {
- return null
- }
-
- return (
-
- )
- }
-})
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss
new file mode 100644
index 0000000000..1cec622700
--- /dev/null
+++ b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.scss
@@ -0,0 +1,20 @@
+@import '../../../styles-var/devui-var.scss';
+
+.devui-drawer-overlay {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background-color: $devui-shadow;
+}
+
+.drawer-overlay-fade-enter-active,
+.drawer-overlay-fade-leave-active {
+ transition: opacity 0.1s linear;
+}
+
+.drawer-overlay-fade-enter-from,
+.drawer-overlay-fade-leave-to {
+ opacity: 0;
+}
diff --git a/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx
new file mode 100644
index 0000000000..973e4b3a0d
--- /dev/null
+++ b/packages/devui-vue/devui/drawer/src/components/drawer-overlay.tsx
@@ -0,0 +1,18 @@
+import { defineComponent, Transition } from 'vue';
+import type { SetupContext } from 'vue';
+import { drawerOverlayProps, DrawerOverlayProps } from '../drawer-types';
+import './drawer-overlay.scss';
+
+export default defineComponent({
+ name: 'DDrawerOverlay',
+ props: drawerOverlayProps,
+ emits: ['click'],
+ setup(props: DrawerOverlayProps, ctx: SetupContext) {
+ const handleClick = (e: Event) => {
+ ctx.emit('click', e);
+ };
+ return () => (
+ {props.visible && }
+ );
+ },
+});
diff --git a/packages/devui-vue/devui/drawer/src/drawer-service.tsx b/packages/devui-vue/devui/drawer/src/drawer-service.tsx
index 2fe7bec3be..22dbae350d 100644
--- a/packages/devui-vue/devui/drawer/src/drawer-service.tsx
+++ b/packages/devui-vue/devui/drawer/src/drawer-service.tsx
@@ -1,77 +1,58 @@
-import { createApp } from 'vue'
-import { DrawerProps } from './drawer-types'
-
-import DDrawer from './drawer'
-import { omit } from 'lodash'
-
-interface drawerInstance {
- hide(): void
- hideDirectly(): void
- destroy(): void
-}
-
-function createDrawerApp(props: DrawerProps, drawer: drawerInstance, el: HTMLElement) {
- if (drawer) {
- return drawer
- }
- const restProps = omit(props, ['header', 'content', 'visible'])
-
- const res = createApp(
- // BUG: this function generates a new app, v-model instructor of template is like not working
- // TODO: could be fixed by using self-defined header slot
- {{ header: props.header, content: props.content }}
- )
- res.mount(el)
- return res
+import { createApp, nextTick, onUnmounted, reactive } from 'vue';
+import type { App } from 'vue';
+import Drawer from './drawer';
+import { DrawerOptions } from './drawer-types';
+
+const defaultOptions: DrawerOptions = {
+ modelValue: false,
+ content: '',
+ zIndex: 1000,
+ showOverlay: true,
+ escKeyCloseable: true,
+ position: 'right',
+ lockScroll: true,
+ closeOnClickOverlay: true,
+};
+
+function initInstance(state: DrawerOptions): App {
+ const container = document.createElement('div');
+ const content = state.content;
+ delete state.content;
+
+ const app: App = createApp({
+ setup() {
+ const handleVisibleChange = () => {
+ state.modelValue = false;
+ };
+ onUnmounted(() => {
+ console.log(111);
+ document.body.removeChild(container);
+ });
+ return () => (
+
+ {content}
+
+ );
+ },
+ });
+
+ document.body.appendChild(container);
+ app.mount(container);
+ return app;
}
export default class DrawerService {
- static create(props: DrawerProps, drawer: drawerInstance): drawerInstance {
- if (!drawer) {
- drawer = new Drawer(props)
- }
- return drawer
- }
-}
-
-class Drawer {
- private drawer: any = null
- private div: HTMLElement = null
- private props: DrawerProps = null
-
- constructor(props: DrawerProps) {
- this.props = props
- }
-
- public show(): void {
- if (!this.drawer) {
- this.div = document.createElement('div')
- this.drawer = createDrawerApp(this.props, this.drawer, this.div)
- }
- // TODO: this is a hack, need to find a better way. (the row 62)
- this.drawer._instance.props.visible = true
- }
-
- public hide = async (): Promise => {
- const beforeHidden = this.props.beforeHidden
- let result = (typeof beforeHidden === 'function' ? beforeHidden() : beforeHidden) ?? false
- if (result instanceof Promise) {
- result = await result
- }
- if (!result) this.hideDirectly()
- }
-
- public hideDirectly = (): void => {
- this.drawer._instance.props.visible = false
- // this.div.remove()
- }
-
- public destroy = (): void => {
- // when drawer is null, it has been destroyed already and no need to destroy again
- if (this.drawer) {
- this.drawer.unmount()
- this.drawer = null
- this.div.remove()
- }
+ open(options: DrawerOptions): { close: () => void } {
+ const state: DrawerOptions = reactive({ ...defaultOptions, ...options });
+ const app = initInstance(state);
+
+ state.modelValue = true;
+
+ return {
+ close: () => {
+ state.modelValue = false;
+ app.unmount();
+ },
+ };
}
}
diff --git a/packages/devui-vue/devui/drawer/src/drawer-types.ts b/packages/devui-vue/devui/drawer/src/drawer-types.ts
index 6ff7e1f959..498f09c187 100644
--- a/packages/devui-vue/devui/drawer/src/drawer-types.ts
+++ b/packages/devui-vue/devui/drawer/src/drawer-types.ts
@@ -1,51 +1,58 @@
-import type { ExtractPropTypes, PropType } from 'vue'
+import type { ExtractPropTypes, PropType, Slot, Ref } from 'vue';
export const drawerProps = {
- width: { // 宽度
- type: String,
- default: '300px',
- },
- visible: { // 是否可见
+ modelValue: {
type: Boolean,
default: false,
},
- zIndex: { // 层级
+ zIndex: {
type: Number,
default: 1000,
},
- isCover: { // 是否有遮罩层
+ showOverlay: {
type: Boolean,
default: true,
},
- escKeyCloseable: { // 是否可通过esc关闭
+ escKeyCloseable: {
type: Boolean,
default: true,
},
- position: { // 位置 只有左和右
+ position: {
type: String as PropType<'left' | 'right'>,
- default: 'left',
+ default: 'right',
},
- backdropCloseable: { // 点击遮罩层是否可关闭
+ lockScroll: {
type: Boolean,
default: true,
},
- destroyOnHide: { // 是否在隐藏时销毁
- type: Boolean,
- default: false,
- },
- showAnimation: { // 是否启用动效
+ closeOnClickOverlay: {
type: Boolean,
default: true,
},
- beforeHidden: { // 关闭前的回调
- type: [Promise, Function] as PropType | (() => boolean | Promise)>,
+ beforeClose: {
+ type: Function as PropType<(done: () => void) => void>,
},
- content: { // 默认内容插槽
- type: Object,
- },
- header: { // 头部内容插槽
- type: Object,
+};
+
+export const drawerOverlayProps = {
+ visible: {
+ type: Boolean,
+ default: false,
},
-} as const
+};
+
+type DrawerEmitEvent = 'update:modelValue' | 'close' | 'open';
+
+export type DrawerEmit = (event: DrawerEmitEvent, result?: unknown) => void;
+
+export type DrawerProps = ExtractPropTypes;
+
+export type DrawerOverlayProps = ExtractPropTypes;
+
+export type DrawerOptions = Partial & { content?: string | Slot };
-export type DrawerProps = ExtractPropTypes
+export type UseDrawerFn = {
+ drawerRef: Ref;
+ drawerClasses: Ref>;
+ handleOverlayClick: () => void;
+};
diff --git a/packages/devui-vue/devui/drawer/src/drawer.scss b/packages/devui-vue/devui/drawer/src/drawer.scss
index 49caf1ee62..3afef767e4 100644
--- a/packages/devui-vue/devui/drawer/src/drawer.scss
+++ b/packages/devui-vue/devui/drawer/src/drawer.scss
@@ -1,41 +1,52 @@
-.devui-drawer{
- &-left-enter-active {
- animation: left-inout 0.3s;
+@import '../../styles-var/devui-var.scss';
+
+.devui-drawer {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ width: 300px;
+ border-radius: $devui-border-radius;
+ background-color: $devui-base-bg;
+ transform: translateX(0);
+ opacity: 1;
+ overflow: auto;
+ box-shadow: $devui-shadow-length-fullscreen-overlay $devui-shadow;
+
+ &-left {
+ left: 0;
}
- &-left-leave-active {
- animation: left-inout 0.3s reverse;
+ &-right {
+ right: 0;
}
+}
+.drawer-fly {
&-right-enter-active {
- animation: right-inout 0.3s;
+ transition: all 0.3s cubic-bezier(0.16, 0.75, 0.5, 1);
}
&-right-leave-active {
- animation: right-inout 0.3s reverse;
+ transition: all 0.3s cubic-bezier(0.5, 0, 0.84, 0.25);
}
-}
-@keyframes right-inout {
- 0% {
- transform: translateX(100px);
+ &-right-enter-from,
+ &-right-leave-to {
opacity: 0;
+ transform: translateX(100%);
}
- 100% {
- transform: translateX(0);
- opacity: 1;
+ &-left-enter-active {
+ transition: all 0.3s cubic-bezier(0.16, 0.75, 0.5, 1);
}
-}
-@keyframes left-inout {
- 0% {
- transform: translateX(-100px);
- opacity: 0;
+ &-left-leave-active {
+ transition: all 0.3s cubic-bezier(0.5, 0, 0.84, 0.25);
}
- 100% {
- transform: translateX(0);
- opacity: 1;
+ &-left-enter-from,
+ &-left-leave-to {
+ opacity: 0;
+ transform: translateX(-100%);
}
-}
\ No newline at end of file
+}
diff --git a/packages/devui-vue/devui/drawer/src/drawer.tsx b/packages/devui-vue/devui/drawer/src/drawer.tsx
index 9bde30a3d5..37e7c09b6f 100644
--- a/packages/devui-vue/devui/drawer/src/drawer.tsx
+++ b/packages/devui-vue/devui/drawer/src/drawer.tsx
@@ -1,103 +1,27 @@
-import { defineComponent, ref, toRefs, watch, onUnmounted, Teleport, provide } from 'vue'
-import { drawerProps, DrawerProps } from './drawer-types'
-
-import DrawerHeader from './components/drawer-header'
-import DrawerContainer from './components/drawer-container'
-import DrawerBody from './components/drawer-body'
-
-import './drawer.scss'
+import { defineComponent, Teleport, Transition } from 'vue';
+import { drawerProps, DrawerProps } from './drawer-types';
+import DrawerOverlay from './components/drawer-overlay';
+import { useDrawer } from './use-drawer';
+import './drawer.scss';
export default defineComponent({
name: 'DDrawer',
+ inheritAttrs: false,
props: drawerProps,
- emits: ['close', 'update:visible', 'afterOpened'],
- setup(props: DrawerProps, { emit, slots }) {
- const {
- width, visible, zIndex, isCover, escKeyCloseable, position,
- backdropCloseable, destroyOnHide, showAnimation
- } = toRefs(props)
- const isFullScreen = ref(false)
-
- const fullscreen = () => {
- isFullScreen.value = !isFullScreen.value
- }
-
- const closeDrawer = async () => {
- const beforeHidden = props.beforeHidden;
- let result = (typeof beforeHidden === 'function' ? beforeHidden(): beforeHidden) ?? false;
- if (result instanceof Promise) {
- result = await result;
- }
- if (result) return;
-
- // BUG: this is not working when use service model
- emit('update:visible', false)
- emit('close')
- }
-
- const escCloseDrawer = (e) => {
- if (e.code === 'Escape') {
- closeDrawer()
- }
- }
-
- watch(visible, (val) => {
- if (val) {
- emit('afterOpened')
- // TODO: destroy-model should reset props, this function should be extracted
- if (destroyOnHide.value) {
- isFullScreen.value = false
- }
- }
- if (escKeyCloseable && val) {
- document.addEventListener('keyup', escCloseDrawer)
- } else {
- document.removeEventListener('keyup', escCloseDrawer)
- }
- })
-
- // TODO: need to handle these params again
- // 1. should be provided by params' value (eg: provide('closeDrawer', closeDrawer.value))
- // 2. which param should be provided
- provide('closeDrawer', closeDrawer)
- provide('zindex', zIndex)
- provide('isCover', isCover)
- provide('position', position)
- provide('width', width)
- provide('visible', visible)
- provide('isFullScreen', isFullScreen)
- provide('backdropCloseable', backdropCloseable)
- provide('destroyOnHide', destroyOnHide)
- provide('showAnimation', showAnimation)
-
- onUnmounted(() => {
- document.removeEventListener('keyup', escCloseDrawer)
- })
-
- return {
- isFullScreen,
- visible,
- slots,
- fullscreen,
- closeDrawer,
- }
- },
- render() {
- const { fullscreen, closeDrawer, visible, destroyOnHide } = this;
-
- if (destroyOnHide.value && !visible) {
- return null
- }
-
- return (
-
-
- {this.slots.header ? this.slots.header({fullscreen, closeDrawer}) :
-
- }
- {this.slots.content ? this.slots.content() : }
-
+ emits: ['close', 'update:modelValue', 'open'],
+ setup(props: DrawerProps, { emit, slots, attrs }) {
+ const { drawerRef, drawerClasses, handleOverlayClick } = useDrawer(props, emit);
+ return () => (
+
+ {props.showOverlay && }
+
+ {props.modelValue && (
+
+ {slots.default?.()}
+
+ )}
+
- )
- }
-})
+ );
+ },
+});
diff --git a/packages/devui-vue/devui/drawer/src/use-drawer.ts b/packages/devui-vue/devui/drawer/src/use-drawer.ts
new file mode 100644
index 0000000000..f328cc1201
--- /dev/null
+++ b/packages/devui-vue/devui/drawer/src/use-drawer.ts
@@ -0,0 +1,50 @@
+import { computed, onUnmounted, ref, watch } from 'vue';
+import { onClickOutside } from '@vueuse/core';
+import { DrawerEmit, DrawerProps, UseDrawerFn } from './drawer-types';
+import { lockScroll } from '../../shared/util/lock-scroll';
+
+export function useDrawer(props: DrawerProps, emit: DrawerEmit): UseDrawerFn {
+ const drawerRef = ref();
+ const drawerClasses = computed(() => ({
+ 'devui-drawer': true,
+ [`devui-drawer-${props.position}`]: true,
+ }));
+ const close = () => {
+ emit('update:modelValue', false);
+ emit('close');
+ };
+ let lockScrollCb: () => void;
+ const execClose = () => {
+ props.beforeClose ? props.beforeClose(close) : close();
+ };
+ const handleOverlayClick = () => {
+ props.closeOnClickOverlay && execClose();
+ };
+ const handleEscClose = (e: KeyboardEvent) => {
+ e.code === 'Escape' && execClose();
+ };
+
+ onClickOutside(drawerRef, execClose);
+
+ const removeBodyAdditions = () => {
+ lockScrollCb?.();
+ document.removeEventListener('keyup', handleEscClose);
+ };
+
+ watch(
+ () => props.modelValue,
+ (val) => {
+ if (val) {
+ emit('open');
+ props.lockScroll && (lockScrollCb = lockScroll());
+ props.escKeyCloseable && document.addEventListener('keyup', handleEscClose);
+ } else {
+ removeBodyAdditions();
+ }
+ }
+ );
+
+ onUnmounted(removeBodyAdditions);
+
+ return { drawerRef, drawerClasses, handleOverlayClick };
+}
diff --git a/packages/devui-vue/devui/shared/util/lock-scroll.ts b/packages/devui-vue/devui/shared/util/lock-scroll.ts
new file mode 100644
index 0000000000..16123eeef9
--- /dev/null
+++ b/packages/devui-vue/devui/shared/util/lock-scroll.ts
@@ -0,0 +1,19 @@
+export function lockScroll(): () => void | undefined {
+ if (document.documentElement.scrollHeight > document.documentElement.clientHeight) {
+ const scrollTop = document.documentElement.scrollTop;
+ const style = document.documentElement.getAttribute('style');
+ document.documentElement.style.position = 'fixed';
+ document.documentElement.style.top = `-${scrollTop}px`;
+ document.documentElement.style.width = document.documentElement.style.width || '100%';
+ document.documentElement.style.overflowY = 'scroll';
+ return () => {
+ if (style) {
+ document.documentElement.setAttribute('style', style);
+ } else {
+ document.documentElement.removeAttribute('style');
+ }
+ document.documentElement.scrollTop = scrollTop;
+ };
+ }
+ return;
+}
diff --git a/packages/devui-vue/docs/components/drawer/index.md b/packages/devui-vue/docs/components/drawer/index.md
index b1aaa8b68e..c6a9fab12e 100644
--- a/packages/devui-vue/docs/components/drawer/index.md
+++ b/packages/devui-vue/docs/components/drawer/index.md
@@ -2,7 +2,7 @@
屏幕边缘滑出的浮层面板组件。
-### 何时使用
+#### 何时使用
1. 抽屉从父窗体边缘滑入,覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务,操作完成后,可以平滑地回到到原任务。
2. 当需要一个附加的面板来控制父窗体内容,这个面板在需要时呼出。比如,控制界面展示样式,往界面中添加内容。
@@ -10,215 +10,161 @@
### 基本用法
-基本用法,可以控制全屏、关闭和设置宽度。
-
-:::demo
+:::demo 默认从右侧滑出,宽度为`300px`。
```vue
- drawer {{ btnName }}
-
+ Click Me
+ Hello Drawer
+```
- const drawerAfterOpened = () => {
- console.log('open');
- };
+:::
- const beforeHidden = () => {
- return new Promise((resolve) => {
- resolve(false);
- });
- };
+### 左侧弹出
- return {
- isDrawerShow,
- btnName,
- drawerWidth,
- drawerShow,
- drawerClose,
- drawerAfterOpened,
- isCover,
- backdropCloseable,
- beforeHidden
- };
- }
-};
+:::demo 通过`position`设置左侧滑出。
+
+```vue
+
+ Click Me
+ Left Drawer
+
+
```
:::
-### 自定义模板
+### 背景滚动
-自定义抽屉板模板。
+:::demo drawer 滑出之后,默认背景滚动会被锁定,可通过`lock-scroll`设置为`false`来解锁。
-:::demo
+```vue
+
+ Click Me
+ Background can be scrolled
+
+
+
+```
+
+:::
+
+### 关闭前回调
+
+:::demo `before-close`在用户关闭 drawer 时会被调用,可在完成某些异步操作后,通过执行`done`函数关闭。
```vue
- drawer
-
- 内容区插槽
-
-
-
-
+ Click Me
+ Delay Close
+
```
:::
-### 以服务的方式调用
+### 服务方式
-:::demo
+:::demo 组件在全局注册了`$drawerService`,可通过服务的方式使用,drawer 的内容通过`content`参数传入。服务返回了用于关闭 drawer 的`close`方法。
```vue
- click me
+ 服务方式
+
```
:::
-### 参数及 API
-
-| 参数 | 类型 | 默认 | 说明 | 跳转 Demo |
-| :---------------: | :-------------------: | :-----: | :------------------------------------------ | --------------------- |
-| v-model:visible | `Boolean` | `false` | 必选,设置抽屉板是否可见 | [基本用法](#基本用法) |
-| width | `String` | `300px` | 可选,设置抽屉板宽度 | [基本用法](#基本用法) |
-| zIndex | `Number` | `1000` | 可选,设置 drawer 的 z-index 值 | [基本用法](#基本用法) |
-| isCover | `Boolean` | `true` | 可选,是否有遮罩层 | [基本用法](#基本用法) |
-| escKeyCloseable | `Boolean` | `true` | 可选,设置可否通过 esc 按键来关闭 drawer 层 | [基本用法](#基本用法) |
-| position | `String` | 'right' | 可选,抽屉板出现的位置,'left'或者'right' | [基本用法](#基本用法) |
-| backdropCloseable | `Boolean` | true | 可选,设置可否通过点击背景来关闭 drawer 层 | [基本用法](#基本用法) |
-| destroyOnHide | `Boolean` | true | 可选,设置是否在隐藏时销毁 drawer 层 | [基本用法](#基本用法) |
-| beforeHidden | `Function \| Promise` | -- | 可选,关闭窗口之前的回调 | [基本用法](#基本用法) |
-| onClose | `Function` | -- | 可选,关闭 drawer 时候调用 | [基本用法](#基本用法) |
-| onAfterOpened | `Function` | -- | 可选,打开 drawer 后时候调用 | [基本用法](#基本用法) |
-| showAnimation | `boolean` | true | 可选,是否开启动效 | [基本用法](#基本用法) |
-
-### 插槽
-
-| 名称 | 类型 | 说明 | 跳转 Demo |
-| :-----: | :--------: | :--------: | :-----------------------: |
-| content | 具名插槽 | 抽屉板内容 | [自定义模板](#自定义模板) |
-| header | 作用域插槽 | 抽屉板头部 | [自定义模板](#自定义模板) |
-
-#### 作用域插槽参数
-
-| 名称 | 作用 | 说明 | 跳转 Demo |
-| :---------: | :--------: | :---------------------------------------------------------------------: | :-----------------------: |
-| fullscreen | 切换全屏 | -- | [自定义模板](#自定义模板) |
-| closeDrawer | 关闭抽屉板 | 在关闭抽屉板时必须调用该方法,否则 `beforeHidden` 和 `close` 属性不生效 | [自定义模板](#自定义模板) |
+### d-drawer 参数
+
+| 参数 | 类型 | 默认 | 说明 | 跳转 Demo |
+| ---------------------- | ---------------- | ------- | ------------------------------------------------- | ------------------------- |
+| v-model | `Boolean` | `false` | 可选,设置抽屉板是否可见 | [基本用法](#基本用法) |
+| position | `String` | `right` | 可选,抽屉板出现的位置,'left'或者'right' | [左侧弹出](#左侧弹出) |
+| show-overlay | `Boolean` | `true` | 可选,是否有遮罩层 | [基本用法](#基本用法) |
+| lock-scroll | `Boolean` | `true` | 可选,是否锁定滚动 | [背景滚动](#背景滚动) |
+| z-index | `Number` | `1000` | 可选,设置 drawer 的 z-index 值 | [基本用法](#基本用法) |
+| esc-key-closeable | `Boolean` | `true` | 可选,设置可否通过 esc 按键来关闭 drawer 层 | [基本用法](#基本用法) |
+| close-on-click-overlay | `Boolean` | `true` | 可选,设置可否通过点击背景来关闭 drawer 层 | [基本用法](#基本用法) |
+| before-close | `(done) => void` | `-` | 可选,关闭窗口前的回调,调用 `done` 可关闭 drawer | [关闭前回调](#关闭前回调) |
+
+### d-drawer 事件
+
+| 事件名 | 类型 | 说明 |
+| ------ | ---- | ----------------- |
+| open | `-` | drawer 打开时触发 |
+| close | `-` | drawer 关闭时触发 |
+
+### d-drawer 插槽
+
+| 名称 | 类型 | 说明 | 跳转 Demo |
+| ------- | ---- | ---------- | --------------------- |
+| default | 默认 | 抽屉板内容 | [基本用法](#基本用法) |
diff --git a/packages/devui-vue/package.json b/packages/devui-vue/package.json
index 25f35e8274..a12e943acf 100644
--- a/packages/devui-vue/package.json
+++ b/packages/devui-vue/package.json
@@ -35,6 +35,7 @@
"dependencies": {
"@devui-design/icons": "^1.3.0",
"@types/lodash-es": "^4.17.4",
+ "@vueuse/core": "^7.7.1",
"async-validator": "^4.0.2",
"devui-theme": "workspace:^0.0.1",
"fs-extra": "^10.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 96659d6697..9dbe5a10c0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -114,6 +114,7 @@ importers:
'@vue/test-utils': ^2.0.0-rc.9
'@vuedx/typecheck': ^0.4.1
'@vuedx/typescript-plugin-vue': ^0.4.1
+ '@vueuse/core': ^7.7.1
async-validator: ^4.0.2
babel-jest: ^27.0.2
chalk: ^4.1.2
@@ -142,6 +143,7 @@ importers:
dependencies:
'@devui-design/icons': 1.3.0
'@types/lodash-es': 4.17.6
+ '@vueuse/core': 7.7.1_vue@3.2.31
async-validator: 4.0.7
devui-theme: link:../devui-theme
fs-extra: 10.0.0
@@ -2930,6 +2932,37 @@ packages:
- supports-color
dev: true
+ /@vueuse/core/7.7.1_vue@3.2.31:
+ resolution: {integrity: sha512-PRRgbATMpoeUmkCEBtUeJgOwtew8s+4UsEd+Pm7MhkjL2ihCNrSqxNVtM6NFE4uP2sWnkGcZpCjPuNSxowJ1Ow==}
+ peerDependencies:
+ '@vue/composition-api': ^1.1.0
+ vue: ^2.6.0 || ^3.2.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+ vue:
+ optional: true
+ dependencies:
+ '@vueuse/shared': 7.7.1_vue@3.2.31
+ vue: 3.2.31
+ vue-demi: 0.12.1_vue@3.2.31
+ dev: false
+
+ /@vueuse/shared/7.7.1_vue@3.2.31:
+ resolution: {integrity: sha512-rN2qd22AUl7VdBxihagWyhUNHCyVk9IpvBTTfHoLH9G7rGE552X1f+zeCfehuno0zXif13jPw+icW/wn2a0rnQ==}
+ peerDependencies:
+ '@vue/composition-api': ^1.1.0
+ vue: ^2.6.0 || ^3.2.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+ vue:
+ optional: true
+ dependencies:
+ vue: 3.2.31
+ vue-demi: 0.12.1_vue@3.2.31
+ dev: false
+
/JSONStream/1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true
@@ -8767,6 +8800,21 @@ packages:
deprecated: This package has been renamed to @vscode/web-custom-data, please update to the new name
dev: true
+ /vue-demi/0.12.1_vue@3.2.31:
+ resolution: {integrity: sha512-QL3ny+wX8c6Xm1/EZylbgzdoDolye+VpCXRhI2hug9dJTP3OUJ3lmiKN3CsVV3mOJKwFi0nsstbgob0vG7aoIw==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+ dependencies:
+ vue: 3.2.31
+ dev: false
+
/vue-eslint-parser/7.11.0_eslint@7.32.0:
resolution: {integrity: sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==}
engines: {node: '>=8.10'}