|
@@ -6,200 +6,201 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
|
- import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
- import { debounce } from 'lodash-es';
|
|
|
|
|
- import { useAppStore } from '/@/store/modules/app';
|
|
|
|
|
- import { computed } from 'vue';
|
|
|
|
|
-
|
|
|
|
|
- // 类型定义
|
|
|
|
|
- interface AdaptiveOptions {
|
|
|
|
|
- width?: number;
|
|
|
|
|
- height?: number;
|
|
|
|
|
- baseWidth?: number; // 设计稿基准宽度,用于REM计算
|
|
|
|
|
- minScale?: number; // 最小缩放比例
|
|
|
|
|
- maxScale?: number; // 最大缩放比例
|
|
|
|
|
- debounceTime?: number; // 防抖时间(ms)
|
|
|
|
|
- ratio?: number; // 宽高比
|
|
|
|
|
- tolerance?: number; // 宽高比容忍度
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- interface ScaleInfo {
|
|
|
|
|
- widthScale: number;
|
|
|
|
|
- heightScale: number;
|
|
|
|
|
- scale: number;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- export default defineComponent({
|
|
|
|
|
- name: 'AdaptiveContainer',
|
|
|
|
|
- props: {
|
|
|
|
|
- options: {
|
|
|
|
|
- type: Object as () => AdaptiveOptions,
|
|
|
|
|
- default: () => ({}),
|
|
|
|
|
- },
|
|
|
|
|
|
|
+import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
+import { debounce } from 'lodash-es';
|
|
|
|
|
+import { useAppStore } from '/@/store/modules/app';
|
|
|
|
|
+import { computed } from 'vue';
|
|
|
|
|
+
|
|
|
|
|
+// 类型定义
|
|
|
|
|
+interface AdaptiveOptions {
|
|
|
|
|
+ width?: number;
|
|
|
|
|
+ height?: number;
|
|
|
|
|
+ baseWidth?: number; // 设计稿基准宽度,用于REM计算
|
|
|
|
|
+ minScale?: number; // 最小缩放比例
|
|
|
|
|
+ maxScale?: number; // 最大缩放比例
|
|
|
|
|
+ debounceTime?: number; // 防抖时间(ms)
|
|
|
|
|
+ ratio?: number; // 宽高比
|
|
|
|
|
+ tolerance?: number; // 宽高比容忍度
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ScaleInfo {
|
|
|
|
|
+ widthScale: number;
|
|
|
|
|
+ heightScale: number;
|
|
|
|
|
+ scale: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default defineComponent({
|
|
|
|
|
+ name: 'AdaptiveContainer',
|
|
|
|
|
+ props: {
|
|
|
|
|
+ options: {
|
|
|
|
|
+ type: Object as () => AdaptiveOptions,
|
|
|
|
|
+ default: () => ({}),
|
|
|
},
|
|
},
|
|
|
- setup(props) {
|
|
|
|
|
- const appStore = useAppStore();
|
|
|
|
|
- const containerRef = ref<HTMLElement | null>(null);
|
|
|
|
|
- // const isReady = ref(false);
|
|
|
|
|
-
|
|
|
|
|
- // 默认配置
|
|
|
|
|
- const defaultOptions = ref<Required<AdaptiveOptions>>({
|
|
|
|
|
- width: 1920,
|
|
|
|
|
- height: 948,
|
|
|
|
|
- baseWidth: 1920,
|
|
|
|
|
- minScale: 0.5,
|
|
|
|
|
- maxScale: 2,
|
|
|
|
|
- debounceTime: 100,
|
|
|
|
|
- ratio: 16 / 9,
|
|
|
|
|
- tolerance: 0.1,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 合并配置
|
|
|
|
|
- const config = computed(() => {
|
|
|
|
|
- return {
|
|
|
|
|
- ...defaultOptions.value,
|
|
|
|
|
- ...props.options,
|
|
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 设计稿尺寸
|
|
|
|
|
- const designSize = computed(() => {
|
|
|
|
|
- return {
|
|
|
|
|
- width: config.value.width,
|
|
|
|
|
- height: config.value.height,
|
|
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 设置REM基准值
|
|
|
|
|
- * @param baseSize 基准大小,默认18px
|
|
|
|
|
- */
|
|
|
|
|
- const setRem = (baseSize: number = 18): void => {
|
|
|
|
|
- try {
|
|
|
|
|
- const baseVal = baseSize / config.value.baseWidth;
|
|
|
|
|
- const vW = window.innerWidth;
|
|
|
|
|
- const rem = vW * baseVal;
|
|
|
|
|
-
|
|
|
|
|
- // 设置全局缩放比例(如果有需要)
|
|
|
|
|
- if (typeof window['$size'] !== 'number') {
|
|
|
|
|
- Object.defineProperty(window, '$size', {
|
|
|
|
|
- value: rem / 100,
|
|
|
|
|
- writable: true,
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
- window['$size'] = rem / 100;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // document.documentElement.style.fontSize = `${rem}px`;
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('设置REM失败:', error);
|
|
|
|
|
- // 回退方案:使用固定REM
|
|
|
|
|
- document.documentElement.style.fontSize = `${baseSize}px`;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ setup(props) {
|
|
|
|
|
+ const appStore = useAppStore();
|
|
|
|
|
+ const containerRef = ref<HTMLElement | null>(null);
|
|
|
|
|
+ // const isReady = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+ // 默认配置
|
|
|
|
|
+ const defaultOptions = ref<Required<AdaptiveOptions>>({
|
|
|
|
|
+ width: 1920,
|
|
|
|
|
+ height: 948,
|
|
|
|
|
+ baseWidth: 1920,
|
|
|
|
|
+ minScale: 0.5,
|
|
|
|
|
+ maxScale: 2,
|
|
|
|
|
+ debounceTime: 100,
|
|
|
|
|
+ ratio: 16 / 9,
|
|
|
|
|
+ tolerance: 0.1,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 合并配置
|
|
|
|
|
+ const config = computed(() => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...defaultOptions.value,
|
|
|
|
|
+ ...props.options,
|
|
|
};
|
|
};
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 计算缩放比例
|
|
|
|
|
- */
|
|
|
|
|
- const calculateScale = (): ScaleInfo => {
|
|
|
|
|
- const { width: designWidth, height: designHeight } = designSize.value;
|
|
|
|
|
- const { innerWidth: currentWidth, innerHeight: currentHeight } = window;
|
|
|
|
|
- const { minScale, maxScale } = config.value;
|
|
|
|
|
-
|
|
|
|
|
- // 计算原始比例
|
|
|
|
|
- const rawWidthScale = currentWidth / designWidth;
|
|
|
|
|
- const rawHeightScale = currentHeight / designHeight;
|
|
|
|
|
-
|
|
|
|
|
- // 应用比例限制
|
|
|
|
|
- const widthScale = Math.max(minScale, Math.min(maxScale, rawWidthScale));
|
|
|
|
|
- const heightScale = Math.max(minScale, Math.min(maxScale, rawHeightScale));
|
|
|
|
|
-
|
|
|
|
|
- // 选择最小比例保持内容完整显示
|
|
|
|
|
- const scale = Math.min(widthScale, heightScale);
|
|
|
|
|
-
|
|
|
|
|
- return { widthScale, heightScale, scale };
|
|
|
|
|
|
|
+ // 设计稿尺寸
|
|
|
|
|
+ const designSize = computed(() => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ width: config.value.width,
|
|
|
|
|
+ height: config.value.height,
|
|
|
};
|
|
};
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置REM基准值
|
|
|
|
|
+ * @param baseSize 基准大小,默认18px
|
|
|
|
|
+ */
|
|
|
|
|
+ const setRem = (baseSize: number = 18): void => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const baseVal = baseSize / config.value.baseWidth;
|
|
|
|
|
+ const vW = window.innerWidth;
|
|
|
|
|
+ const rem = vW * baseVal;
|
|
|
|
|
+
|
|
|
|
|
+ // 设置全局缩放比例(如果有需要)
|
|
|
|
|
+ if (typeof window['$size'] !== 'number') {
|
|
|
|
|
+ Object.defineProperty(window, '$size', {
|
|
|
|
|
+ value: rem / 100,
|
|
|
|
|
+ writable: true,
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ window['$size'] = rem / 100;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 应用缩放变换
|
|
|
|
|
- */
|
|
|
|
|
- const applyScale = (): void => {
|
|
|
|
|
- if (!containerRef.value) return;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // document.documentElement.style.fontSize = `${rem}px`;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('设置REM失败:', error);
|
|
|
|
|
+ // 回退方案:使用固定REM
|
|
|
|
|
+ document.documentElement.style.fontSize = `${baseSize}px`;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算缩放比例
|
|
|
|
|
+ */
|
|
|
|
|
+ const calculateScale = (): ScaleInfo => {
|
|
|
|
|
+ const { width: designWidth, height: designHeight } = designSize.value;
|
|
|
|
|
+ const { innerWidth: currentWidth, innerHeight: currentHeight } = window;
|
|
|
|
|
+ const { minScale, maxScale } = config.value;
|
|
|
|
|
+
|
|
|
|
|
+ // 计算原始比例
|
|
|
|
|
+ const rawWidthScale = currentWidth / designWidth;
|
|
|
|
|
+ const rawHeightScale = currentHeight / designHeight;
|
|
|
|
|
+
|
|
|
|
|
+ // 应用比例限制
|
|
|
|
|
+ const widthScale = Math.max(minScale, Math.min(maxScale, rawWidthScale));
|
|
|
|
|
+ const heightScale = Math.max(minScale, Math.min(maxScale, rawHeightScale));
|
|
|
|
|
+
|
|
|
|
|
+ // 选择最小比例保持内容完整显示
|
|
|
|
|
+ const scale = Math.min(widthScale, heightScale);
|
|
|
|
|
+
|
|
|
|
|
+ return { widthScale, heightScale, scale };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 应用缩放变换
|
|
|
|
|
+ */
|
|
|
|
|
+ const applyScale = (): void => {
|
|
|
|
|
+ if (!containerRef.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ const container = containerRef.value;
|
|
|
|
|
+ const { widthScale, heightScale } = calculateScale();
|
|
|
|
|
+
|
|
|
|
|
+ // 应用缩放
|
|
|
|
|
+ container.style.transform = `scale(${widthScale}, ${heightScale})`;
|
|
|
|
|
+ // container.style.transformOrigin = 'top left';
|
|
|
|
|
+
|
|
|
|
|
+ // 更新store中的比例信息(如果需要)
|
|
|
|
|
+ debugger;
|
|
|
|
|
+ appStore.setWidthScale(widthScale);
|
|
|
|
|
+ appStore.setHeightScale(heightScale);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理窗口大小变化
|
|
|
|
|
+ */
|
|
|
|
|
+ const handleResize = debounce((): void => {
|
|
|
|
|
+ // 更新缩放和REM
|
|
|
|
|
+ applyScale();
|
|
|
|
|
+ setRem();
|
|
|
|
|
+ }, config.value.debounceTime);
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(async () => {
|
|
|
|
|
+ // 生命周期
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!containerRef.value) {
|
|
|
|
|
+ // if (!document.getElementById('app')) {
|
|
|
|
|
+ throw new Error('容器元素未找到');
|
|
|
|
|
+ }
|
|
|
const container = containerRef.value;
|
|
const container = containerRef.value;
|
|
|
- const { widthScale, heightScale } = calculateScale();
|
|
|
|
|
-
|
|
|
|
|
- // 应用缩放
|
|
|
|
|
- container.style.transform = `scale(${widthScale}, ${heightScale})`;
|
|
|
|
|
- // container.style.transformOrigin = 'top left';
|
|
|
|
|
|
|
+ const { ratio, tolerance } = config.value;
|
|
|
|
|
+ const normalRatio = Math.abs(ratio - container.clientWidth / container.clientHeight) < ratio * tolerance;
|
|
|
|
|
+ // const container = document.getElementById('app')!;
|
|
|
|
|
+ if (normalRatio) {
|
|
|
|
|
+ defaultOptions.value.width = container.clientWidth;
|
|
|
|
|
+ defaultOptions.value.height = container.clientHeight;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 更新store中的比例信息(如果需要)
|
|
|
|
|
- appStore.setWidthScale(widthScale);
|
|
|
|
|
- appStore.setHeightScale(heightScale);
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const { width, height } = designSize.value;
|
|
|
|
|
+ container.style.width = `${width}px`;
|
|
|
|
|
+ container.style.height = `${height}px`;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 处理窗口大小变化
|
|
|
|
|
- */
|
|
|
|
|
- const handleResize = debounce((): void => {
|
|
|
|
|
- // 更新缩放和REM
|
|
|
|
|
applyScale();
|
|
applyScale();
|
|
|
setRem();
|
|
setRem();
|
|
|
- }, config.value.debounceTime);
|
|
|
|
|
-
|
|
|
|
|
- onMounted(async () => {
|
|
|
|
|
- // 生命周期
|
|
|
|
|
- try {
|
|
|
|
|
- if (!containerRef.value) {
|
|
|
|
|
- // if (!document.getElementById('app')) {
|
|
|
|
|
- throw new Error('容器元素未找到');
|
|
|
|
|
- }
|
|
|
|
|
- const container = containerRef.value;
|
|
|
|
|
- const { ratio, tolerance } = config.value;
|
|
|
|
|
- const normalRatio = Math.abs(ratio - container.clientWidth / container.clientHeight) < ratio * tolerance;
|
|
|
|
|
- // const container = document.getElementById('app')!;
|
|
|
|
|
- if (normalRatio) {
|
|
|
|
|
- defaultOptions.value.width = container.clientWidth;
|
|
|
|
|
- defaultOptions.value.height = container.clientHeight;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const { width, height } = designSize.value;
|
|
|
|
|
- container.style.width = `${width}px`;
|
|
|
|
|
- container.style.height = `${height}px`;
|
|
|
|
|
-
|
|
|
|
|
- applyScale();
|
|
|
|
|
- setRem();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('初始化自适应容器失败:', error);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('resize', handleResize);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- onUnmounted(() => {
|
|
|
|
|
- window.removeEventListener('resize', handleResize);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- containerRef,
|
|
|
|
|
- // isReady,
|
|
|
|
|
- };
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('初始化自适应容器失败:', error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ window.addEventListener('resize', handleResize);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ window.removeEventListener('resize', handleResize);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ containerRef,
|
|
|
|
|
+ // isReady,
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
<style lang="less" scoped>
|
|
|
- .adaptive-container {
|
|
|
|
|
- // position: relative;
|
|
|
|
|
- // top: 0;
|
|
|
|
|
- // left: 0;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- transform-origin: left top;
|
|
|
|
|
- // z-index: 0;
|
|
|
|
|
- // 防止缩放导致的模糊(开启GPU加速)
|
|
|
|
|
- backface-visibility: hidden;
|
|
|
|
|
- // -webkit-font-smoothing: antialiased;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.adaptive-container {
|
|
|
|
|
+ // position: relative;
|
|
|
|
|
+ // top: 0;
|
|
|
|
|
+ // left: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ transform-origin: left top;
|
|
|
|
|
+ // z-index: 0;
|
|
|
|
|
+ // 防止缩放导致的模糊(开启GPU加速)
|
|
|
|
|
+ backface-visibility: hidden;
|
|
|
|
|
+ // -webkit-font-smoothing: antialiased;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|