Procházet zdrojové kódy

[Feat 0000]首页增加自适应

wangkeyi před 4 měsíci
rodič
revize
c1508451b8

+ 1 - 0
package.json

@@ -58,6 +58,7 @@
     "path-to-regexp": "^6.3.0",
     "pinia": "2.1.7",
     "print-js": "^1.6.0",
+    "qiankun": "^2.10.16",
     "qrcode": "^1.5.4",
     "qs": "^6.14.0",
     "resize-observer-polyfill": "^1.5.1",

+ 34 - 6
src/App.vue

@@ -1,13 +1,15 @@
 <template>
-  <ConfigProvider :theme="appTheme" :locale="getAntdLocale">
-    <AppProvider>
-      <RouterView />
-    </AppProvider>
-  </ConfigProvider>
+  <AdaptiveContainer :options="{ width: width, height: height }" style="overflow-y: hidden">
+    <ConfigProvider :theme="appTheme" :locale="getAntdLocale">
+      <AppProvider>
+        <RouterView />
+      </AppProvider>
+    </ConfigProvider>
+  </AdaptiveContainer>
 </template>
 
 <script lang="ts" setup>
-  import { watch, ref } from 'vue';
+  import { watch, ref, nextTick, provide, onUnmounted} from 'vue';
   import { theme } from 'ant-design-vue';
   import { ConfigProvider } from 'ant-design-vue';
   import { AppProvider } from '/@/components/Application';
@@ -17,12 +19,38 @@
   import { useRootSetting } from '/@/hooks/setting/useRootSetting';
   import { ThemeEnum } from '/@/enums/appEnum';
   import { changeTheme } from '/@/logics/theme/index';
+  import AdaptiveContainer from '/@/components/Container/src/Adaptive.vue';
 
   const appStore = useAppStore();
   // 解决日期时间国际化问题
   import 'dayjs/locale/zh-cn';
   // support Multi-language
   const { getAntdLocale } = useLocale();
+  const isReload = ref(false);
+  const width = ref(1920);
+  const height = ref(928);
+
+  const body = document.body.getBoundingClientRect();
+  if (screen.height === body.height && screen.width === body.width) {
+    height.value = 1080;
+  }
+
+  const reloadRouter = () => {
+    isReload.value = true;
+    nextTick(() => {
+      isReload.value = false;
+    });
+  };
+  provide('reloadRouter', reloadRouter);
+  onUnmounted(() => {
+    window['renderer']?.dispose();
+    window['renderer']?.forceContextLoss();
+    if (window['renderer']) {
+      window['renderer'].content = null;
+    }
+    const gl = window['renderer']?.domElement.getContext('webgl');
+    gl && gl.getExtension('WEBGL_lose_context').loseContext();
+  });
 
   useTitle();
   /**

+ 2 - 2
src/components/Configurable/detail/CustomChart.vue

@@ -416,14 +416,14 @@
         legend: baseSeries.map((e) => {
           return {
             orient: 'vertical',
-            right: '5%',
+            right: '20%',
             top: 'middle',
             itemGap: 20,
             itemWidth: 10,
             itemHeight: 10,
             align: 'left',
             textStyle: {
-              fontSize: 20,
+              fontSize: 18,
               color: '#000',
             },
             data: e.data.map((e) => {

+ 12 - 12
src/components/Configurable/detail/MiniBoard.vue

@@ -131,13 +131,14 @@
   }
   .mini-board_A {
     width: 220px;
-    height: 50px;
+    height: 44px;
     display: flex;
     justify-content: space-between;
     align-items: center;
     padding: 0 20px 0 20px;
     background-image: var(--image-board-bg-A-1);
     background-size: 100% 100%;
+    margin-top: 5px;
   }
   .mini-board_A:nth-child(2) {
     background-image: var(--image-board-bg-A-2);
@@ -169,11 +170,11 @@
     font-family: 'Microsoft YaHei';
   }
   .mini-board_B {
-    width: 100px;
-    height: 115px;
+    width: 98px;
+    height: 98px;
     background-image: var(--image-board-bg-B);
     background-size: 100% 100%;
-    padding-top: 42px;
+    padding-top: 30px;
   }
 
   .mini-board__label_B {
@@ -186,7 +187,7 @@
     font-size: 26px;
     font-weight: bold;
     font-family: 'Microsoft YaHei';
-    padding-bottom: 17px;
+    padding-bottom: 15px;
   }
   /* 1. 第一个容器:label和value为蓝色 */
   .mini-board_B:nth-child(1) .mini-board__label_B,
