655 lines
17 KiB
Vue
655 lines
17 KiB
Vue
<template>
|
|
<div class="responsive-layout" :class="layoutClasses">
|
|
<!-- 响应式容器 -->
|
|
<div class="responsive-container" :class="containerClasses">
|
|
<!-- 插槽内容 -->
|
|
<slot></slot>
|
|
</div>
|
|
|
|
<!-- 响应式调试信息 (开发环境显示) -->
|
|
<div v-if="showDebug" class="responsive-debug">
|
|
<div class="debug-info">
|
|
<strong>屏幕尺寸:</strong> {{ currentScreenSize }}
|
|
<br>
|
|
<strong>宽度:</strong> {{ screenWidth }}px
|
|
<br>
|
|
<strong>设备类型:</strong> {{ deviceType }}
|
|
<br>
|
|
<strong>触摸设备:</strong> {{ isTouchDevice ? '是' : '否' }}
|
|
<br>
|
|
<strong>高分辨率:</strong> {{ isHighDPI ? '是' : '否' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'ResponsiveLayout',
|
|
props: {
|
|
// 是否显示调试信息
|
|
showDebug: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 自定义断点
|
|
customBreakpoints: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
// 响应式模式
|
|
responsiveMode: {
|
|
type: String,
|
|
default: 'auto', // auto, mobile-first, desktop-first
|
|
validator: value => ['auto', 'mobile-first', 'desktop-first'].includes(value)
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
// 当前屏幕尺寸
|
|
currentScreenSize: '',
|
|
// 屏幕宽度
|
|
screenWidth: 0,
|
|
// 是否为触摸设备
|
|
isTouchDevice: false,
|
|
// 是否为高分辨率屏幕
|
|
isHighDPI: false,
|
|
// 响应式助手实例
|
|
responsiveHelper: null,
|
|
// 断点配置
|
|
breakpoints: {
|
|
xs: 380,
|
|
sm: 480,
|
|
md: 620,
|
|
lg: 768,
|
|
xl: 992,
|
|
xxl: 1200,
|
|
xxxl: 1500,
|
|
xxxxl: 1600
|
|
}
|
|
};
|
|
},
|
|
computed: {
|
|
// 布局类名
|
|
layoutClasses() {
|
|
return {
|
|
[`screen-${this.currentScreenSize}`]: true,
|
|
'touch-device': this.isTouchDevice,
|
|
'no-touch-device': !this.isTouchDevice,
|
|
'high-dpi': this.isHighDPI,
|
|
'low-dpi': !this.isHighDPI
|
|
};
|
|
},
|
|
// 容器类名
|
|
containerClasses() {
|
|
return {
|
|
[`container-${this.currentScreenSize}`]: true,
|
|
'responsive-container': true
|
|
};
|
|
},
|
|
// 设备类型
|
|
deviceType() {
|
|
if (this.currentScreenSize === 'xs' || this.currentScreenSize === 'sm' || this.currentScreenSize === 'md') {
|
|
return '移动端';
|
|
} else if (this.currentScreenSize === 'lg') {
|
|
return '平板';
|
|
} else {
|
|
return '桌面端';
|
|
}
|
|
},
|
|
// 是否为移动端
|
|
isMobile() {
|
|
return this.currentScreenSize === 'xs' || this.currentScreenSize === 'sm' || this.currentScreenSize === 'md';
|
|
},
|
|
// 是否为平板
|
|
isTablet() {
|
|
return this.currentScreenSize === 'lg';
|
|
},
|
|
// 是否为桌面端
|
|
isDesktop() {
|
|
return this.currentScreenSize === 'xl' || this.currentScreenSize === 'xxl' || this.currentScreenSize === 'xxxl' || this.currentScreenSize === 'xxxxl';
|
|
}
|
|
},
|
|
mounted() {
|
|
this.initResponsiveHelper();
|
|
this.addEventListeners();
|
|
},
|
|
beforeDestroy() {
|
|
this.removeEventListeners();
|
|
if (this.responsiveHelper) {
|
|
this.responsiveHelper.destroy();
|
|
}
|
|
},
|
|
methods: {
|
|
/**
|
|
* 初始化响应式助手
|
|
*/
|
|
initResponsiveHelper() {
|
|
// 合并自定义断点
|
|
this.breakpoints = { ...this.breakpoints, ...this.customBreakpoints };
|
|
|
|
// 创建响应式助手实例
|
|
this.responsiveHelper = new this.$options.responsiveHelperClass || window.ResponsiveHelper;
|
|
|
|
if (this.responsiveHelper) {
|
|
// 更新断点配置
|
|
this.responsiveHelper.breakpoints = this.breakpoints;
|
|
|
|
// 检测初始屏幕尺寸
|
|
this.detectScreenSize();
|
|
|
|
// 检测设备特性
|
|
this.detectDeviceFeatures();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 检测屏幕尺寸
|
|
*/
|
|
detectScreenSize() {
|
|
const width = window.innerWidth;
|
|
this.screenWidth = width;
|
|
|
|
let newSize = '';
|
|
|
|
// 根据宽度确定屏幕尺寸
|
|
if (width <= this.breakpoints.xs) {
|
|
newSize = 'xs';
|
|
} else if (width <= this.breakpoints.sm) {
|
|
newSize = 'sm';
|
|
} else if (width <= this.breakpoints.md) {
|
|
newSize = 'md';
|
|
} else if (width <= this.breakpoints.lg) {
|
|
newSize = 'lg';
|
|
} else if (width <= this.breakpoints.xl) {
|
|
newSize = 'xl';
|
|
} else if (width <= this.breakpoints.xxl) {
|
|
newSize = 'xxl';
|
|
} else if (width <= this.breakpoints.xxxl) {
|
|
newSize = 'xxxl';
|
|
} else {
|
|
newSize = 'xxxxl';
|
|
}
|
|
|
|
// 如果尺寸发生变化,更新状态
|
|
if (newSize !== this.currentScreenSize) {
|
|
this.currentScreenSize = newSize;
|
|
this.$emit('screenSizeChange', {
|
|
size: newSize,
|
|
width: width,
|
|
height: window.innerHeight,
|
|
breakpoint: this.breakpoints[newSize]
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 检测设备特性
|
|
*/
|
|
detectDeviceFeatures() {
|
|
// 检测触摸设备
|
|
this.isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
|
|
// 检测高分辨率屏幕
|
|
this.isHighDPI = (window.devicePixelRatio || 1) >= 2;
|
|
},
|
|
|
|
/**
|
|
* 添加事件监听器
|
|
*/
|
|
addEventListeners() {
|
|
// 监听窗口大小变化
|
|
window.addEventListener('resize', this.handleResize);
|
|
|
|
// 监听方向变化
|
|
window.addEventListener('orientationchange', this.handleOrientationChange);
|
|
|
|
// 监听屏幕尺寸变化事件
|
|
window.addEventListener('screenSizeChange', this.handleScreenSizeChange);
|
|
},
|
|
|
|
/**
|
|
* 移除事件监听器
|
|
*/
|
|
removeEventListeners() {
|
|
window.removeEventListener('resize', this.handleResize);
|
|
window.removeEventListener('orientationchange', this.handleOrientationChange);
|
|
window.removeEventListener('screenSizeChange', this.handleScreenSizeChange);
|
|
},
|
|
|
|
/**
|
|
* 处理窗口大小变化
|
|
*/
|
|
handleResize() {
|
|
// 使用防抖优化性能
|
|
clearTimeout(this.resizeTimeout);
|
|
this.resizeTimeout = setTimeout(() => {
|
|
this.detectScreenSize();
|
|
}, 100);
|
|
},
|
|
|
|
/**
|
|
* 处理方向变化
|
|
*/
|
|
handleOrientationChange() {
|
|
// 方向变化后延迟检测,确保布局已更新
|
|
setTimeout(() => {
|
|
this.detectScreenSize();
|
|
}, 100);
|
|
},
|
|
|
|
/**
|
|
* 处理屏幕尺寸变化事件
|
|
*/
|
|
handleScreenSizeChange(event) {
|
|
this.currentScreenSize = event.detail.size;
|
|
this.screenWidth = event.detail.width;
|
|
},
|
|
|
|
/**
|
|
* 检查是否匹配特定断点
|
|
*/
|
|
matchesBreakpoint(breakpoint) {
|
|
return this.screenWidth <= this.breakpoints[breakpoint];
|
|
},
|
|
|
|
/**
|
|
* 获取当前屏幕信息
|
|
*/
|
|
getCurrentScreenInfo() {
|
|
return {
|
|
size: this.currentScreenSize,
|
|
width: this.screenWidth,
|
|
height: window.innerHeight,
|
|
breakpoint: this.breakpoints[this.currentScreenSize],
|
|
isMobile: this.isMobile,
|
|
isTablet: this.isTablet,
|
|
isDesktop: this.isDesktop,
|
|
isTouchDevice: this.isTouchDevice,
|
|
isHighDPI: this.isHighDPI
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 响应式显示/隐藏元素
|
|
*/
|
|
responsiveShow(element, breakpoint = 'lg') {
|
|
if (this.matchesBreakpoint(breakpoint)) {
|
|
element.style.display = 'block';
|
|
} else {
|
|
element.style.display = 'none';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 响应式隐藏/显示元素
|
|
*/
|
|
responsiveHide(element, breakpoint = 'lg') {
|
|
if (this.matchesBreakpoint(breakpoint)) {
|
|
element.style.display = 'none';
|
|
} else {
|
|
element.style.display = 'block';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 响应式切换类名
|
|
*/
|
|
responsiveToggleClass(element, className, breakpoint = 'lg') {
|
|
if (this.matchesBreakpoint(breakpoint)) {
|
|
element.classList.add(className);
|
|
} else {
|
|
element.classList.remove(className);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.responsive-layout {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.responsive-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* 响应式调试信息 */
|
|
.responsive-debug {
|
|
position: fixed;
|
|
top: 10px;
|
|
right: 10px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
color: white;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
font-size: 12px;
|
|
z-index: 9999;
|
|
max-width: 200px;
|
|
}
|
|
|
|
.debug-info {
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* 响应式工具类 */
|
|
.responsive-hidden-xs { display: none !important; }
|
|
.responsive-hidden-sm { display: none !important; }
|
|
.responsive-hidden-md { display: none !important; }
|
|
.responsive-hidden-lg { display: none !important; }
|
|
.responsive-hidden-xl { display: none !important; }
|
|
|
|
.responsive-visible-xs { display: block !important; }
|
|
.responsive-visible-sm { display: block !important; }
|
|
.responsive-visible-md { display: block !important; }
|
|
.responsive-visible-lg { display: block !important; }
|
|
.responsive-visible-xl { display: block !important; }
|
|
|
|
/* 响应式网格 */
|
|
.responsive-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.responsive-grid-2 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.responsive-grid-3 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.responsive-grid-4 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* 响应式弹性布局 */
|
|
.responsive-flex {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.responsive-flex > * {
|
|
flex: 1 1 300px;
|
|
}
|
|
|
|
/* 响应式列 */
|
|
.responsive-column {
|
|
flex: 1 1 clamp(300px, 50%, 600px);
|
|
}
|
|
|
|
/* 响应式容器 */
|
|
.responsive-wrapper {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* 响应式侧边栏 */
|
|
.responsive-sidebar {
|
|
width: clamp(250px, 25vw, 300px);
|
|
min-width: 250px;
|
|
}
|
|
|
|
/* 响应式主内容 */
|
|
.responsive-main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* 响应式按钮 */
|
|
.responsive-button {
|
|
width: 100%;
|
|
max-width: 300px;
|
|
padding: clamp(0.75rem, 2vw, 1.5rem);
|
|
font-size: clamp(0.875rem, 2vw, 1.125rem);
|
|
}
|
|
|
|
/* 响应式卡片 */
|
|
.responsive-card {
|
|
padding: clamp(1rem, 3vw, 2rem);
|
|
border-radius: clamp(0.5rem, 2vw, 1rem);
|
|
}
|
|
|
|
/* 响应式表单 */
|
|
.responsive-form {
|
|
display: grid;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.responsive-form input,
|
|
.responsive-form textarea,
|
|
.responsive-form select {
|
|
width: 100%;
|
|
padding: clamp(0.5rem, 2vw, 1rem);
|
|
font-size: clamp(0.875rem, 2vw, 1rem);
|
|
}
|
|
|
|
/* 响应式模态框 */
|
|
.responsive-modal {
|
|
width: min(90vw, 600px);
|
|
margin: clamp(2rem, 5vw, 5rem) auto;
|
|
padding: clamp(1rem, 3vw, 2rem);
|
|
}
|
|
|
|
/* 响应式图片 */
|
|
.responsive-image {
|
|
width: 100%;
|
|
height: auto;
|
|
max-width: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
/* 响应式表格 */
|
|
.responsive-table {
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
display: block;
|
|
}
|
|
|
|
.responsive-table table {
|
|
min-width: 600px;
|
|
}
|
|
|
|
/* 响应式导航 */
|
|
.responsive-nav {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.responsive-nav a {
|
|
flex: 1 1 auto;
|
|
text-align: center;
|
|
padding: 0.5rem 1rem;
|
|
}
|
|
|
|
/* 响应式文本 */
|
|
.responsive-text {
|
|
font-size: clamp(1rem, 2.5vw, 2rem);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* 响应式间距 */
|
|
.responsive-spacing {
|
|
padding: clamp(1rem, 3vw, 3rem);
|
|
margin: clamp(0.5rem, 2vw, 2rem);
|
|
}
|
|
|
|
/* 响应式阴影 */
|
|
.responsive-shadow-sm { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
|
|
.responsive-shadow { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }
|
|
.responsive-shadow-lg { box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); }
|
|
|
|
/* 响应式圆角 */
|
|
.responsive-rounded { border-radius: 0.375rem; }
|
|
.responsive-rounded-sm { border-radius: 0.25rem; }
|
|
.responsive-rounded-lg { border-radius: 0.5rem; }
|
|
.responsive-rounded-full { border-radius: 9999px; }
|
|
|
|
/* 响应式过渡 */
|
|
.responsive-transition { transition: all 0.3s ease; }
|
|
.responsive-transition-fast { transition: all 0.15s ease; }
|
|
.responsive-transition-slow { transition: all 0.5s ease; }
|
|
|
|
/* 响应式变换 */
|
|
.responsive-scale-95 { transform: scale(0.95); }
|
|
.responsive-scale-105 { transform: scale(1.05); }
|
|
.responsive-rotate-90 { transform: rotate(90deg); }
|
|
.responsive-rotate-180 { transform: rotate(180deg); }
|
|
|
|
/* 响应式颜色 */
|
|
.responsive-text-primary { color: #3498db; }
|
|
.responsive-text-success { color: #27ae60; }
|
|
.responsive-text-warning { color: #f39c12; }
|
|
.responsive-text-danger { color: #e74c3c; }
|
|
|
|
.responsive-bg-primary { background-color: #3498db; }
|
|
.responsive-bg-success { background-color: #27ae60; }
|
|
.responsive-bg-warning { background-color: #f39c12; }
|
|
.responsive-bg-danger { background-color: #e74c3c; }
|
|
|
|
/* 响应式间距工具类 */
|
|
.responsive-m-0 { margin: 0 !important; }
|
|
.responsive-m-1 { margin: 0.25rem !important; }
|
|
.responsive-m-2 { margin: 0.5rem !important; }
|
|
.responsive-m-3 { margin: 1rem !important; }
|
|
.responsive-m-4 { margin: 1.5rem !important; }
|
|
.responsive-m-5 { margin: 3rem !important; }
|
|
|
|
.responsive-p-0 { padding: 0 !important; }
|
|
.responsive-p-1 { padding: 0.25rem !important; }
|
|
.responsive-p-2 { padding: 0.5rem !important; }
|
|
.responsive-p-3 { padding: 1rem !important; }
|
|
.responsive-p-4 { padding: 1.5rem !important; }
|
|
.responsive-p-5 { padding: 3rem !important; }
|
|
|
|
/* 响应式文本对齐 */
|
|
.responsive-text-center { text-align: center; }
|
|
.responsive-text-left { text-align: left; }
|
|
.responsive-text-right { text-align: right; }
|
|
|
|
/* 响应式浮动 */
|
|
.responsive-float-left { float: left; }
|
|
.responsive-float-right { float: right; }
|
|
|
|
/* 响应式清除浮动 */
|
|
.responsive-clearfix::after {
|
|
content: "";
|
|
clear: both;
|
|
display: table;
|
|
}
|
|
|
|
/* 响应式隐藏/显示 */
|
|
.responsive-hidden { display: none !important; }
|
|
.responsive-visible { display: block !important; }
|
|
|
|
/* 响应式内联 */
|
|
.responsive-inline { display: inline !important; }
|
|
.responsive-inline-block { display: inline-block !important; }
|
|
|
|
/* 响应式块级 */
|
|
.responsive-block { display: block !important; }
|
|
|
|
/* 响应式弹性 */
|
|
.responsive-flex { display: flex !important; }
|
|
.responsive-inline-flex { display: inline-flex !important; }
|
|
|
|
/* 响应式网格 */
|
|
.responsive-grid { display: grid !important; }
|
|
.responsive-inline-grid { display: inline-grid !important; }
|
|
|
|
/* 响应式表格 */
|
|
.responsive-table { display: table !important; }
|
|
.responsive-table-row { display: table-row !important; }
|
|
.responsive-table-cell { display: table-cell !important; }
|
|
|
|
/* 响应式定位 */
|
|
.responsive-relative { position: relative !important; }
|
|
.responsive-absolute { position: absolute !important; }
|
|
.responsive-fixed { position: fixed !important; }
|
|
.responsive-sticky { position: sticky !important; }
|
|
|
|
/* 响应式溢出 */
|
|
.responsive-overflow-auto { overflow: auto !important; }
|
|
.responsive-overflow-hidden { overflow: hidden !important; }
|
|
.responsive-overflow-visible { overflow: visible !important; }
|
|
.responsive-overflow-scroll { overflow: scroll !important; }
|
|
|
|
/* 响应式光标 */
|
|
.responsive-cursor-pointer { cursor: pointer !important; }
|
|
.responsive-cursor-default { cursor: default !important; }
|
|
.responsive-cursor-not-allowed { cursor: not-allowed !important; }
|
|
|
|
/* 响应式用户选择 */
|
|
.responsive-select-none { user-select: none !important; }
|
|
.responsive-select-text { user-select: text !important; }
|
|
.responsive-select-all { user-select: all !important; }
|
|
|
|
/* 响应式指针事件 */
|
|
.responsive-pointer-events-none { pointer-events: none !important; }
|
|
.responsive-pointer-events-auto { pointer-events: auto !important; }
|
|
|
|
/* 响应式可见性 */
|
|
.responsive-visible { visibility: visible !important; }
|
|
.responsive-invisible { visibility: hidden !important; }
|
|
|
|
/* 响应式透明度 */
|
|
.responsive-opacity-0 { opacity: 0 !important; }
|
|
.responsive-opacity-25 { opacity: 0.25 !important; }
|
|
.responsive-opacity-50 { opacity: 0.5 !important; }
|
|
.responsive-opacity-75 { opacity: 0.75 !important; }
|
|
.responsive-opacity-100 { opacity: 1 !important; }
|
|
|
|
/* 响应式Z索引 */
|
|
.responsive-z-0 { z-index: 0 !important; }
|
|
.responsive-z-10 { z-index: 10 !important; }
|
|
.responsive-z-20 { z-index: 20 !important; }
|
|
.responsive-z-30 { z-index: 30 !important; }
|
|
.responsive-z-40 { z-index: 40 !important; }
|
|
.responsive-z-50 { z-index: 50 !important; }
|
|
|
|
/* 响应式宽度 */
|
|
.responsive-w-full { width: 100% !important; }
|
|
.responsive-w-auto { width: auto !important; }
|
|
.responsive-w-screen { width: 100vw !important; }
|
|
|
|
/* 响应式高度 */
|
|
.responsive-h-full { height: 100% !important; }
|
|
.responsive-h-auto { height: auto !important; }
|
|
.responsive-h-screen { height: 100vh !important; }
|
|
|
|
/* 响应式最大宽度 */
|
|
.responsive-max-w-none { max-width: none !important; }
|
|
.responsive-max-w-full { max-width: 100% !important; }
|
|
.responsive-max-w-screen-sm { max-width: 640px !important; }
|
|
.responsive-max-w-screen-md { max-width: 768px !important; }
|
|
.responsive-max-w-screen-lg { max-width: 1024px !important; }
|
|
.responsive-max-w-screen-xl { max-width: 1280px !important; }
|
|
|
|
/* 响应式最小宽度 */
|
|
.responsive-min-w-0 { min-width: 0 !important; }
|
|
.responsive-min-w-full { min-width: 100% !important; }
|
|
|
|
/* 响应式最大高度 */
|
|
.responsive-max-h-none { max-height: none !important; }
|
|
.responsive-max-h-full { max-height: 100% !important; }
|
|
.responsive-max-h-screen { max-height: 100vh !important; }
|
|
|
|
/* 响应式最小高度 */
|
|
.responsive-min-h-0 { min-height: 0 !important; }
|
|
.responsive-min-h-full { min-height: 100% !important; }
|
|
.responsive-min-h-screen { min-height: 100vh !important; }
|
|
</style>
|