npm install screenfull chart.js # 可选,用于全屏和图表
# 或使用yarn
yarn add screenfull chart.js // main.js
import { createApp } from 'vue';
import App from './App.vue';
import { ScreenAdaptPlugin } from './plugins/screen-adapt';
const app = createApp(App);
app.use(ScreenAdaptPlugin, {
designWidth: 1920, // 设计稿宽度
designHeight: 1080, // 设计稿高度
maxScale: 1.2, // 最大缩放比例
minScale: 0.6, // 最小缩放比例
target: '#app' // 应用缩放的目标元素
});
app.mount('#app'); <template>
<div class="dashboard">
<Header v-adapt-font:28>数据监控大屏</Header>
<div class="grid-container">
<Card title="访问统计" :width="500" :height="300">
<ChartComponent :data="visitorData" />
</Card>
<Card title="在线人数" :width="500" :height="300">
<ChartComponent :data="onlineData" />
</Card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Header from './components/Header.vue';
import Card from './components/Card.vue';
import ChartComponent from './components/ChartComponent.vue';
const visitorData = ref([120, 190, 300, 240, 290, 350, 400]);
const onlineData = ref([50, 80, 120, 90, 150, 180, 200]);
</script> <template>
<div>
<h1 v-adapt-font:24>标题文字</h1>
<p v-adapt-font>默认大小文字</p>
</div>
</template> import { inject } from 'vue';
const screenScale = inject('screenScale');
// 在计算属性中使用
const cardWidth = computed(() => 500 * screenScale); export default {
methods: {
// 自适应尺寸
adaptSize(size) {
return size * this.$screenAdapt.getScale();
},
// 自适应字体
adaptFont(size) {
return `${size * this.$screenAdapt.getScale()}px`;
}
}
}<!-- components/Card.vue -->
<template>
<div class="card" :style="cardStyle">
<h3 v-adapt-font:18>{{ title }}</h3>
<div class="content">
<slot />
</div>
</div>
</template>
<script setup>
import { computed, inject } from 'vue';
const props = defineProps({
title: {
type: String,
default: ''
},
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 300
}
});
const screenScale = inject('screenScale');
const cardStyle = computed(() => ({
width: `${props.width * screenScale}px`,
height: `${props.height * screenScale}px`,
padding: `${15 * screenScale}px`,
borderRadius: `${8 * screenScale}px`,
margin: `${10 * screenScale}px`
}));
</script><!-- components/ChartComponent.vue -->
<template>
<div class="chart-container" :style="containerStyle">
<canvas ref="chartCanvas"></canvas>
</div>
</template>
<script setup>
import { ref, onMounted, watch, inject } from 'vue';
import Chart from 'chart.js/auto';
const props = defineProps({
data: {
type: Array,
required: true
},
type: {
type: String,
default: 'line'
},
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 200
}
});
const chartCanvas = ref(null);
const chartInstance = ref(null);
const screenScale = inject('screenScale');
const containerStyle = computed(() => ({
width: `${props.width * screenScale}px`,
height: `${props.height * screenScale}px`
}));
const initChart = () => {
if (chartInstance.value) {
chartInstance.value.destroy();
}
const ctx = chartCanvas.value.getContext('2d');
chartInstance.value = new Chart(ctx, {
type: props.type,
data: {
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
datasets: [{
label: '数据趋势',
data: props.data,
borderColor: '#36A2EB',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
font: {
size: 12 * screenScale
}
}
}
},
scales: {
y: {
ticks: {
font: {
size: 10 * screenScale
}
}
},
x: {
ticks: {
font: {
size: 10 * screenScale
}
}
}
}
}
});
};
onMounted(() => {
initChart();
});
watch(() => [props.data, screenScale], () => {
if (chartCanvas.value) {
initChart();
}
});
</script><!-- components/GridLayout.vue -->
<template>
<div class="grid-layout" :style="layoutStyle">
<slot />
</div>
</template>
<script setup>
import { computed, inject } from 'vue';
const props = defineProps({
columns: {
type: Number,
default: 2
},
gap: {
type: Number,
default: 20
},
width: {
type: Number,
default: 1920
}
});
const screenScale = inject('screenScale');
const layoutStyle = computed(() => ({
display: 'grid',
gridTemplateColumns: `repeat(${props.columns}, 1fr)`,
gap: `${props.gap * screenScale}px`,
width: `${props.width * screenScale}px`
}));
</script><!-- Dashboard.vue -->
<template>
<div class="dashboard-container">
<div class="header" v-adapt-font:32>数据监控中心</div>
<div class="toolbar">
<button v-adapt-font @click="toggleFullscreen">全屏显示</button>
<button v-adapt-font @click="refreshData">刷新数据</button>
</div>
<GridLayout :columns="2">
<Card title="访问统计" :width="800" :height="400">
<ChartComponent :data="visitorData" type="line" />
</Card>
<Card title="在线人数" :width="800" :height="400">
<ChartComponent :data="onlineData" type="bar" />
</Card>
<Card title="地域分布" :width="800" :height="400">
<MapComponent :data="regionData" />
</Card>
<Card title="系统状态" :width="800" :height="400">
<StatusComponent :data="systemData" />
</Card>
</GridLayout>
</div>
</template>
<script setup>
import { ref } from 'vue';
import screenfull from 'screenfull';
import GridLayout from './components/GridLayout.vue';
import Card from './components/Card.vue';
import ChartComponent from './components/ChartComponent.vue';
import MapComponent from './components/MapComponent.vue';
import StatusComponent from './components/StatusComponent.vue';
// 模拟数据
const visitorData = ref([120, 190, 300, 240, 290, 350, 400]);
const onlineData = ref([50, 80, 120, 90, 150, 180, 200]);
const regionData = ref([
{ name: '华东', value: 4500 },
{ name: '华北', value: 3200 },
{ name: '华南', value: 2800 },
{ name: '西南', value: 1500 },
{ name: '西北', value: 900 },
{ name: '东北', value: 1200 }
]);
const systemData = ref({
cpu: 65,
memory: 78,
disk: 45,
traffic: 82
});
// 全屏切换
const toggleFullscreen = () => {
if (screenfull.isEnabled) {
screenfull.toggle();
}
};
// 刷新数据
const refreshData = () => {
// 模拟数据刷新
visitorData.value = visitorData.value.map(v => v * (0.8 + Math.random() * 0.4));
onlineData.value = onlineData.value.map(v => v * (0.9 + Math.random() * 0.2));
};
</script><!-- MapComponent.vue -->
<template>
<div class="map-container" :style="containerStyle">
<!-- 地图内容 -->
<svg viewBox="0 0 1000 600" :style="svgStyle">
<!-- 地图路径 -->
<path v-for="(region, index)" :key="index" :d="region.path"
:fill="region.color" @mouseover="highlightRegion(index)" />
<!-- 数据点 -->
<circle v-for="(point, index)" :key="index"
:cx="point.x" :cy="point.y"
:r="point.value / 100" fill="#36A2EB" />
</svg>
</div>
</template>
<script setup>
import { computed, inject, ref } from 'vue';
const props = defineProps({
data: {
type: Array,
required: true
},
width: {
type: Number,
default: 800
},
height: {
type: Number,
default: 400
}
});
const screenScale = inject('screenScale');
const highlightedRegion = ref(null);
const containerStyle = computed(() => ({
width: `${props.width * screenScale}px`,
height: `${props.height * screenScale}px`
}));
const svgStyle = computed(() => ({
width: '100%',
height: '100%'
}));
const highlightRegion = (index) => {
highlightedRegion.value = index;
};
</script><!-- VirtualList.vue -->
<template>
<div class="virtual-list" :style="listStyle">
<div class="list-content" :style="contentStyle">
<div
v-for="item in visibleItems"
:key="item.id"
:style="itemStyle(item)"
class="list-item"
>
<span v-adapt-font>{{ item.name }}</span>
<span v-adapt-font>{{ item.value }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
const props = defineProps({
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 40
},
width: {
type: Number,
default: 400
},
height: {
type: Number,
default: 500
}
});
const listRef = ref(null);
const contentRef = ref(null);
// 计算可见区域的起始和结束索引
const calculateVisibleRange = () => {
if (!listRef.value) return { start: 0, end: 0 };
const scrollTop = listRef.value.scrollTop;
const visibleHeight = listRef.value.clientHeight;
const startIndex = Math.floor(scrollTop / props.itemHeight);
const endIndex = Math.ceil((scrollTop + visibleHeight) / props.itemHeight);
return { start: startIndex, end: endIndex };
};
// 可见项目
const visibleItems = computed(() => {
const { start, end } = calculateVisibleRange();
return props.items.slice(start, end);
});
// 列表样式
const listStyle = computed(() => ({
width: `${props.width}px`,
height: `${props.height}px`,
overflow: 'auto',
position: 'relative'
}));
// 内容样式
const contentStyle = computed(() => ({
height: `${props.items.length * props.itemHeight}px`
}));
// 项目样式
const itemStyle = (item) => {
const index = props.items.findIndex(i => i.id === item.id);
return {
position: 'absolute',
top: `${index * props.itemHeight}px`,
width: '100%',
height: `${props.itemHeight}px`,
padding: '10px',
boxSizing: 'border-box'
};
};
// 处理滚动
const handleScroll = () => {
// 更新可见项目
};
onMounted(() => {
listRef.value.addEventListener('scroll', handleScroll);
});
watch(() => props.items, () => {
// 重新计算可见区域
});
</script>// 按需加载重型组件
const HeavyComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
delay: 200
});
// 在大屏中使用
<HeavyComponent v-if="showHeavyComponent" /><template>
<img
v-lazy="imageUrl"
:width="adaptSize(300)"
:height="adaptSize(200)"
alt="示例图片"
/>
</template>
<script setup>
import { ref } from 'vue';
const imageUrl = ref('https://picsum.photos/800/600');
</script>// 测试不同分辨率
const testResolutions = [
{ width: 1920, height: 1080 },
{ width: 1600, height: 900 },
{ width: 1366, height: 768 },
{ width: 1280, height: 720 }
];
const simulateResolution = (resolution) => {
document.documentElement.style.width = `${resolution.width}px`;
document.documentElement.style.height = `${resolution.height}px`;
window.dispatchEvent(new Event('resize'));
};
// 使用方法
testResolutions.forEach((resolution, index) => {
setTimeout(() => {
simulateResolution(resolution);
console.log(`测试分辨率: ${resolution.width}x${resolution.height}`);
}, index * 3000);
});// 使用Performance API监控渲染性能
const monitorPerformance = () => {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.name === 'render') {
console.log('渲染时间:', entry.duration, 'ms');
}
});
});
observer.observe({ entryTypes: ['measure'] });
// 在关键渲染点标记
performance.mark('render-start');
// 渲染操作...
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');
};通过本文提供的方法,你可以在Vue3项目中高效地实现和封装大屏自适应组件。关键要点包括:
这个方案适用于各类大屏展示系统,如数据可视化平台、监控中心等。根据具体业务需求,你可以进一步扩展其功能,如添加主题切换、多语言支持等。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。