diff --git a/devui/fullscreen/index.ts b/devui/fullscreen/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cc6c868a5fbb83c25948d1d46f5491a5ed6babe --- /dev/null +++ b/devui/fullscreen/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' +import Fullscreen from './src/fullscreen' + +Fullscreen.install = function(app: App): void { + app.component(Fullscreen.name, Fullscreen) +} + +export { Fullscreen } + +export default { + title: 'Fullscreen 全屏', + category: '通用', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + + app.use(Fullscreen as any) + } +} diff --git a/devui/fullscreen/src/fullscreen-types.ts b/devui/fullscreen/src/fullscreen-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..5428abb3889a27f5a9d3d0899f7aa6d0993c579b --- /dev/null +++ b/devui/fullscreen/src/fullscreen-types.ts @@ -0,0 +1,19 @@ +import type { PropType, ExtractPropTypes } from 'vue' + +type ModeType = PropType<'immersive' | 'normal'> +export const fullscreenProps = { + fullscreenLaunch: { + type: Function, + default: undefined + }, + mode: { + type: String as ModeType, + default: 'immersive' + }, + zIndex: { + type: Number, + default: 10 + } +} as const + +export type FullscreenProps = ExtractPropTypes diff --git a/devui/fullscreen/src/fullscreen.scss b/devui/fullscreen/src/fullscreen.scss new file mode 100644 index 0000000000000000000000000000000000000000..c0f01d4ebdad1f5899a3c517419f5cfa368d8ca7 --- /dev/null +++ b/devui/fullscreen/src/fullscreen.scss @@ -0,0 +1,16 @@ +@import '../../style/theme/color'; + +.fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 10; + overflow: auto; + background-color: $devui-base-bg; +} + +.devui-fullscreen { + overflow: hidden; +} diff --git a/devui/fullscreen/src/fullscreen.tsx b/devui/fullscreen/src/fullscreen.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b0e682cdbe89fe0abb8c3d8fed478f60be3b813b --- /dev/null +++ b/devui/fullscreen/src/fullscreen.tsx @@ -0,0 +1,165 @@ +import './fullscreen.scss' + +import { + defineComponent, + useSlots, + renderSlot, + onMounted, + onUnmounted, + ref +} from 'vue' +import { fullscreenProps, FullscreenProps } from './fullscreen-types' + +export default defineComponent({ + name: 'DFullscreen', + props: fullscreenProps, + emits: ['fullscreenLaunch'], + setup(props: FullscreenProps, ctx) { + + let currentTarget = ref(null) + const isFullscreen = ref(false) + const doc = document + + const onFullScreenChange = () => { + if (currentTarget.value) { + const targetElement: HTMLElement = currentTarget + if (document.fullscreenElement) { // 进入全屏 + addFullScreenStyle() + launchNormalFullscreen(targetElement) + } else { // 退出全屏 + removeFullScreenStyle() + currentTarget = null + exitNormalFullscreen(targetElement) + } + // F11退出全屏时,需要将全屏状态传出去 + isFullscreen.value = !!(doc.fullscreenElement) + ctx.emit('fullscreenLaunch', isFullscreen.value) + } + } + + // 页面全屏 + const launchNormalFullscreen = (targetElement: HTMLElement) => { + targetElement.classList.add('fullscreen') + if (props.zIndex) { + targetElement.setAttribute('style', `z-index: ${props.zIndex}`) + } + } + const exitNormalFullscreen = (targetElement: HTMLElement) => { + targetElement.classList.remove('fullscreen') + targetElement.style.zIndex = null + } + + // 事件监听 + const handleFullscreen = async () => { + const targetElement = document.querySelector('[fullscreen-target]') + let isFull = false + // 判断模式 + if (props.mode === 'normal') { // 浏览器全屏 + console.log('普通') + const fullscreen = targetElement.classList.contains('fullscreen') + if (!fullscreen) { // 进入全屏 + addFullScreenStyle() + launchNormalFullscreen(targetElement) + isFull = true + } else { // 退出全屏 + removeFullScreenStyle() + exitNormalFullscreen(targetElement) + isFull = false + } + } else { // 沉浸式全屏 + console.log('沉浸式') + currentTarget = targetElement + if (document.fullscreenElement || document.msFullscreenElement || document.webkitFullscreenElement) { + isFull = await exitImmersiveFullScreen(document) + } else { + isFull = await launchImmersiveFullScreen(document.documentElement) + } + } + + isFullscreen.value = isFull + ctx.emit('fullscreenLaunch', isFullscreen.value) + } + + const addFullScreenStyle = (): void => { + document.getElementsByTagName('html')[0].classList.add('devui-fullscreen'); + } + + const removeFullScreenStyle = (): void => { + document.getElementsByTagName('html')[0].classList.remove('devui-fullscreen') + } + + const exitImmersiveFullScreen = async (doc: any) => { + let fullscreenExit = null + if (doc.exitFullscreen) { + fullscreenExit = doc.exitFullscreen() + } else if (doc.mozCancelFullScreen) { + fullscreenExit = doc.mozCancelFullScreen() + } else if (doc.webkitCancelFullScreen) { + fullscreenExit = Promise.resolve(doc.webkitCancelFullScreen()); + } else if (doc.msExitFullscreen) { + fullscreenExit = Promise.resolve(doc.msExitFullscreen()); + } + return await fullscreenExit.then(() => !!document.fullscreenElement) + } + + const launchImmersiveFullScreen = async (docElement: any) => { + let fullscreenLaunch = null + if (docElement.requestFullscreen) { + fullscreenLaunch = docElement.requestFullscreen() + } else if (docElement.mozRequestFullScreen) { + fullscreenLaunch = docElement.mozRequestFullScreen() + } else if (docElement.webkitRequestFullScreen) { + fullscreenLaunch = Promise.resolve(docElement.webkitRequestFullScreen()) + } else if (docElement.msRequestFullscreen) { + fullscreenLaunch = Promise.resolve(docElement.msRequestFullscreen()) + } + return await fullscreenLaunch.then(() => !!document.fullscreenElement) + } + + const handleKeyDown = (event) => { + if (event.keyCode === 'ESC_KEYCODE') { // 按ESC键退出全屏 + if (isFullscreen.value) { + const targetElement = document.querySelector('[fullscreen-target]') + if (props.mode === 'normal') { + removeFullScreenStyle() + exitNormalFullscreen(targetElement) + } else { + if (doc.fullscreenElement) { exitImmersiveFullScreen(doc) } + } + ctx.emit('fullscreenLaunch', isFullscreen.value) + isFullscreen.value = false + } + } + } + + onMounted (() => { + console.log(ctx.slots) + const btnLaunch = document.querySelector('[fullscreen-launch]') + if (btnLaunch) { btnLaunch.addEventListener('click', handleFullscreen) } + document.addEventListener('fullscreenchange', onFullScreenChange) + document.addEventListener('MSFullscreenChange', onFullScreenChange) + document.addEventListener('webkitfullscreenchange', onFullScreenChange) + document.addEventListener('keydown', handleKeyDown) + }) + onUnmounted (() => { + document.removeEventListener('fullscreenchange', onFullScreenChange) + document.removeEventListener('MSFullscreenChange', onFullScreenChange) + document.removeEventListener('webkitfullscreenchange', onFullScreenChange) + document.removeEventListener('keydown', handleKeyDown) + const btnLaunch = document.querySelector('[fullscreen-launch]') + if (btnLaunch) { btnLaunch.removeEventListener('click', handleFullscreen) } + }) + return () => { + const defaultSlot = renderSlot(useSlots(), 'default') + // fullscreen-target 全屏元素属性 + // fullscreen-launch 全屏事件属性 + // if (defaultSlot.children.length === 0) throw new Error('未发现全屏元素') + // const targetElement = document.querySelector('[fullscreen-target]') + return ( +
+
{ defaultSlot }
+
+ ) + } + } +}) diff --git a/docs/components/fullscreen/index.md b/docs/components/fullscreen/index.md new file mode 100644 index 0000000000000000000000000000000000000000..945770e76e932f80577b5098dd8ea993f1796749 --- /dev/null +++ b/docs/components/fullscreen/index.md @@ -0,0 +1,57 @@ +# Fullscreen 全屏 + +全屏组件。 + +### 何时使用 + +当用户需要将某一区域进行全屏展示时。 + +### 沉浸式全屏 + +充满整个显示器屏幕的沉浸式全屏(待完善)。 + + + +### 普通全屏 + +充满当前浏览器窗口的普通全屏。 + +:::demo + +```vue + + +``` +::: + +### 参数及API + +| 参数 | 类型 | 默认 | 说明 | +| :---------: | :------: | :-------: | :----------------------- | +| mode | `immersive` 或 `normal` | `immersive` | 可选,设置全屏模式 | +| zIndex | `Number` | 10 | 可选,设置全屏层级 | +| fullscreenLaunch | `EventEmitter` | | 可选,全屏之后的回调 | \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 01433bca2ac76590c48fabfee8d69d7b223f48bb..8aef29becd73aa900ab9a99ada50dabaf12add5d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ import { createApp } from 'vue' -import App from './App.vue' +import App from './app.vue' createApp(App).mount('#app') diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c060e97a85b28a195677272263c7ca3981e1f962 --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file