11<template >
2- <!-- 主容器:根据展开状态和过渡状态添加对应类名 -->
3- <div v-if =" href" class =" demo-model" :class =" {
2+ <!-- 大屏幕:原有的固定容器 -->
3+ <div v-if =" href && !isSmallScreen " class =" demo-model" :class =" {
44 'collapsed': !expanded,
55 'transition-end': transitionEnd
66 }" @transitionend =" onTransitionEnd" >
1818 <iframe v-if =" expanded&&transitionEnd" ref =" iframe" id =" demo" class =" iframe" scrolling =" auto" frameborder =" 0" :src =" href" />
1919 </div >
2020 </div >
21+
22+ <!-- 小屏幕:触发按钮 -->
23+ <div v-if =" href && isSmallScreen && !isTinyScreen" class =" demo-trigger" @click =" openDrawer" >
24+ <el-icon class =" trigger-icon" >
25+ <component :is =" Expand" />
26+ </el-icon >
27+ </div >
28+
29+ <!-- 小屏幕:Drawer抽屉 -->
30+ <view v-if =" isSmallScreen && !isTinyScreen" class =" drawer-container" >
31+ <el-drawer
32+
33+ v-model =" drawerVisible"
34+ direction =" rtl"
35+ size =" 380px"
36+ :modal =" false"
37+ :lock-scroll =" false"
38+ :before-close =" closeDrawer"
39+ >
40+ <div class =" demo-header" >
41+ <ExternalLink :href =" href " class="demo-link">
42+ </ExternalLink >
43+ <QrCode class="demo-qrcode" :src =" qrcode " v-if =" qrcode " ></QrCode >
44+ </div >
45+ <div class =" drawer-content" >
46+ <iframe v-if =" drawerVisible" ref =" drawerIframe" class =" drawer-iframe" scrolling =" auto" frameborder =" 0" :src =" href" />
47+ </div >
48+ </el-drawer >
49+ </view >
2150</template >
2251
2352<script setup lang="ts">
2453import { Expand , Fold } from ' @element-plus/icons-vue'
54+ import { ElDrawer , ElIcon } from ' element-plus'
2555import { useRoute , useData } from ' vitepress'
26- import { computed , onMounted , ref , watch } from ' vue'
56+ import { computed , onMounted , ref , watch , onUnmounted } from ' vue'
2757import QrCode from ' ./QrCode.vue'
2858
2959interface Props {
@@ -38,7 +68,11 @@ const props = withDefaults(defineProps<Props>(), {
3868// 状态管理
3969const baseUrl = ref (' ' )
4070const iframe = ref <HTMLIFrameElement | null >(null )
71+ const drawerIframe = ref <HTMLIFrameElement | null >(null )
4172const transitionEnd = ref (true )
73+ const drawerVisible = ref (false )
74+ const isSmallScreen = ref (false )
75+ const isTinyScreen = ref (false )
4276
4377const emit = defineEmits <{
4478 ' update:expanded' : [boolean ] // 更新展开状态
@@ -64,6 +98,19 @@ const qrcode = computed(() => {
6498 return ` /wxqrcode/${paths [paths .length - 1 ]}.png `
6599})
66100
101+ // 获取页面标题
102+ const pageTitle = computed (() => {
103+ const path = route .path
104+ const paths = path ? path .split (' .' )[0 ].split (' /' ) : []
105+ if (! paths .length ) return ' 预览Demo'
106+
107+ // 将kebab-case转换为更友好的标题格式
108+ const pageName = paths [paths .length - 1 ]
109+ return pageName .split (' -' ).map (word =>
110+ word .charAt (0 ).toUpperCase () + word .slice (1 )
111+ ).join (' ' )
112+ })
113+
67114// 工具函数:转换 kebab-case 为 camelCase
68115function kebabToCamel(input : string ): string {
69116 return input .replace (/ -([a-z ] )/ g , (match , group ) => group .toUpperCase ())
@@ -87,6 +134,26 @@ function toggleExpand() {
87134 transitionEnd .value = false
88135}
89136
137+ // 小屏幕抽屉控制
138+ function openDrawer() {
139+ drawerVisible .value = true
140+ }
141+
142+ function closeDrawer() {
143+ drawerVisible .value = false
144+ }
145+
146+ // 检查屏幕尺寸
147+ function checkScreenSize() {
148+ isSmallScreen .value = window .innerWidth <= 1439
149+ isTinyScreen .value = window .innerWidth < 1280
150+ }
151+
152+ // 监听窗口大小变化
153+ function handleResize() {
154+ checkScreenSize ()
155+ }
156+
90157// 过渡结束处理
91158function onTransitionEnd() {
92159 transitionEnd .value = true
@@ -95,15 +162,19 @@ function onTransitionEnd() {
95162
96163// iframe 消息通信
97164function sendMessage() {
98- if (iframe .value ?.contentWindow ) {
99- iframe .value .contentWindow .postMessage (vitepressData .isDark .value , href .value )
165+ const targetIframe = isSmallScreen .value ? drawerIframe .value : iframe .value
166+ if (targetIframe ?.contentWindow ) {
167+ const targetOrigin = new URL (href .value , location .origin ).origin
168+ targetIframe .contentWindow .postMessage (vitepressData .isDark .value , targetOrigin )
100169 }
101170}
102171
103172// 发送语言信息给iframe
104173function sendLanguageMessage() {
105- if (iframe .value ?.contentWindow ) {
106- iframe .value .contentWindow .postMessage (vitepressData .lang .value , href .value )
174+ const targetIframe = isSmallScreen .value ? drawerIframe .value : iframe .value
175+ if (targetIframe ?.contentWindow ) {
176+ const targetOrigin = new URL (href .value , location .origin ).origin
177+ targetIframe .contentWindow .postMessage (vitepressData .lang .value , targetOrigin )
107178 }
108179}
109180
@@ -112,13 +183,31 @@ onMounted(() => {
112183 ? ` ${location .origin }/demo/?timestamp=${new Date ().getTime ()}#/ `
113184 : ' http://localhost:5173/demo/#/'
114185
186+ // 初始化屏幕尺寸检查
187+ checkScreenSize ()
188+ window .addEventListener (' resize' , handleResize )
189+
115190 // 监听 iframe 加载完成事件
116191 iframe .value ?.addEventListener (' load' , () => {
117192 sendMessage ()
118193 sendLanguageMessage ()
119194 })
120195})
121196
197+ onUnmounted (() => {
198+ window .removeEventListener (' resize' , handleResize )
199+ })
200+
201+ // 监听抽屉iframe加载完成
202+ watch (drawerIframe , (newVal ) => {
203+ if (newVal ) {
204+ newVal .addEventListener (' load' , () => {
205+ sendMessage ()
206+ sendLanguageMessage ()
207+ })
208+ }
209+ })
210+
122211watch (
123212 () => vitepressData .isDark .value ,
124213 sendMessage
@@ -266,8 +355,102 @@ watch(
266355 }
267356}
268357
269- @media (max-width : 1439px ) {
270- .demo-model {
358+ /* 小屏幕触发按钮样式 */
359+ .demo-trigger {
360+ position : fixed ;
361+ z-index : 10 ;
362+ right : 16px ;
363+ top : calc (var (--vp-nav-height ) + 32px );
364+ width : 48px ;
365+ height : 48px ;
366+ background : var (--vp-c-bg-alt );
367+ border-radius : 12px ;
368+ box-shadow : var (--vp-shadow-4 );
369+ display : flex ;
370+ align-items : center ;
371+ justify-content : center ;
372+ cursor : pointer ;
373+ transition : all 0.3s ease-in-out ;
374+ }
375+
376+ .demo-trigger :hover {
377+ transform : scale (1.05 );
378+ }
379+
380+ .trigger-icon {
381+ font-size : 24px ;
382+ color : var (--vp-c-text-1 );
383+ }
384+
385+ /* 抽屉内容样式 */
386+ .drawer-header {
387+ display : flex ;
388+ align-items : center ;
389+ justify-content : space-between ;
390+ padding : 16px 0 ;
391+ border-bottom : 1px solid var (--vp-c-divider );
392+ margin-bottom : 16px ;
393+ }
394+
395+ .drawer-title {
396+ font-size : 18px ;
397+ font-weight : 600 ;
398+ color : var (--vp-c-text-1 );
399+ flex : 1 ;
400+ }
401+
402+ .drawer-actions {
403+ display : flex ;
404+ align-items : center ;
405+ gap : 12px ;
406+ }
407+
408+ .drawer-actions .demo-link {
409+ font-size : 20px !important ;
410+ color : var (--vp-c-text-2 );
411+ transition : color 0.3s ease ;
412+ }
413+
414+ .drawer-actions .demo-link :hover {
415+ color : var (--vp-c-brand-1 );
416+ }
417+
418+ .drawer-actions .demo-qrcode {
419+ font-size : 20px !important ;
420+ color : var (--vp-c-text-2 );
421+ }
422+
423+ .drawer-content {
424+ display : flex ;
425+ flex-direction : column ;
426+ align-items : center ;
427+ width : 340px ;
428+ height : calc (340px * 143.6 / 70.9 );
429+ }
430+
431+ .drawer-iframe {
432+ width : 340px ;
433+ height : calc (340px * 143.6 / 70.9 );
434+ border : none ;
435+ border-radius : 20px ;
436+ box-shadow : 0 8px 32px rgba (0 , 0 , 0 , 0.1 );
437+ background : var (--vp-c-bg );
438+ overflow : hidden ;
439+ }
440+
441+ .drawer-container {
442+ --el-drawer-bg-color : var (--vp-c-bg );
443+ :deep() {
444+ .el-drawer {
445+ background-color : var (--vp-c-bg ) !important ;
446+ }
447+ }
448+ }
449+
450+
451+
452+ @media (max-width : 768px ) {
453+ .demo-trigger {
271454 display : none ;
272455 }
273456}
0 commit comments