@@ -212,11 +213,11 @@
     color: #d6666c;
   }
   .mini-board_C {
-    width: 100px;
-    height: 115px;
+    width: 98px;
+    height: 98px;
     background-image: var(--image-board-bg-B);
     background-size: 100% 100%;
-    padding-top: 42px;
+    padding-top: 30px;
   }
 
   .mini-board__label_C {
@@ -229,7 +230,7 @@
     font-size: 26px;
     font-weight: bold;
     font-family: 'Microsoft YaHei';
-    padding-bottom: 17px;
+    padding-bottom: 15px;
   }
   /* 1. 第一个容器:label和value为蓝色 */
   .mini-board_C:nth-child(1) .mini-board__label_C,
@@ -256,10 +257,10 @@
   }
   .mini-board_D {
     width: 96px;
-    height: 81px;
+    height: 65px;
     background-image: var(--image-board-bg-D);
     background-size: 100% 100%;
-    padding-top: 12px;
+    padding-top: 6px;
   }
 
   .mini-board__label_D {
@@ -271,7 +272,6 @@
     color: #2b6ff0;
     font-size: 22px;
     font-family: 'Microsoft YaHei';
-    padding-bottom: 8px;
   }
 
   .mini-board_E {

+ 175 - 0
src/components/Container/src/Adaptive.vue

@@ -0,0 +1,175 @@
+<template>
+  <div id="adaptive-container" :ref="refName">
+    <template v-if="ready">
+      <slot></slot>
+    </template>
+  </div>
+</template>
+
+<script>
+  import { ref, onMounted, onUnmounted, nextTick, defineComponent } from 'vue';
+  import { debounce, setRem } from '/@/utils/index';
+  import { useAppStore } from '/@/store/modules/app';
+  import { getActions } from '/@/qiankun/state';
+
+  export default defineComponent({
+    name: 'AdaptiveContainer',
+    props: {
+      options: Object,
+    },
+    setup(ctx) {
+      const appStore = useAppStore();
+
+      const refName = 'AdaptiveContainer'; //AdaptiveContainer
+      // 屏幕宽度
+      const width = ref(0);
+      // 屏幕高度
+      const height = ref(0);
+      // 原始屏幕宽度
+      const originalWidth = ref(0);
+      // 原始屏幕高度
+      const originalHeight = ref(0);
+      // 控制显示
+      const ready = ref(false);
+      /*
+       * dom:well-container的dom
+       * observer: window.MutationObserver(Bom实例)监听dom改变
+       */
+      let dom, observer;
+      const actions = getActions();
+
+      //设置初始值
+      const initSize = () => {
+        return new Promise((resolve) => {
+          nextTick(() => {
+            dom = document.getElementById('adaptive-container');
+            // 获取大屏的传入尺寸
+            if (ctx.options && ctx.options.width && ctx.options.height) {
+              //传入宽高
+              width.value = ctx.options.width;
+              height.value = ctx.options.height;
+            } else {
+              //可见宽高
+              width.value = dom.clientWidth;
+              height.value = dom.clientHeight;
+            }
+            // 获取画布尺寸
+            if (!originalWidth.value || !originalHeight.value) {
+              //屏幕分辨率宽高
+              originalWidth.value = window.screen.width;
+              originalHeight.value = window.screen.height;
+            }
+            resolve();
+          });
+        });
+      };
+
+      const updateSize = () => {
+        if (width.value && height.value) {
+          dom.style.width = `${width.value}px`;
+          dom.style.height = `${height.value}px`;
+        } else {
+          dom.style.width = `${originalWidth.value}px`;
+          dom.style.height = `${originalHeight.value}px`;
+        }
+      };
+
+      const updateScale = () => {
+        // debugger
+        // 获取真实的视口尺寸
+        const currentWidth = document.body.clientWidth;
+        const currentHeight = document.body.clientHeight;
+        // 获取大屏最终的宽高
+        const realWidth = width.value || originalWidth.value;
+        const realHeight = height.value || originalHeight.value;
+        // console.log(currentWidth, currentHeight)
+        // 缩放比例  = 分辨率宽高 / 传入宽高(可视宽高)
+        const widthScale = currentWidth / realWidth;
+        const heightScale = currentHeight / realHeight;
+        appStore.setWidthScale(widthScale);
+        appStore.setHeightScale(heightScale);
+        actions.setGlobalState({ widthScale, heightScale });
+
+        //如果dom存在,就按照比例缩放
+        dom && (dom.style.transform = `scale(${widthScale}, ${heightScale})`);
+      };
+      const cssSize = () => {
+        const currentWidth = document.body.clientWidth;
+        const realWidth = width.value || originalWidth.value;
+        const whdef = 100 / currentWidth; // 表示1920的设计图,使用100PX的默认值
+        const wW = currentWidth / realWidth; // 当前窗口的宽度
+        const rem = wW * whdef; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应FONT-SIZE值
+        // document.documentElement.style.fontSize = rem + 'px';
+        document.documentElement.style.fontSize = rem + 'px';
+        window.addEventListener('resize', function () {
+          const whdef = 100 / currentWidth; // 表示1920的设计图,使用100PX的默认值
+          const wW = window.innerWidth; // 当前窗口的宽度
+          const rem = wW * whdef; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应FONT-SIZE值
+          document.documentElement.style.fontSize = rem + 'px';
+        });
+      };
+
+      //重置缩放比例
+      const onResize = async () => {
+        // debugger;
+        await initSize();
+        updateScale();
+        setRem();
+        cssSize();
+      };
+
+      const initMutationObserver = () => {
+        //监听元素属性变化
+        const MutationObserver = window.MutationObserver;
+        //如果变化,就用onResize重置屏幕所缩放比例
+        observer = new MutationObserver(onResize);
+        observer.observe(dom, {
+          attributes: true,
+          // attributeFilter: ['style'],
+          attributeOldValue: true,
+        });
+      };
+      //移除监听属性
+      const removeMutationObserver = () => {
+        if (observer) {
+          observer.disconnect();
+          observer.takeRecords();
+          observer = null;
+        }
+      };
+      //
+      onMounted(async () => {
+        ready.value = false;
+        await initSize();
+        updateSize();
+        updateScale();
+        setRem();
+        cssSize();
+        window.addEventListener('resize', debounce(100, onResize));
+        // initMutationObserver();
+        ready.value = true;
+      });
+
+      onUnmounted(() => {
+        window.removeEventListener('resize', onResize);
+        // removeMutationObserver();
+      });
+
+      return {
+        refName,
+        ready,
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  #adaptive-container {
+    position: relative;
+    top: 0;
+    left: 0;
+    overflow: hidden;
+    transform-origin: left top;
+    z-index: 0;
+  }
+</style>

+ 1 - 0
src/components/Table/src/BasicTable.vue

@@ -31,6 +31,7 @@
           :rowClassName="getRowClassName"
           @resize-column="handleResizeColumn"
           @change="handleTableChange"
+          style="width: 100%; height: 80% !important; overflow: auto;"
         >
           <!-- antd的原生插槽直接传递 -->
           <template #[item]="data" v-for="item in slotNamesGroup.native" :key="item">

+ 40 - 0
src/qiankun/apps.ts

@@ -0,0 +1,40 @@
+import { prefetchApps, AppMetadata } from 'qiankun';
+/**
+ *微应用apps
+ * @name: 微应用名称 - 具有唯一性
+ * @entry: 微应用入口.必选 - 通过该地址加载微应用,
+ * @container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
+ * @activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
+ */
+//子应用列表
+const _apps: AppMetadata[] = [];
+for (const key in import.meta.env) {
+  if (key === 'VITE_APP_SUB_APP') {
+    const appList = JSON.parse(import.meta.env[key].replace(/'/g, '"'));
+
+    appList.forEach((app) => {
+      let utlStr;
+      if (import.meta.env.PROD) {
+        // 多端口请求
+        if (VUE_APP_URL.baseUrl.split(':').length > 1) {
+          utlStr = VUE_APP_URL.baseUrl.split(':')[1] + app[1];
+        } else {
+          utlStr = app[1];
+        }
+      } else {
+        utlStr = app[1];
+      }
+      const obj = {
+        name: app[0],
+        entry: utlStr,
+        container: `#${app[0]}`,
+        activeRule: app[0],
+      };
+      // debugger;
+
+      _apps.push(obj);
+    });
+  }
+  prefetchApps([..._apps]);
+}
+export const apps = _apps;

+ 51 - 0
src/qiankun/index.ts

@@ -0,0 +1,51 @@
+/**
+ * qiankun配置
+ */
+import { loadMicroApp, start } from 'qiankun';
+import { apps } from './apps';
+import { getProps } from './state';
+
+const activeApps = {};
+/**
+ * 重构apps
+ */
+function filterApps() {
+  apps.forEach((item) => {
+    //主应用需要传递给微应用的数据。
+    item['props'] = getProps();
+
+    //微应用触发的路由规则
+    // @ts-ignore
+    item.activeRule = item.activeRule.startsWith('/') ? item.activeRule : `/${item.activeRule}`;
+  });
+  return apps;
+}
+
+const mountMicroApp = (name, isReFresh = false) => {
+  const microApps = filterApps();
+  const app = microApps.find((item) => item.name === name);
+  if (app) {
+    const instance = activeApps[app.name];
+    if (instance && instance.getStatus() === 'NOT_MOUNTED' && !isReFresh) {
+      instance.mount(app);
+    } else {
+      delete activeApps[app.name];
+      activeApps[app.name] = loadMicroApp(app, { autoStart: false, sandbox: { strictStyleIsolation: false } }); // 手动加载子应用'
+    }
+  }
+};
+
+// 卸载app的方法
+const unmountMicroApps = async (multipleApp) => {
+  if (JSON.stringify(activeApps) !== '{}' && multipleApp.some) {
+    for (const key in activeApps) {
+      multipleApp.filter(async (name) => {
+        if (name.includes(key)) {
+          await activeApps[key].unmount();
+        }
+      });
+    }
+  }
+};
+
+export { mountMicroApp, unmountMicroApps, activeApps };

+ 58 - 0
src/qiankun/state.ts

@@ -0,0 +1,58 @@
+/**
+ *公共数据
+ */
+import { initGlobalState } from 'qiankun';
+import { store } from '/@/store';
+import { router } from '/@/router';
+import { getToken } from '/@/utils/auth';
+
+let actions;
+
+//定义传入子应用的数据
+export function getProps() {
+  return {
+    data: {
+      publicPath: '/',
+      token: getToken(),
+      store: store,
+      router,
+      isMounted: false,
+    },
+    actions: getActions(),
+  };
+}
+
+/**
+ * 定义全局状态,并返回通信方法,在主应用使用,微应用通过 props 获取通信方法。
+ * @param state 主应用穿的公共数据
+ */
+export function initGlState(
+  info: any = { token: '', userInfo: {}, isMounted: false, locationObj: null, locationId: '', pageObj: null, widthScale: 1, heightScale: 1 }
+) {
+  if (actions) return;
+  // 初始化state
+  actions = initGlobalState(info);
+  // 设置新的值
+  actions.setGlobalState({
+    token: getToken(),
+    isMounted: false,
+    pageObj: {},
+    widthScale: 1,
+    heightScale: 1,
+    url: {},
+  });
+  // 注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
+  actions.onGlobalStateChange((newState, prev) => {
+    // state: 变更后的状态; prev 变更前的状态
+    console.info('newState', newState);
+    console.info('prev', prev);
+    for (const key in newState) {
+      console.info('onGlobalStateChange', key);
+    }
+  });
+}
+
+export function getActions() {
+  if (!actions) initGlState();
+  return actions;
+}

+ 18 - 0
src/store/modules/app.ts

@@ -26,6 +26,9 @@ interface AppState {
   messageHrefParams: any,
   // 应用参数
   mainAppProps: MainAppProps,
+
+  widthScale: number;
+  heightScale: number;
 }
 let timeId: TimeoutHandle;
 export const useAppStore = defineStore({
@@ -37,6 +40,8 @@ export const useAppStore = defineStore({
     beforeMiniInfo: {},
     messageHrefParams: {},
     mainAppProps: {},
+    widthScale: 1,
+    heightScale: 1,
   }),
   getters: {
     getPageLoading(): boolean {
@@ -107,6 +112,12 @@ export const useAppStore = defineStore({
       }
       return !!this.mainAppProps.hideMultiTabs;
     },
+    getWidthScale(): number {
+      return this.widthScale;
+    },
+    getHeightScale(): number {
+      return this.heightScale;
+    },
   },
   actions: {
     setPageLoading(loading: boolean): void {
@@ -156,6 +167,13 @@ export const useAppStore = defineStore({
       this.mainAppProps.hideMultiTabs = args.hideMultiTabs ?? false;
     },
 
+    setWidthScale(scale: number) {
+      this.widthScale = scale;
+    },
+    setHeightScale(scale: number) {
+      this.heightScale = scale;
+    },
+
   },
 });
 

+ 21 - 0
src/utils/index.ts

@@ -669,3 +669,24 @@ export const split = (str) => {
   }
   return str;
 };
+
+// 防抖截流
+export function debounce(delay, callback) {
+  let task;
+  return function () {
+    clearTimeout(task);
+    task = setTimeout(() => {
+      callback.apply(this, arguments);
+    }, delay);
+  };
+}
+
+export function setRem() {
+  // 默认使用100px作为基准大小
+  const baseSize = 100;
+  const baseVal = baseSize / 1920;
+  const vW = window.innerWidth; // 当前窗口的宽度
+  const rem = vW * baseVal; // 以默认比例值乘以当前窗口宽度,得到该宽度下的相应font-size值
+  window.$size = rem / 100;
+  document.documentElement.style.fontSize = rem + 'px';
+}

+ 16 - 16
src/views/dashboard/SealedGoaf/configurable.data.sealedGoaf.ts

@@ -60,7 +60,7 @@ export const testConfigSealedGoaf: Config[] = [
       preset: [],
     },
     showStyle: {
-      size: 'width:440px;height:200px;',
+      size: 'width:440px;height:185px;',
       version: '原版',
       position: 'top:20px;left:10px;',
     },
@@ -116,9 +116,9 @@ export const testConfigSealedGoaf: Config[] = [
       preset: [],
     },
     showStyle: {
-      size: 'width:440px;height:150px;',
+      size: 'width:440px;height:140px;',
       version: '原版',
-      position: 'top:220px;left:10px;',
+      position: 'top:210px;left:10px;',
     },
   },
   // 3. 煤层自燃倾向性
@@ -171,9 +171,9 @@ export const testConfigSealedGoaf: Config[] = [
       preset: [],
     },
     showStyle: {
-      size: 'width:440px;height:200px;',
+      size: 'width:440px;height:180px;',
       version: '原版',
-      position: 'top:370px;left:10px;',
+      position: 'top:340px;left:10px;',
     },
   },
   // 4. 联网状态
@@ -204,12 +204,12 @@ export const testConfigSealedGoaf: Config[] = [
         items: [
           {
             name: 'board',
-            basis: '50%',
+            basis: '40%',
             overflow: true,
           },
           {
             name: 'table',
-            basis: '50%',
+            basis: '60%',
             overflow: true,
           },
         ],
@@ -345,9 +345,9 @@ export const testConfigSealedGoaf: Config[] = [
       },
     },
     showStyle: {
-      size: 'width:440px;height:280px;',
+      size: 'width:440px;height:300px;',
       version: '原版',
-      position: 'top:570px;left:10px;',
+      position: 'top:520px;left:10px;',
     },
   },
   // 5. 密闭情况总览
@@ -516,7 +516,7 @@ export const testConfigSealedGoaf: Config[] = [
     showStyle: {
       size: 'width:440px;height:330px;',
       version: '原版',
-      position: 'top:20px;right:10px;',
+      position: 'top:-20px;right:10px;',
     },
   },
   // 6. 采空区密闭预警分析
@@ -547,12 +547,12 @@ export const testConfigSealedGoaf: Config[] = [
         items: [
           {
             name: 'board',
-            basis: '50%',
+            basis: '40%',
             overflow: true,
           },
           {
             name: 'table',
-            basis: '50%',
+            basis: '60%',
             overflow: true,
           },
         ],
@@ -681,9 +681,9 @@ export const testConfigSealedGoaf: Config[] = [
       // },
     },
     showStyle: {
-      size: 'width:440px;height:280px;',
+      size: 'width:440px;height:300px;',
       version: '原版',
-      position: 'top:350px;right:10px;',
+      position: 'top:310px;right:10px;',
     },
   },
   // 7. 当日报警情况统计
@@ -768,9 +768,9 @@ export const testConfigSealedGoaf: Config[] = [
       preset: [],
     },
     showStyle: {
-      size: 'width:440px;height:230px;',
+      size: 'width:440px;height:210px;',
       version: '原版',
-      position: 'top:630px;right:10px;',
+      position: 'top:620px;right:10px;',
     },
   },
 ];

+ 4 - 0
src/views/dashboard/SealedGoaf/index.vue

@@ -81,4 +81,8 @@
     // 允许点击穿透以支持下面的地图进行交互
     pointer-events: none;
   }
+  :deep(.ant-select-selection-item){
+    display: flex;
+    align-items: center;
+  }
 </style>

+ 1 - 1
src/views/dashboard/basicInfo/minesInfo/index.vue

@@ -2,7 +2,7 @@
 <template>
   <BasicTable @register="registerTable">
     <template #action="{ record }">
-      <button @click="handleGoToPage(record, '/basicInfo/accessStatistics')" class="action-btn">
+      <button @click="handleGoToPage(record, `/sealed/${record.areaId}`)" class="action-btn">
         <SvgIcon name="data" />
       </button>
       <button @click="handleGoToPage(record, '/basicInfo/accessStatistics')" class="action-btn">