Pārlūkot izejas kodu

Merge branch 'master' of http://39.97.59.228:8013/hrx/goaf-monitoring-system

bobo04052021@163.com 3 mēneši atpakaļ
vecāks
revīzija
79a9201c58
30 mainītis faili ar 2055 papildinājumiem un 1945 dzēšanām
  1. 2 2
      README.md
  2. 13 14
      build/vite/plugin/pwa.ts
  3. 1 1
      index.html
  4. 67 67
      src/components/Application/src/AppLogo.vue
  5. 83 99
      src/components/Form/src/jeecg/components/MineCascader/MineCascader.vue
  6. 53 50
      src/layouts/default/feature/SystemSelect.vue
  7. 0 1
      src/layouts/default/header/index.less
  8. 77 77
      src/layouts/default/index.vue
  9. 190 188
      src/layouts/default/menu/index.vue
  10. 77 70
      src/layouts/default/plain.vue
  11. 134 135
      src/layouts/default/sider/LayoutSider.vue
  12. 23 6
      src/store/modules/mine.ts
  13. 24 18
      src/views/analysis/warningAnalysis/airLeakStatus/index.vue
  14. 29 30
      src/views/analysis/warningAnalysis/autoFireAnalysis/index.vue
  15. 267 267
      src/views/analysis/warningAnalysis/connectAnalysis/index.vue
  16. 27 30
      src/views/analysis/warningAnalysis/fireAreaJudgeAnalysis/index.vue
  17. 25 28
      src/views/analysis/warningAnalysis/overlimitAlarm/index.vue
  18. 13 38
      src/views/analysis/warningAnalysis/pressureDiffAnalysis/index.vue
  19. 25 28
      src/views/analysis/warningAnalysis/sealRiskJudgeAnalysis/index.vue
  20. 66 68
      src/views/dashboard/SealedGoaf/index.vue
  21. 190 86
      src/views/dashboard/basicInfo/dataQuality/components/DataQualityModal.vue
  22. 30 2
      src/views/dashboard/basicInfo/dataQuality/dataQuality.data.ts
  23. 1 1
      src/views/demo/charts/china.json
  24. 1 1
      src/views/demo/charts/data.ts
  25. 68 77
      src/views/demo/comp/qrcode/index.vue
  26. 193 189
      src/views/sys/login/TokenLoginPage.vue
  27. 1 1
      src/views/system/loginmini/MiniCodelogin.vue
  28. 186 183
      src/views/system/loginmini/MiniForgotpad.vue
  29. 1 1
      src/views/system/loginmini/MiniLogin.vue
  30. 188 187
      src/views/system/loginmini/MiniRegister.vue

+ 2 - 2
README.md

@@ -1,6 +1,6 @@
-## 省局密闭采空区监测系统
+## 密闭采空区监测系统
 
-省局密闭采空区监测系统前端代码仓库
+密闭采空区监测系统前端代码仓库
 
 ### 前言
 

+ 13 - 14
build/vite/plugin/pwa.ts

@@ -20,22 +20,22 @@ export function configPwaPlugin(isBuild: boolean): PluginOption | PluginOption[]
       short_name: 'Jeecg',
       theme_color: '#ffffff',
       icons: [
-        {
-          src: '/logo.png',
-          sizes: '192x192',
-          type: 'image/png',
-        },
-        {
-          src: '/logo.png',
-          sizes: '512x512',
-          type: 'image/png',
-        },
+        // {
+        //   src: '/logo.png',
+        //   sizes: '192x192',
+        //   type: 'image/png',
+        // },
+        // {
+        //   src: '/logo.png',
+        //   sizes: '512x512',
+        //   type: 'image/png',
+        // },
       ],
     },
     workbox: {
       maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10MB
       cleanupOutdatedCaches: true,
-      
+
       // 预缓存:只缓存关键资源,不预缓存路由组件 CSS/JS(避免登录页加载全部资源)
       globPatterns: [
         'index.html', // 必须预缓存(避免 non-precached-url 错误)
@@ -46,10 +46,10 @@ export function configPwaPlugin(isBuild: boolean): PluginOption | PluginOption[]
         'js/index-*.js',
         'js/*-vendor-*.js',
       ],
-      
+
       // 不使用导航回退功能
       navigateFallback: undefined,
-      
+
       // 运行时缓存:按需加载的资源
       runtimeCaching: [
         {
@@ -136,4 +136,3 @@ export function configPwaPlugin(isBuild: boolean): PluginOption | PluginOption[]
 
   return VitePWA(pwaOptions);
 }
-

+ 1 - 1
index.html

@@ -7,7 +7,7 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
 
     <title><%= title %></title>
-    <link rel="icon" href="<%= basePublicPath %>/logo.png" />
+    <!-- <link rel="icon" href="<%= basePublicPath %>/logo.png" /> -->
     <!-- 全局配置 -->
     <script>
       window._CONFIG = {};

+ 67 - 67
src/components/Application/src/AppLogo.vue

@@ -4,91 +4,91 @@
 -->
 <template>
   <div class="anticon" :class="getAppLogoClass" @click="goHome">
-    <img src="../../../assets/images/logo.png" />
+    <!-- <img src="../../../assets/images/logo.png" /> -->
     <div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
       {{ title }}
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-  import { computed, unref } from 'vue';
-  import { useGlobSetting } from '/@/hooks/setting';
-  import { useGo } from '/@/hooks/web/usePage';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { PageEnum } from '/@/enums/pageEnum';
-  import { useUserStore } from '/@/store/modules/user';
+import { computed, unref } from 'vue';
+import { useGlobSetting } from '/@/hooks/setting';
+import { useGo } from '/@/hooks/web/usePage';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useDesign } from '/@/hooks/web/useDesign';
+import { PageEnum } from '/@/enums/pageEnum';
+import { useUserStore } from '/@/store/modules/user';
 
-  const props = defineProps({
-    /**
-     * The theme of the current parent component
-     */
-    theme: { type: String, validator: (v: string) => ['light', 'dark'].includes(v) },
-    /**
-     * Whether to show title
-     */
-    showTitle: { type: Boolean, default: true },
-    /**
-     * The title is also displayed when the menu is collapsed
-     */
-    alwaysShowTitle: { type: Boolean },
-  });
+const props = defineProps({
+  /**
+   * The theme of the current parent component
+   */
+  theme: { type: String, validator: (v: string) => ['light', 'dark'].includes(v) },
+  /**
+   * Whether to show title
+   */
+  showTitle: { type: Boolean, default: true },
+  /**
+   * The title is also displayed when the menu is collapsed
+   */
+  alwaysShowTitle: { type: Boolean },
+});
 
-  const { prefixCls } = useDesign('app-logo');
-  const { getCollapsedShowTitle } = useMenuSetting();
-  const userStore = useUserStore();
-  const { title } = useGlobSetting();
+const { prefixCls } = useDesign('app-logo');
+const { getCollapsedShowTitle } = useMenuSetting();
+const userStore = useUserStore();
+const { title } = useGlobSetting();
 
-  const go = useGo();
+const go = useGo();
 
-  const getAppLogoClass = computed(() => [prefixCls, props.theme, { 'collapsed-show-title': unref(getCollapsedShowTitle) }]);
+const getAppLogoClass = computed(() => [prefixCls, props.theme, { 'collapsed-show-title': unref(getCollapsedShowTitle) }]);
 
-  const getTitleClass = computed(() => [
-    `${prefixCls}__title`,
-    {
-      'xs:opacity-0': !props.alwaysShowTitle,
-    },
-  ]);
+const getTitleClass = computed(() => [
+  `${prefixCls}__title`,
+  {
+    'xs:opacity-0': !props.alwaysShowTitle,
+  },
+]);
 
-  function goHome() {
-    go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
-  }
+function goHome() {
+  go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
+}
 </script>
 <style lang="less" scoped>
-  @prefix-cls: ~'@{namespace}-app-logo';
+@prefix-cls: ~'@{namespace}-app-logo';
 
-  .@{prefix-cls} {
-    display: flex;
-    align-items: center;
-    padding-left: 7px;
-    cursor: pointer;
-    transition: all 0.2s ease;
-    //左侧菜单模式和左侧菜单混合模式加渐变背景色
-    // &.jeecg-layout-mix-sider-logo,
-    // &.jeecg-layout-menu-logo {
-    //   background: @sider-logo-bg-color;
-    // }
-    // &.light {
-    //   border-bottom: 1px solid @border-color-base;
-    // }
+.@{prefix-cls} {
+  display: flex;
+  align-items: center;
+  padding-left: 7px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  //左侧菜单模式和左侧菜单混合模式加渐变背景色
+  // &.jeecg-layout-mix-sider-logo,
+  // &.jeecg-layout-menu-logo {
+  //   background: @sider-logo-bg-color;
+  // }
+  // &.light {
+  //   border-bottom: 1px solid @border-color-base;
+  // }
 
-    &.collapsed-show-title {
-      padding-left: 20px;
-    }
+  &.collapsed-show-title {
+    padding-left: 20px;
+  }
 
-    &.light &__title {
-      color: @primary-color;
-    }
+  &.light &__title {
+    color: @primary-color;
+  }
 
-    &.dark &__title {
-      color: @white;
-    }
+  &.dark &__title {
+    color: @white;
+  }
 
-    &__title {
-      font-size: 18px;
-      font-weight: 600;
-      transition: all 0.5s;
-      line-height: normal;
-    }
+  &__title {
+    font-size: 18px;
+    font-weight: 600;
+    transition: all 0.5s;
+    line-height: normal;
   }
+}
 </style>

+ 83 - 99
src/components/Form/src/jeecg/components/MineCascader/MineCascader.vue

@@ -21,116 +21,100 @@
 </template>
 
 <script lang="ts">
-import { last } from 'lodash';
-import { defineComponent, onUnmounted, h, ref, computed, watchEffect } from 'vue';
-// import { useMessage } from '/@/hooks/web/useMessage';
-import { propTypes } from '/@/utils/propTypes';
-import { useMineDepartmentStore } from '/@/store/modules/mine';
-import { storeToRefs } from 'pinia';
-import { Cascader } from 'ant-design-vue';
+  import { last } from 'lodash';
+  import { defineComponent, ref, watch } from 'vue';
+  // import { useMessage } from '/@/hooks/web/useMessage';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useMineDepartmentStore } from '/@/store/modules/mine';
+  import { Cascader } from 'ant-design-vue';
 
-export default defineComponent({
-  name: 'MineCascader',
-  components: { Cascader },
-  props: {
-    value: propTypes.string.def(''),
-    placeholder: propTypes.string.def('全部'),
-    /** 根节点ID,如果传入,组件将过滤该节点下的节点 */
-    rootId: propTypes.string,
-    showSearch: propTypes.bool.def(true),
-    allowClear: propTypes.bool.def(true),
-    changeOnSelect: propTypes.bool.def(true),
-    // clearOnDestroy: propTypes.bool.def(false),
-    mineCode: propTypes.string,//URL传入的矿井ID
-    showDefaultValue: propTypes.bool.def(false) //是否需要显示默认值
-  },
-  emits: ['change', 'update:value'],
-  setup(props, { emit }) {
-    // const { createMessage } = useMessage();
-    const mineStore = useMineDepartmentStore();
-    const { getDepartTree, getMineCode, } = storeToRefs(mineStore);
+  /**
+   * 矿区级联选择器,该组件会根据配置从store中获取初始数据
+   *
+   * 组件数据流梳理:初始化数据及选项 -> 更新数据 -> 同步STORE -> 计算组件内依赖
+   *
+   * 本组件在初始化后不再监听STORE中的数据变化,因为这样可能导致用户通过Tabs切换时出现数据混乱
+   */
+  export default defineComponent({
+    name: 'MineCascader',
+    components: { Cascader },
+    props: {
+      value: propTypes.string.def(''),
+      placeholder: propTypes.string.def('全部'),
+      /** 根节点ID,如果传入,组件将过滤该节点下的节点 */
+      rootId: propTypes.string,
+      /** 是否从已存储的信息中初始化组件值 */
+      initFromStore: propTypes.bool.def(true),
+      /** 是否从将值同步至STORE */
+      syncToStore: propTypes.bool.def(true),
+      showSearch: propTypes.bool.def(true),
+      allowClear: propTypes.bool.def(true),
+      changeOnSelect: propTypes.bool.def(true),
+      // clearOnDestroy: propTypes.bool.def(false),
+    },
+    emits: ['change', 'update:value'],
+    setup(props, { emit }) {
+      // const { createMessage } = useMessage();
+      const mineStore = useMineDepartmentStore();
+      const innerValue = ref<string[]>([]);
+      const options = ref(mineStore.getDepartTree);
 
-    // if (props.clearOnDestroy) {
-    //   const raw = getDepartId.value;
-    //   onUnmounted(() => {
-    //     mineStore.setDepartById(raw);
-    //   });
-    // }
+      // if (props.clearOnDestroy) {
+      //   const raw = getDepartId.value;
+      //   onUnmounted(() => {
+      //     mineStore.setDepartById(raw);
+      //   });
+      // }
 
-    if (props.rootId) {
-      mineStore.clearDepart();
-      mineStore.filterDepartTree((e) => e.parentId === props.rootId);
+      if (props.rootId) {
+        options.value = mineStore.filterDepartTree((e) => e.parentId === props.rootId);
+      }
 
-      onUnmounted(() => {
-        mineStore.restoreDepartTree();
-      });
-    }
+      /**
+       * change事件
+       * @param e
+       */
+      function handleChange(value: any[] = []) {
+        // const dep = mineStore.findDepartById(id, options.value);
+        const val = last(value) ? last(value) : mineStore.calcMineCodeByDepart(options.value);
 
-    // const shownText = computed(() => getDepart.value.departName || '-');
-    const innerValue = ref<string[]>([]);
-    // const innerValue = computed(() => getDepartPath.value.map((e) => e.id));
-    const options = getDepartTree;
+        emit('update:value', val);
+        emit('change', val);
+      }
 
-    //默认显示内容
-    function findNodeWithPath(tree, id, path = []) {
-      for (const node of tree) {
-        const currentPath: any = [...path, node];
-        if (node.id == id) {
-          return currentPath; // 返回包含目标节点及其祖先的数组
+      function handleWatch(id: string = '') {
+        // rootId提供了选项过滤功能,任何传入的值都应该检查
+        if (!mineStore.findDepartById(id, options.value)) {
+          console.warn('The value of MineCascader is not included in MineOptions');
+          innerValue.value = [];
+          return;
         }
-        if (node.childDepart && node.childDepart.length > 0) {
-          const result = findNodeWithPath(node.childDepart, id, currentPath)
-          if (result) {
-            return result.map(el => el.id);
-          }
+        if (props.syncToStore) {
+          mineStore.setDepartById(id);
         }
+
+        const path = mineStore.calcDepartPathById(id, options.value, (e) => e.id);
+        innerValue.value = path;
       }
-      return null;
-    }
-    const defaultValue = computed(() => {
-      if (props.mineCode) {
-        return findNodeWithPath(getDepartTree.value, props.mineCode)
+
+      // 如果从STORE里初始化数据需要触发一次watcher以初始化组件状态
+      if (props.initFromStore) {
+        handleWatch(mineStore.getDepartId);
+        // 为了让使用该组件的各个页面能够第一时间使用到该组件传递的值,手动触发一次emit
+        handleChange([mineStore.getDepartId]);
       } else {
-        const levelData = getDepartTree.value.filter(v => v.childDepart.length)
-        const firstLevel = levelData[0]['id']
-        const secondLevel = levelData[0]['childDepart'][0]['id']
-        const thirdLevel = levelData[0]['childDepart'][0]['childDepart'][0]['id']
-        return [firstLevel, secondLevel, thirdLevel]
+        handleWatch(props.value);
+        // 为了让使用该组件的各个页面能够第一时间使用到该组件传递的值,手动触发一次emit
+        handleChange([props.value]);
       }
-    });
-
-
-    /**
-     * change事件
-     * @param e
-     */
-    function handleChange(value: any[]) {
-      innerValue.value = value;
-      mineStore.setDepartById(last(value));
-      const val = getMineCode.value;
-      emit('update:value', val);
-      emit('change', val);
-    }
-
-
-
-    if (props.showDefaultValue) {
-      handleChange(defaultValue.value);
-    }  else {
-      handleChange([]);
-    }
 
-    onUnmounted(() => {
-      mineStore.clearDepart();
-    });
+      watch(() => props.value, handleWatch);
 
-    return {
-      innerValue,
-      options,
-      getDepartTree,
-      handleChange,
-      h,
-    };
-  },
-});
+      return {
+        innerValue,
+        options,
+        handleChange,
+      };
+    },
+  });
 </script>

+ 53 - 50
src/layouts/default/feature/SystemSelect.vue

@@ -3,66 +3,69 @@
 </template>
 
 <script lang="ts" setup>
-  // import { Button, Dropdown, Menu, MenuItem } from 'ant-design-vue';
-  // import { DownOutlined } from '@ant-design/icons-vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { onMounted, ref } from 'vue';
-  import { PageEnum } from '/@/enums/pageEnum';
-  import { useRouter } from 'vue-router';
-  import { useI18n } from '/@/hooks/web/useI18n';
+// import { Button, Dropdown, Menu, MenuItem } from 'ant-design-vue';
+// import { DownOutlined } from '@ant-design/icons-vue';
+import { useDesign } from '/@/hooks/web/useDesign';
+import { onMounted, ref } from 'vue';
+import { PageEnum } from '/@/enums/pageEnum';
+import { useRouter } from 'vue-router';
+import { useI18n } from '/@/hooks/web/useI18n';
 
-  const { prefixCls } = useDesign('system-select');
-  const router = useRouter();
-  const { t } = useI18n();
-  const text = ref('');
+const { prefixCls } = useDesign('system-select');
+const router = useRouter();
+const { t } = useI18n();
+const text = ref('');
 
-  function handleMenuClick(value) {
-    router.push({ path: value });
-  }
+function handleMenuClick(value) {
+  router.push({ path: value });
+}
 
-  // const OPTIONS = ref({
-  //   [PageEnum.BASE_HOME]: t('routes.basic.baseHome'),
-  //   [PageEnum.SECONDARY_HOME]: t('routes.basic.secondaryHome'),
-  // });
-  const OPTIONS = ref([
-    {
-      value: PageEnum.BASE_HOME,
-      label: t('routes.basic.baseHome'),
-    },
-    {
-      value: PageEnum.SECONDARY_HOME,
-      label: t('routes.basic.secondaryHome'),
-    },
-  ]);
+// const OPTIONS = ref({
+//   [PageEnum.BASE_HOME]: t('routes.basic.baseHome'),
+//   [PageEnum.SECONDARY_HOME]: t('routes.basic.secondaryHome'),
+// });
+const OPTIONS = ref([
+  {
+    value: PageEnum.BASE_HOME,
+    label: t('routes.basic.baseHome'),
+  },
+  {
+    value: PageEnum.SECONDARY_HOME,
+    label: t('routes.basic.secondaryHome'),
+  },
+]);
 
-  onMounted(() => {
-    // 由于BASE_HOME仅代表了一个页面,其他页面都隶属于SECONDARY_HOME,所以简单处理
-    const isBaseHome = router.currentRoute.value.path === PageEnum.BASE_HOME;
-    text.value = isBaseHome ? t('routes.basic.baseHome') : t('routes.basic.secondaryHome');
-  });
+onMounted(() => {
+  // 由于BASE_HOME仅代表了一个页面,其他页面都隶属于SECONDARY_HOME,所以简单处理
+  const isBaseHome = router.currentRoute.value.path === PageEnum.BASE_HOME;
+  text.value = isBaseHome ? t('routes.basic.baseHome') : t('routes.basic.secondaryHome');
+});
 
-  defineExpose({ OPTIONS, prefixCls, handleMenuClick });
+defineExpose({ OPTIONS, prefixCls, handleMenuClick });
 </script>
 <style lang="less" scoped>
-  @prefix-cls: ~'@{namespace}-system-select';
+@prefix-cls: ~'@{namespace}-system-select';
 
-  .@{prefix-cls} {
-    margin-left: 10px;
-    margin-top: 10px;
-    width: 210px;
-    position: relative;
-    z-index: @layout-basic-z-index;
-  }
+.@{prefix-cls} {
+  max-width: 210px;
+  width: 100%;
+  min-width: 10px;
+  margin-bottom: 10px;
+  position: relative;
+  z-index: @layout-basic-z-index;
+}
 
-  .@{prefix-cls}.ant-select {
-    :deep(.ant-select-selector) {
-      background-color: @primary-color;
-      .ant-select-selection-item {
-        color: @white;
-      }
-    }
-    :deep(.ant-select-arrow) {
+.@{prefix-cls}.ant-select {
+  :deep(.ant-select-selector) {
+    background-color: @primary-color;
+
+    .ant-select-selection-item {
       color: @white;
     }
   }
+
+  :deep(.ant-select-arrow) {
+    color: @white;
+  }
+}
 </style>

+ 0 - 1
src/layouts/default/header/index.less

@@ -68,7 +68,6 @@
     display: flex;
     height: 100%;
     align-items: center;
-
     .@{header-trigger-prefix-cls} {
       display: flex;
       height: 100%;

+ 77 - 77
src/layouts/default/index.vue

@@ -3,7 +3,7 @@
   <Layout :class="prefixCls" v-bind="lockEvents">
     <LayoutFeatures />
     <LayoutHeader />
-    <SystemSelect />
+    <!-- <SystemSelect /> -->
     <SimpleMap />
     <Layout :class="[layoutClass]">
       <LayoutSideBar />
@@ -17,93 +17,93 @@
 </template>
 
 <script lang="ts">
-  import { defineComponent, computed, unref } from 'vue';
-  import { Layout } from 'ant-design-vue';
-  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+import { defineComponent, computed, unref } from 'vue';
+import { Layout } from 'ant-design-vue';
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-  import LayoutHeader from './header/index.vue';
-  import MultipleTabs from './tabs/index.vue';
-  import LayoutContent from './content/index.vue';
-  import LayoutSideBar from './sider/index.vue';
-  import SystemSelect from './feature/SystemSelect.vue';
-  import SimpleMap from './feature/SimpleMap.vue';
+import LayoutHeader from './header/index.vue';
+import MultipleTabs from './tabs/index.vue';
+import LayoutContent from './content/index.vue';
+import LayoutSideBar from './sider/index.vue';
+// import SystemSelect from './feature/SystemSelect.vue';
+import SimpleMap from './feature/SimpleMap.vue';
 
-  // import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useLockPage } from '/@/hooks/web/useLockPage';
+// import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useDesign } from '/@/hooks/web/useDesign';
+import { useLockPage } from '/@/hooks/web/useLockPage';
 
-  // import { useAppInject } from '/@/hooks/web/useAppInject';
+// import { useAppInject } from '/@/hooks/web/useAppInject';
 
-  export default defineComponent({
-    name: 'DefaultLayout',
-    components: {
-      LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')),
-      LayoutFooter: createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')),
-      LayoutHeader,
-      LayoutContent,
-      LayoutSideBar,
-      MultipleTabs,
-      Layout,
-      SystemSelect,
-      SimpleMap,
-    },
-    setup() {
-      const { prefixCls } = useDesign('default-layout');
-      // const { getIsMobile } = useAppInject();
-      // const { getShowFullHeaderRef } = useHeaderSetting();
-      const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting();
+export default defineComponent({
+  name: 'DefaultLayout',
+  components: {
+    LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')),
+    LayoutFooter: createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')),
+    LayoutHeader,
+    LayoutContent,
+    LayoutSideBar,
+    MultipleTabs,
+    Layout,
+    // SystemSelect,
+    SimpleMap,
+  },
+  setup() {
+    const { prefixCls } = useDesign('default-layout');
+    // const { getIsMobile } = useAppInject();
+    // const { getShowFullHeaderRef } = useHeaderSetting();
+    const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting();
 
-      // Create a lock screen monitor
-      const lockEvents = useLockPage();
+    // Create a lock screen monitor
+    const lockEvents = useLockPage();
 
-      const layoutClass = computed(() => {
-        let cls: string[] = ['ant-layout'];
-        if (unref(getIsMixSidebar) || unref(getShowMenu)) {
-          cls.push('ant-layout-has-sider');
-        }
-        return cls;
-      });
+    const layoutClass = computed(() => {
+      let cls: string[] = ['ant-layout'];
+      if (unref(getIsMixSidebar) || unref(getShowMenu)) {
+        cls.push('ant-layout-has-sider');
+      }
+      return cls;
+    });
 
-      return {
-        getShowSidebar,
-        prefixCls,
-        getIsMixSidebar,
-        layoutClass,
-        lockEvents,
-      };
-    },
-  });
+    return {
+      getShowSidebar,
+      prefixCls,
+      getIsMixSidebar,
+      layoutClass,
+      lockEvents,
+    };
+  },
+});
 </script>
 <style lang="less">
-  @prefix-cls: ~'@{namespace}-default-layout';
+@prefix-cls: ~'@{namespace}-default-layout';
 
-  .@{prefix-cls} {
-    display: flex;
-    width: 100%;
-    min-height: 100%;
-    flex-direction: column;
+.@{prefix-cls} {
+  display: flex;
+  width: 100%;
+  min-height: 100%;
+  flex-direction: column;
 
-    > .ant-layout {
-      height: 0;
-      margin: 10px;
-      background-color: transparent;
-      position: relative;
-      z-index: @layout-basic-z-index;
-    }
+  >.ant-layout {
+    height: 0;
+    margin: 10px;
+    background-color: transparent;
+    position: relative;
+    z-index: @layout-basic-z-index;
+  }
 
-    &-main {
-      width: 100%;
-      border-top-right-radius: 4px;
-      border-bottom-right-radius: 4px;
-      // background-color: #aaaaaa32;
-      // border: 1px solid @white;
-      box-shadow: inset 0 0 5px 0 @white;
-      background: rgba(255, 255, 255, 0.2);
-      backdrop-filter: blur(14px);
-      // -webkit-backdrop-filter: blur(10px);
-      // 代码逻辑说明:【issues/8709】LayoutContent样式多出1px
-      // margin-left: 1px;
-    }
+  &-main {
+    width: 100%;
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+    // background-color: #aaaaaa32;
+    // border: 1px solid @white;
+    box-shadow: inset 0 0 5px 0 @white;
+    background: rgba(255, 255, 255, 0.2);
+    backdrop-filter: blur(14px);
+    // -webkit-backdrop-filter: blur(10px);
+    // 代码逻辑说明:【issues/8709】LayoutContent样式多出1px
+    // margin-left: 1px;
   }
+}
 </style>

+ 190 - 188
src/layouts/default/menu/index.vue

@@ -1,207 +1,209 @@
 <script lang="tsx">
-  import type { PropType, CSSProperties } from 'vue';
-
-  import { computed, defineComponent, unref, toRef } from 'vue';
-  import { BasicMenu } from '/@/components/Menu';
-  import { SimpleMenu } from '/@/components/SimpleMenu';
-  // import { AppLogo } from '/@/components/Application';
-
-  import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
-
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { ScrollContainer } from '/@/components/Container';
-
-  import { useGo } from '/@/hooks/web/usePage';
-  import { useSplitMenu } from './useLayoutMenu';
-  import { openWindow } from '/@/utils';
-  import { propTypes } from '/@/utils/propTypes';
-  import { isUrl } from '/@/utils/is';
-  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
-  import { useAppInject } from '/@/hooks/web/useAppInject';
-  // import { useDesign } from '/@/hooks/web/useDesign';
-  import { useLocaleStore } from '/@/store/modules/locale';
-  import { Button } from 'ant-design-vue';
-  import { SearchOutlined } from '@ant-design/icons-vue';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { AppSearch } from '/@/components/Application';
-
-  export default defineComponent({
-    name: 'LayoutMenu',
-    props: {
-      theme: propTypes.oneOf(['light', 'dark']),
-
-      splitType: {
-        type: Number as PropType<MenuSplitTyeEnum>,
-        default: MenuSplitTyeEnum.NONE,
-      },
-
-      isHorizontal: propTypes.bool,
-      // menu Mode
-      menuMode: {
-        type: [String] as PropType<Nullable<MenuModeEnum>>,
-        default: '',
-      },
+import type { PropType, CSSProperties } from 'vue';
+
+import { computed, defineComponent, unref, toRef } from 'vue';
+import { BasicMenu } from '/@/components/Menu';
+import { SimpleMenu } from '/@/components/SimpleMenu';
+// import { AppLogo } from '/@/components/Application';
+
+import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
+
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { ScrollContainer } from '/@/components/Container';
+
+import { useGo } from '/@/hooks/web/usePage';
+import { useSplitMenu } from './useLayoutMenu';
+import { openWindow } from '/@/utils';
+import { propTypes } from '/@/utils/propTypes';
+import { isUrl } from '/@/utils/is';
+import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+import { useAppInject } from '/@/hooks/web/useAppInject';
+// import { useDesign } from '/@/hooks/web/useDesign';
+import { useLocaleStore } from '/@/store/modules/locale';
+import { Button } from 'ant-design-vue';
+import { SearchOutlined } from '@ant-design/icons-vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { AppSearch } from '/@/components/Application';
+
+
+
+export default defineComponent({
+  name: 'LayoutMenu',
+  props: {
+    theme: propTypes.oneOf(['light', 'dark']),
+
+    splitType: {
+      type: Number as PropType<MenuSplitTyeEnum>,
+      default: MenuSplitTyeEnum.NONE,
     },
-    setup(props) {
-      const go = useGo();
-      const { t } = useI18n();
-
-      const {
-        getMenuMode,
-        getMenuType,
-        getMenuTheme,
-        getCollapsed,
-        getCollapsedShowTitle,
-        getAccordion,
-        getIsHorizontal,
-        getIsSidebarType,
-        getSplit,
-      } = useMenuSetting();
-      const { getShowLogo } = useRootSetting();
-
-      // const { prefixCls } = useDesign('layout-menu');
-
-      const { menusRef } = useSplitMenu(toRef(props, 'splitType'));
-
-      const { getIsMobile } = useAppInject();
-
-      const getComputedMenuMode = computed(() => (unref(getIsMobile) ? MenuModeEnum.INLINE : props.menuMode || unref(getMenuMode)));
-
-      const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
-
-      const getIsShowLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
-
-      const getUseScroll = computed(() => {
-        return (
-          !unref(getIsHorizontal) &&
-          (unref(getIsSidebarType) || props.splitType === MenuSplitTyeEnum.LEFT || props.splitType === MenuSplitTyeEnum.NONE)
-        );
-      });
-
-      const getWrapperStyle = computed((): CSSProperties => {
-        return {
-          // 代码逻辑说明: 【issues/7548】侧边栏导航模式时会导致下面菜单滚动显示不全
-          height: `calc(100% - ${unref(getIsShowLogo) ? '60px' : '0px'})`,
-        };
-      });
-
-      // const getLogoClass = computed(() => {
-      //   return [
-      //     `${prefixCls}-logo`,
-      //     unref(getComputedMenuTheme),
-      //     {
-      //       [`${prefixCls}--mobile`]: unref(getIsMobile),
-      //     },
-      //   ];
-      // });
-
-      const getCommonProps = computed(() => {
-        const menus = unref(menusRef);
-        return {
-          menus,
-          beforeClickFn: beforeMenuClickFn,
-          items: menus,
-          theme: unref(getComputedMenuTheme),
-          accordion: unref(getAccordion),
-          collapse: unref(getCollapsed),
-          collapsedShowTitle: unref(getCollapsedShowTitle),
-          onMenuClick: handleMenuClick,
-        };
-      });
-      /**
-       * click menu
-       * @param menu
-       */
-      // 代码逻辑说明: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称
-      const localeStore = useLocaleStore();
-      function handleMenuClick(path: string, item) {
-        if (item) {
-          localeStore.setPathTitle(path, item.title || '');
-        }
-        go(path);
-      }
 
-      /**
-       * before click menu
-       * @param menu
-       */
-      async function beforeMenuClickFn(path: string) {
-        if (!isUrl(path)) {
-          return true;
-        }
-        openWindow(path);
-        return false;
+    isHorizontal: propTypes.bool,
+    // menu Mode
+    menuMode: {
+      type: [String] as PropType<Nullable<MenuModeEnum>>,
+      default: '',
+    },
+  },
+  setup(props) {
+    const go = useGo();
+    const { t } = useI18n();
+
+    const {
+      getMenuMode,
+      getMenuType,
+      getMenuTheme,
+      getCollapsed,
+      getCollapsedShowTitle,
+      getAccordion,
+      getIsHorizontal,
+      getIsSidebarType,
+      getSplit,
+    } = useMenuSetting();
+    const { getShowLogo } = useRootSetting();
+
+    // const { prefixCls } = useDesign('layout-menu');
+
+    const { menusRef } = useSplitMenu(toRef(props, 'splitType'));
+
+    const { getIsMobile } = useAppInject();
+
+    const getComputedMenuMode = computed(() => (unref(getIsMobile) ? MenuModeEnum.INLINE : props.menuMode || unref(getMenuMode)));
+
+    const getComputedMenuTheme = computed(() => props.theme || unref(getMenuTheme));
+
+    const getIsShowLogo = computed(() => unref(getShowLogo) && unref(getIsSidebarType));
+
+    const getUseScroll = computed(() => {
+      return (
+        !unref(getIsHorizontal) &&
+        (unref(getIsSidebarType) || props.splitType === MenuSplitTyeEnum.LEFT || props.splitType === MenuSplitTyeEnum.NONE)
+      );
+    });
+
+    const getWrapperStyle = computed((): CSSProperties => {
+      return {
+        // 代码逻辑说明: 【issues/7548】侧边栏导航模式时会导致下面菜单滚动显示不全
+        height: `calc(100% - ${unref(getIsShowLogo) ? '60px' : '0px'})`,
+      };
+    });
+
+    // const getLogoClass = computed(() => {
+    //   return [
+    //     `${prefixCls}-logo`,
+    //     unref(getComputedMenuTheme),
+    //     {
+    //       [`${prefixCls}--mobile`]: unref(getIsMobile),
+    //     },
+    //   ];
+    // });
+
+    const getCommonProps = computed(() => {
+      const menus = unref(menusRef);
+      return {
+        menus,
+        beforeClickFn: beforeMenuClickFn,
+        items: menus,
+        theme: unref(getComputedMenuTheme),
+        accordion: unref(getAccordion),
+        collapse: unref(getCollapsed),
+        collapsedShowTitle: unref(getCollapsedShowTitle),
+        onMenuClick: handleMenuClick,
+      };
+    });
+    /**
+     * click menu
+     * @param menu
+     */
+    // 代码逻辑说明: VUEN-1144 online 配置成菜单后,打开菜单,显示名称未展示为菜单名称
+    const localeStore = useLocaleStore();
+    function handleMenuClick(path: string, item) {
+      if (item) {
+        localeStore.setPathTitle(path, item.title || '');
       }
+      go(path);
+    }
 
-      function renderMenu() {
-        const { menus, ...menuProps } = unref(getCommonProps);
-        // console.log(menus);
-        if (!menus || !menus.length) return null;
-        return (
-          <>
-            {!getCollapsed.value ? (
-              <AppSearch class="m-5px">
-                <Button class="w-full">
-                  {t('layout.setting.menuSearch')}
-                  <SearchOutlined />
-                </Button>
-              </AppSearch>
-            ) : (
-              <AppSearch class="m-5px">
-                <Button shape="circle">
-                  <SearchOutlined />
-                </Button>
-              </AppSearch>
-            )}
-            {!props.isHorizontal ? (
-              <SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menusRef.value} />
-            ) : (
-              <BasicMenu
-                {...(menuProps as any)}
-                isHorizontal={props.isHorizontal}
-                type={unref(getMenuType)}
-                showLogo={unref(getIsShowLogo)}
-                mode={unref(getComputedMenuMode as any)}
-                items={menusRef.value}
-              />
-            )}
-          </>
-        );
+    /**
+     * before click menu
+     * @param menu
+     */
+    async function beforeMenuClickFn(path: string) {
+      if (!isUrl(path)) {
+        return true;
       }
+      openWindow(path);
+      return false;
+    }
 
-      return () => {
-        return <>{unref(getUseScroll) ? <ScrollContainer style={unref(getWrapperStyle)}>{() => renderMenu()}</ScrollContainer> : renderMenu()}</>;
-      };
-    },
-  });
+    function renderMenu() {
+      const { menus, ...menuProps } = unref(getCommonProps);
+      // console.log(menus);
+      if (!menus || !menus.length) return null;
+      return (
+        <>
+          {!getCollapsed.value ? (
+            <AppSearch class="m-5px">
+              <Button class="w-full">
+                {t('layout.setting.menuSearch')}
+                <SearchOutlined />
+              </Button>
+            </AppSearch>
+          ) : (
+            <AppSearch class="m-5px">
+              <Button shape="circle">
+                <SearchOutlined />
+              </Button>
+            </AppSearch>
+          )}
+          {!props.isHorizontal ? (
+            <SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menusRef.value} />
+          ) : (
+            <BasicMenu
+              {...(menuProps as any)}
+              isHorizontal={props.isHorizontal}
+              type={unref(getMenuType)}
+              showLogo={unref(getIsShowLogo)}
+              mode={unref(getComputedMenuMode as any)}
+              items={menusRef.value}
+            />
+          )}
+        </>
+      );
+    }
+
+    return () => {
+      return <>{unref(getUseScroll) ? <ScrollContainer style={unref(getWrapperStyle)}>{() => renderMenu()}</ScrollContainer> : renderMenu()}</>;
+    };
+  },
+});
 </script>
 <style lang="less" scoped>
-  // 代码逻辑说明: 【QQYUN-5872】菜单优化,上下滚动条去掉
-  .scroll-container :deep(.scrollbar__bar) {
-    display: none;
-  }
+// 代码逻辑说明: 【QQYUN-5872】菜单优化,上下滚动条去掉
+.scroll-container :deep(.scrollbar__bar) {
+  display: none;
+}
 </style>
 <style lang="less">
-  @prefix-cls: ~'@{namespace}-layout-menu';
-  @logo-prefix-cls: ~'@{namespace}-app-logo';
+@prefix-cls: ~'@{namespace}-layout-menu';
+@logo-prefix-cls: ~'@{namespace}-app-logo';
 
-  .@{prefix-cls} {
-    &-logo {
-      height: @header-height;
-      padding: 10px 4px 10px 10px;
+.@{prefix-cls} {
+  &-logo {
+    height: @header-height;
+    padding: 10px 4px 10px 10px;
 
-      img {
-        width: @logo-width;
-        height: @logo-width;
-      }
+    img {
+      width: @logo-width;
+      height: @logo-width;
     }
+  }
 
-    &--mobile {
-      .@{logo-prefix-cls} {
-        &__title {
-          opacity: 1;
-        }
+  &--mobile {
+    .@{logo-prefix-cls} {
+      &__title {
+        opacity: 1;
       }
     }
   }
+}
 </style>

+ 77 - 70
src/layouts/default/plain.vue

@@ -10,86 +10,93 @@
 </template>
 
 <script lang="ts">
-  import { defineComponent, computed, unref } from 'vue';
-  import { Layout } from 'ant-design-vue';
-  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
+import { defineComponent, computed, unref } from 'vue';
+import { Layout } from 'ant-design-vue';
+import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-  import LayoutHeader from './header/index.vue';
-  import LayoutContent from './content/index.vue';
-  import SystemSelect from './feature/SystemSelect.vue';
-  import SimpleMap from './feature/SimpleMap.vue';
+import LayoutHeader from './header/index.vue';
+import LayoutContent from './content/index.vue';
+import SystemSelect from './feature/SystemSelect.vue';
+import SimpleMap from './feature/SimpleMap.vue';
 
-  // import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useLockPage } from '/@/hooks/web/useLockPage';
+// import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useDesign } from '/@/hooks/web/useDesign';
+import { useLockPage } from '/@/hooks/web/useLockPage';
 
-  // import { useAppInject } from '/@/hooks/web/useAppInject';
+// import { useAppInject } from '/@/hooks/web/useAppInject';
 
-  export default defineComponent({
-    name: 'DefaultLayout',
-    components: {
-      LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')),
-      LayoutHeader,
-      LayoutContent,
-      Layout,
-      SystemSelect,
-      SimpleMap,
-    },
-    setup() {
-      const { prefixCls } = useDesign('default-layout');
-      // const { getIsMobile } = useAppInject();
-      // const { getShowFullHeaderRef } = useHeaderSetting();
-      const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting();
+export default defineComponent({
+  name: 'DefaultLayout',
+  components: {
+    LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')),
+    LayoutHeader,
+    LayoutContent,
+    Layout,
+    SystemSelect,
+    SimpleMap,
+  },
+  setup() {
+    const { prefixCls } = useDesign('default-layout');
+    // const { getIsMobile } = useAppInject();
+    // const { getShowFullHeaderRef } = useHeaderSetting();
+    const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting();
 
-      // Create a lock screen monitor
-      const lockEvents = useLockPage();
+    // Create a lock screen monitor
+    const lockEvents = useLockPage();
 
-      const layoutClass = computed(() => {
-        let cls: string[] = ['ant-layout'];
-        if (unref(getIsMixSidebar) || unref(getShowMenu)) {
-          cls.push('ant-layout-has-sider');
-        }
-        return cls;
-      });
+    const layoutClass = computed(() => {
+      let cls: string[] = ['ant-layout'];
+      if (unref(getIsMixSidebar) || unref(getShowMenu)) {
+        cls.push('ant-layout-has-sider');
+      }
+      return cls;
+    });
 
-      return {
-        getShowSidebar,
-        prefixCls,
-        getIsMixSidebar,
-        layoutClass,
-        lockEvents,
-      };
-    },
-  });
+    return {
+      getShowSidebar,
+      prefixCls,
+      getIsMixSidebar,
+      layoutClass,
+      lockEvents,
+    };
+  },
+});
 </script>
-<style lang="less">
-  @prefix-cls: ~'@{namespace}-default-layout';
+<style lang="less" scoped>
+@prefix-cls: ~'@{namespace}-default-layout';
 
-  .@{prefix-cls} {
-    display: flex;
-    width: 100%;
-    min-height: 100%;
-    flex-direction: column;
+.@{prefix-cls} {
+  display: flex;
+  width: 100%;
+  min-height: 100%;
+  flex-direction: column;
 
-    > .ant-layout {
-      height: 0;
-      margin: 10px;
-      background-color: transparent;
-      position: relative;
-      z-index: @layout-basic-z-index;
-    }
+  >.ant-layout {
+    height: 0;
+    margin: 10px;
+    background-color: transparent;
+    position: relative;
+    z-index: @layout-basic-z-index;
+  }
 
-    &-main {
-      width: 100%;
-      border-top-right-radius: 10px;
-      border-bottom-right-radius: 10px;
-      // background-color: #aaaaaa32;
-      background: rgba(255, 255, 255, 0.2);
-      backdrop-filter: blur(10px);
-      -webkit-backdrop-filter: blur(10px);
-      // 代码逻辑说明:【issues/8709】LayoutContent样式多出1px
-      // margin-left: 1px;
-    }
+  &-main {
+    width: 100%;
+    border-top-right-radius: 10px;
+    border-bottom-right-radius: 10px;
+    // background-color: #aaaaaa32;
+    background: rgba(255, 255, 255, 0.2);
+    backdrop-filter: blur(10px);
+    -webkit-backdrop-filter: blur(10px);
+    // 代码逻辑说明:【issues/8709】LayoutContent样式多出1px
+    // margin-left: 1px;
   }
+
+
+}
+
+:deep(.jeecg-system-select) {
+  margin-left: 10px;
+  margin-top: 10px;
+}
 </style>

+ 134 - 135
src/layouts/default/sider/LayoutSider.vue

@@ -1,156 +1,155 @@
 <template>
-  <Sider
-    v-show="showClassSideBarRef"
-    breakpoint="lg"
-    collapsible
-    :class="getSiderClass"
-    :width="getMenuWidth"
-    :collapsed="getCollapsed"
-    :collapsedWidth="getCollapsedWidth"
-    :theme="getMenuTheme"
-    @breakpoint="onBreakpointChange"
-  >
-    <LayoutMenu :theme="getMenuTheme" :menuMode="getMode" :splitType="getSplitType" showSearch />
-    <template #trigger>
-      <LayoutTrigger />
-    </template>
-  </Sider>
+  <div style="display: flex; flex-direction: column; height: 100%;">
+    <SystemSelect v-if="!getCollapsed" />
+    <Sider v-show="showClassSideBarRef" breakpoint="lg" collapsible :class="getSiderClass" :width="getMenuWidth"
+      :collapsed="getCollapsed" :collapsedWidth="getCollapsedWidth" :theme="getMenuTheme"
+      @breakpoint="onBreakpointChange">
+      <LayoutMenu :theme="getMenuTheme" :menuMode="getMode" :splitType="getSplitType" showSearch />
+      <template #trigger>
+        <LayoutTrigger />
+      </template>
+    </Sider>
+  </div>
+
 </template>
 <script lang="ts">
-  import { computed, defineComponent, unref, CSSProperties } from 'vue';
-
-  import { Layout } from 'ant-design-vue';
-  import LayoutMenu from '../menu/index.vue';
-  // import LayoutMenu from '../menu/SearchMenu.vue';
-  import LayoutTrigger from '/@/layouts/default/trigger/index.vue';
-
-  import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
-
-  import { useAppStore } from '@/store/modules/app';
-  import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
-  import { useSiderEvent } from './useLayoutSider';
-  import { useDesign } from '/@/hooks/web/useDesign';
-
-  // import DragBar from './DragBar.vue';
-
-  export default defineComponent({
-    name: 'LayoutSideBar',
-    components: { Sider: Layout.Sider, LayoutMenu, LayoutTrigger },
-    setup() {
-      // const dragBarRef = ref<ElRef>(null);
-      // const sideRef = ref<ElRef>(null);
-
-      const { getCollapsed, getMenuWidth, getSplit, getMenuTheme, getRealWidth, getMenuHidden, getMenuFixed, getIsMixMode, toggleCollapsed } =
-        useMenuSetting();
-
-      const { prefixCls } = useDesign('layout-sideBar');
-
-      const appStore = useAppStore();
-
-      // useDragLine(sideRef, dragBarRef);
-
-      const { getCollapsedWidth, onBreakpointChange } = useSiderEvent();
-
-      const getMode = computed(() => {
-        return unref(getSplit) ? MenuModeEnum.INLINE : null;
-      });
-
-      const getSplitType = computed(() => {
-        return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE;
-      });
-
-      const showClassSideBarRef = computed(() => {
-        // 控制是否显示侧边栏
-        if (appStore.getLayoutHideSider) {
-          return false;
-        }
-        return unref(getSplit) ? !unref(getMenuHidden) : true;
-      });
-
-      const getSiderClass = computed(() => {
-        return [
-          prefixCls,
-          {
-            [`${prefixCls}--fixed`]: unref(getMenuFixed),
-            [`${prefixCls}--mix`]: unref(getIsMixMode),
-          },
-        ];
-      });
-
-      const getHiddenDomStyle = computed((): CSSProperties => {
-        const width = `${unref(getRealWidth)}px`;
-        return {
-          width: width,
-          overflow: 'hidden',
-          flex: `0 0 ${width}`,
-          maxWidth: width,
-          minWidth: width,
-          transition: 'all 0.2s',
-        };
-      });
-
-      // 在此处使用计算量可能会导致sider异常
-      // andv 更新后,如果trigger插槽可用,则此处代码可废弃
-      // const getTrigger = h(LayoutTrigger);
+import { computed, defineComponent, unref, CSSProperties } from 'vue';
 
-      return {
+import { Layout } from 'ant-design-vue';
+import LayoutMenu from '../menu/index.vue';
+// import LayoutMenu from '../menu/SearchMenu.vue';
+import LayoutTrigger from '/@/layouts/default/trigger/index.vue';
+
+import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum';
+
+import { useAppStore } from '@/store/modules/app';
+import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
+import { useSiderEvent } from './useLayoutSider';
+import { useDesign } from '/@/hooks/web/useDesign';
+import SystemSelect from '../feature/SystemSelect.vue';
+
+// import DragBar from './DragBar.vue';
+
+export default defineComponent({
+  name: 'LayoutSideBar',
+  components: { Sider: Layout.Sider, LayoutMenu, LayoutTrigger, SystemSelect },
+  setup() {
+    // const dragBarRef = ref<ElRef>(null);
+    // const sideRef = ref<ElRef>(null);
+
+    const { getCollapsed, getMenuWidth, getSplit, getMenuTheme, getRealWidth, getMenuHidden, getMenuFixed, getIsMixMode, toggleCollapsed } =
+      useMenuSetting();
+
+    const { prefixCls } = useDesign('layout-sideBar');
+
+    const appStore = useAppStore();
+
+    // useDragLine(sideRef, dragBarRef);
+
+    const { getCollapsedWidth, onBreakpointChange } = useSiderEvent();
+
+    const getMode = computed(() => {
+      return unref(getSplit) ? MenuModeEnum.INLINE : null;
+    });
+
+    const getSplitType = computed(() => {
+      return unref(getSplit) ? MenuSplitTyeEnum.LEFT : MenuSplitTyeEnum.NONE;
+    });
+
+    const showClassSideBarRef = computed(() => {
+      // 控制是否显示侧边栏
+      if (appStore.getLayoutHideSider) {
+        return false;
+      }
+      return unref(getSplit) ? !unref(getMenuHidden) : true;
+    });
+
+    const getSiderClass = computed(() => {
+      return [
         prefixCls,
-        getHiddenDomStyle,
-        getSiderClass,
-        // getTrigger,
-        getCollapsedWidth,
-        getMenuFixed,
-        showClassSideBarRef,
-        getMenuWidth,
-        getCollapsed,
-        getMenuTheme,
-        onBreakpointChange,
-        getMode,
-        getSplitType,
-        toggleCollapsed,
+        {
+          [`${prefixCls}--fixed`]: unref(getMenuFixed),
+          [`${prefixCls}--mix`]: unref(getIsMixMode),
+        },
+      ];
+    });
+
+    const getHiddenDomStyle = computed((): CSSProperties => {
+      const width = `${unref(getRealWidth)}px`;
+      return {
+        width: width,
+        overflow: 'hidden',
+        flex: `0 0 ${width}`,
+        maxWidth: width,
+        minWidth: width,
+        transition: 'all 0.2s',
       };
-    },
-  });
+    });
+
+    // 在此处使用计算量可能会导致sider异常
+    // andv 更新后,如果trigger插槽可用,则此处代码可废弃
+    // const getTrigger = h(LayoutTrigger);
+
+    return {
+      prefixCls,
+      getHiddenDomStyle,
+      getSiderClass,
+      // getTrigger,
+      getCollapsedWidth,
+      getMenuFixed,
+      showClassSideBarRef,
+      getMenuWidth,
+      getCollapsed,
+      getMenuTheme,
+      onBreakpointChange,
+      getMode,
+      getSplitType,
+      toggleCollapsed,
+
+    };
+  },
+});
 </script>
 <style lang="less">
-  @prefix-cls: ~'@{namespace}-layout-sideBar';
+@prefix-cls: ~'@{namespace}-layout-sideBar';
 
-  .@{prefix-cls} {
-    border-top-left-radius: 10px;
-    border-bottom-left-radius: 10px;
-    z-index: @layout-sider-fixed-z-index;
+.@{prefix-cls} {
+  border-top-left-radius: 10px;
+  border-bottom-left-radius: 10px;
+  z-index: @layout-sider-fixed-z-index;
+  flex: 1 !important;
 
-    &.ant-layout-sider-dark {
-      background-color: @sider-dark-bg-color;
+  &.ant-layout-sider-dark {
+    background-color: @sider-dark-bg-color;
 
-      .ant-layout-sider-trigger {
-        color: darken(@white, 25%);
-        background-color: @trigger-dark-bg-color;
+    .ant-layout-sider-trigger {
+      color: darken(@white, 25%);
+      background-color: @trigger-dark-bg-color;
 
-        &:hover {
-          color: @white;
-          background-color: @trigger-dark-hover-bg-color;
-        }
+      &:hover {
+        color: @white;
+        background-color: @trigger-dark-hover-bg-color;
       }
     }
+  }
 
-    &:not(.ant-layout-sider-dark) {
-      // box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+  &:not(.ant-layout-sider-dark) {
+    // box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
 
-      .ant-layout-sider-trigger {
-        color: @text-color-base;
-        border-top: 1px solid @border-color-light;
-      }
+    .ant-layout-sider-trigger {
+      color: @text-color-base;
+      border-top: 1px solid @border-color-light;
     }
+  }
 
-    .ant-layout-sider-zero-width-trigger {
-      top: 40%;
-      z-index: 10;
-    }
+  .ant-layout-sider-zero-width-trigger {
+    top: 40%;
+    z-index: 10;
+  }
 
-    & .ant-layout-sider-trigger {
-      // height: 36px;
-      line-height: 36px;
-    }
+  & .ant-layout-sider-trigger {
+    // height: 36px;
+    line-height: 36px;
   }
+}
 </style>

+ 23 - 6
src/store/modules/mine.ts

@@ -3,12 +3,14 @@ import { ref, computed } from 'vue';
 
 import { getEnfMineTree } from '/@/api/sys/menu';
 import { getUserMinePermissionData } from '/@/components/Form/src/jeecg/components/MineCascader/mineData.api';
-import { findNode, findNodeAll, findPath, listToTree, treeToList } from '/@/utils/helper/treeHelper';
+import { findNode, findNodeAll, findPath, listToTree, treeToList, forEach } from '/@/utils/helper/treeHelper';
 import { isArray, isFunction, isNil } from 'lodash';
 
 export interface MineDepartment {
   /** 唯一标识 */
   id: string;
+  /** 原本的标识 */
+  rawid: string;
   /** 部门名称 */
   departName: string;
   /** 父部门ID */
@@ -19,6 +21,8 @@ export interface MineDepartment {
   childDepart: MineDepartment[];
   /** 传真,用作矿井编号(矿井编码) */
   fax: string | null;
+  /** 是否禁用,没有子节点的部门即禁用 */
+  disabled?: boolean;
 }
 
 const DEFAULT_CONFIG = {
@@ -101,14 +105,29 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
       ]);
 
       // 标记节点类型
-      r1.forEach((node) => (node.isLeaf = false)); // 非叶子节点
+      r1.forEach((node: any) => {
+        // node.id = ''
+        node.isLeaf = false;
+      }); // 非叶子节点
       r2.forEach((node) => {
+        node.rawid = node.id;
+        node.id = node.fax; // 使用矿井编码作为ID
         node.isLeaf = true; // 叶子节点(矿井)
-        node.id = node.fax || node.id; // 使用矿井编码作为ID
       });
 
       // 合并数据并转换为树形结构
       const tree = listToTree([...r1, ...r2], DEFAULT_CONFIG);
+      forEach(
+        tree,
+        (node) => {
+          // 跳过叶节点
+          if (node.isLeaf) return;
+          node.rawid = node.id;
+          node.id = calcMineCodeByDepart(node);
+          node.disabled = !Boolean(node.childDepart.length);
+        },
+        DEFAULT_CONFIG
+      );
 
       departTree.value = tree;
       // 深拷贝保存原始数据,用于过滤后恢复
@@ -164,10 +183,9 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
     }
     const target = depart.filter((e) => !isNil(e));
     const list = treeToList(target, DEFAULT_CONFIG);
-
     if (isArray(list)) {
       return list
-        .filter((item) => item.isLeaf && item.fax) // 只取叶子节点(矿井)且fax不为空
+        .filter((item) => item.isLeaf) // 只取叶子节点(矿井)且fax不为空
         .map((item) => item.fax!) // 提取矿井编码
         .join(','); // 用逗号分隔多个矿井编码
     }
@@ -208,7 +226,6 @@ export const useMineDepartmentStore = defineStore('mine-department-store', () =>
 
     const target = depart.filter((e) => !isNil(e));
     const node = findNode(target, (e) => e.id === id, DEFAULT_CONFIG);
-
     if (isNil(node)) return;
     return node;
   }

+ 24 - 18
src/views/analysis/warningAnalysis/airLeakStatus/index.vue

@@ -41,16 +41,10 @@
     <a-table></a-table>
   </a-modal>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="height: 400px"
-    v-model:visible="visibleresolveModal"
-    :width="600"
-    centered
-    title="密闭漏风处理情况"
-    @ok="handleOkEdit"
-    @cancel="handleCancelEdit"
-  >
-    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue" placeholder="请输入解决情况" :rows="4" />
+  <a-modal style="height: 400px" v-model:visible="visibleresolveModal" :width="600" centered title="密闭漏风处理情况"
+    @ok="handleOkEdit" @cancel="handleCancelEdit">
+    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue"
+      placeholder="请输入解决情况" :rows="4" />
   </a-modal>
 </template>
 
@@ -328,22 +322,34 @@ onUnmounted(() => {
 </script>
 
 <style lang="less" scoped>
+// .board-info {
+//   display: flex;
+//   padding: 10px;
+//   background-color: @white;
+//   // margin: 0 10px;
+//   margin-bottom: 5px;
+//   gap: 10px;
+//   box-sizing: border-box;
+// }
+// .board-item {
+//   flex: 1;
+//   box-sizing: border-box;
+// }
 .board-info {
+  width: 100%;
   display: flex;
-  padding: 10px;
-  background-color: @white;
-  // margin: 0 10px;
-  margin-bottom: 5px;
-  gap: 10px;
-  box-sizing: border-box;
+  flex-wrap: wrap;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  flex: 1;
-  box-sizing: border-box;
+  margin-right: 80px;
 }
+
 :deep(.jeecg-basic-table-form-container .ant-form) {
   border: none !important;
 }
+
 :where(.css-dev-only-do-not-override-x9w3vz).ant-form-item .ant-form-item-label {
   margin-top: 10px !important;
 }

+ 29 - 30
src/views/analysis/warningAnalysis/autoFireAnalysis/index.vue

@@ -4,15 +4,8 @@
     <Tabs v-model:activeKey="activeTab" type="line" class="common-page-tabs">
       <TabPane tab="实时监测" key="realtime">
         <div class="board-info">
-          <MiniBoard
-            :key="index"
-            v-for="(item, index) in boardData"
-            type="A"
-            :label="item.label"
-            :value="item.value"
-            layout="label-top"
-            class="board-item"
-          />
+          <MiniBoard :key="index" v-for="(item, index) in boardData" type="A" :label="item.label" :value="item.value"
+            layout="label-top" class="board-item" />
         </div>
         <!-- 实时数据表格 -->
         <BasicTable @register="registerTable" :scroll="{ x: 'max-content' }" :style="{ padding: 0 }">
@@ -41,16 +34,10 @@
       </TabPane>
     </Tabs>
     <!-- 弹窗组件 -->
-    <a-modal
-      style="height: 400px"
-      v-model:visible="visibleresolveModal"
-      :width="600"
-      centered
-      title="密闭漏风处理情况"
-      @ok="handleOkEdit()"
-      @cancel="handleCancelEdit"
-    >
-      <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue" placeholder="请输入解决情况" :rows="4" />
+    <a-modal style="height: 400px" v-model:visible="visibleresolveModal" :width="600" centered title="密闭漏风处理情况"
+      @ok="handleOkEdit()" @cancel="handleCancelEdit">
+      <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue"
+        placeholder="请输入解决情况" :rows="4" />
     </a-modal>
   </div>
 </template>
@@ -321,20 +308,32 @@ onUnmounted(() => {
 </script>
 
 <style lang="less" scoped>
+// .board-info {
+//   display: grid;
+//   grid-template-columns: repeat(5, auto);
+//   /* 3列:改5则为5列 */
+//   gap: auto;
+//   justify-content: start;
+//   flex-wrap: wrap;
+//   box-sizing: border-box;
+//   background-color: @white;
+//   padding: 10px;
+//   gap: 100px;
+//   // margin: 0 10px;
+//   margin-bottom: 8px;
+// }
+
+// .board-item {
+//   box-sizing: border-box;
+// }
 .board-info {
-  display: grid;
-  grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
-  gap: auto;
-  justify-content: start;
+  width: 100%;
+  display: flex;
   flex-wrap: wrap;
-  box-sizing: border-box;
-  background-color: @white;
-  padding: 10px;
-  gap: 100px;
-  // margin: 0 10px;
-  margin-bottom: 5px;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  box-sizing: border-box;
+  margin-right: 80px;
 }
 </style>

+ 267 - 267
src/views/analysis/warningAnalysis/connectAnalysis/index.vue

@@ -6,7 +6,7 @@
           <div class="filter-section param-section">
             <span class="filter-label">煤矿名称:</span>
             <div class="param-selector">
-              <MineCascader :showDefaultValue="true" :mineCode="mineCode" @change="changeCascader" style="width: 300px"></MineCascader>
+              <MineCascader v-model:value="mineCode" style="width: 300px" :sync-to-store="false" :init-from-store="false"></MineCascader>
             </div>
           </div>
         </Col>
@@ -15,16 +15,21 @@
           <div class="filter-section param-section">
             <span class="filter-label">采空区选择:</span>
             <Select ref="select" v-model:value="goafId" style="width: 300px" placeholder="请选择采空区">
-              <SelectOption v-for="(item, index) in goafOption" :key="index" :value="item.value">{{ item.label }}
-              </SelectOption>
+              <SelectOption v-for="(item, index) in goafOption" :key="index" :value="item.value">{{ item.label }} </SelectOption>
             </Select>
           </div>
         </Col>
         <Col :span="6">
           <div class="filter-section param-section">
             <span class="filter-label">时间选择:</span>
-            <RangePicker v-model:value="dateRange" format="YYYY-MM-DD HH:mm:ss" :placeholder="['开始时间', '结束时间']"
-              style="width: 300px" :show-time="{ format: 'HH:mm:ss' }" @change="changeTime" />
+            <RangePicker
+              v-model:value="dateRange"
+              format="YYYY-MM-DD HH:mm:ss"
+              :placeholder="['开始时间', '结束时间']"
+              style="width: 300px"
+              :show-time="{ format: 'HH:mm:ss' }"
+              @change="changeTime"
+            />
           </div>
         </Col>
         <Col :span="6">
@@ -54,8 +59,7 @@
       <div class="chart-item" style="flex: 1 1 100%">
         <div class="chart-placeholder">
           <template v-if="generatedChartData.length">
-            <CustomChart :chart-data="generatedChartData" :chart-config="generatedChartConfig"
-              style="height: 100%; width: 100%" />
+            <CustomChart :chart-data="generatedChartData" :chart-config="generatedChartConfig" style="height: 100%; width: 100%" />
           </template>
           <template v-else-if="isChartGenerated">
             <div class="empty-chart">暂无匹配数据,请调整筛选条件</div>
@@ -70,88 +74,85 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref, onMounted, watchEffect } from 'vue';
-import dayjs from 'dayjs';
-// import { treeData, historicalMockChartData } from './connectAnalysis.data'; // 引入模拟数据
-import { Select, SelectOption, Row, Col, DatePicker, Button, message, Input } from 'ant-design-vue';
-// import { BasicTree } from '/@/components/Tree/index';
-import CustomChart from '/@/components/Configurable/detail/CustomChart.vue';
-import MineCascader from '@/components/Form/src/jeecg/components/MineCascader/MineCascader.vue';
-import { getGoafHistory, getGoafList } from './connectAnalysis.api';
-import { useRouter } from 'vue-router';
+  import { computed, ref, onMounted, watchEffect } from 'vue';
+  import dayjs from 'dayjs';
+  // import { treeData, historicalMockChartData } from './connectAnalysis.data'; // 引入模拟数据
+  import { Select, SelectOption, Row, Col, DatePicker, Button, message, Input } from 'ant-design-vue';
+  // import { BasicTree } from '/@/components/Tree/index';
+  import CustomChart from '/@/components/Configurable/detail/CustomChart.vue';
+  import MineCascader from '@/components/Form/src/jeecg/components/MineCascader/MineCascader.vue';
+  import { getGoafHistory, getGoafList } from './connectAnalysis.api';
+  import { useRouter } from 'vue-router';
 
-// 组件注册
-const RangePicker: any = DatePicker.RangePicker;
-const { currentRoute } = useRouter();
-const mineCode = ref<any>(currentRoute.value['query']['mineCode'])//传递过来的矿ID
-// 筛选相关响应式数据
-const dateRange = ref([dayjs().add(-30, 'day'), dayjs()]); // 默认时间范围(近1天)
-// const selectedParamsText = ref(''); // 参数选择框显示文本
-// const showTree = ref(false); // 控制树形选择器显示/隐藏
-// const checkedTreeKeys = ref([]); // 树形选中的key
-const selectedParams = ref(['coVal', 'ch4Val', 'c2h4Val', 'c2h2Val', 'co2Val', 'o2Val', 'sourcePressure', 'temperature']); // 选中的参数(实际用于图表)
-const generatedChartData = ref<any[]>([]); // 生成的图表数据
-const generatedChartConfig = ref({}); // 生成的图表配置
-const isChartGenerated = ref(false); // 是否已点击生成
-const goafId = ref('')//采空区id
-const goafOption = ref<any[]>([])//采空区列表
-const filteredData = ref<any[]>([])//曲线数据
+  // 组件注册
+  const RangePicker: any = DatePicker.RangePicker;
+  const { currentRoute } = useRouter();
+  const mineCode = ref<any>(currentRoute.value['query']['mineCode']); //传递过来的矿ID
+  // 筛选相关响应式数据
+  const dateRange = ref([dayjs().add(-30, 'day'), dayjs()]); // 默认时间范围(近1天)
+  // const selectedParamsText = ref(''); // 参数选择框显示文本
+  // const showTree = ref(false); // 控制树形选择器显示/隐藏
+  // const checkedTreeKeys = ref([]); // 树形选中的key
+  const selectedParams = ref(['coVal', 'ch4Val', 'c2h4Val', 'c2h2Val', 'co2Val', 'o2Val', 'sourcePressure', 'temperature']); // 选中的参数(实际用于图表)
+  const generatedChartData = ref<any[]>([]); // 生成的图表数据
+  const generatedChartConfig = ref({}); // 生成的图表配置
+  const isChartGenerated = ref(false); // 是否已点击生成
+  const goafId = ref(''); //采空区id
+  const goafOption = ref<any[]>([]); //采空区列表
+  const filteredData = ref<any[]>([]); //曲线数据
 
-const innerValue = ref('')
+  const innerValue = ref('');
 
-// // Tree Key 与参数名映射(关键:关联树形节点和实际参数)
-//   const treeKeyToParamMap = computed(() => ({
-//     '0-0-0': 'CO',
-//     '0-0-1': 'CH4',
-//     '0-0-2': 'C2H4',
-//     '0-0-3': 'C2H2',
-//     '0-0-4': 'CO2',
-//     '0-0-5': 'O2',
-//     '1-1-0': 'innerPressure',
-//     '1-1-1': 'outerPressure',
-//     '1-1-2': 'pressureDiff',
-//     '2-2': 'temperature',
-//   }));
-//  // 树形选择事件处理
-//   const handleTreeCheck = (checkedKeys) => {
-//     checkedTreeKeys.value = checkedKeys;
-//     // 转换为实际参数名
-//     const params = checkedKeys
-//       .map(key => treeKeyToParamMap.value[key])
-//       .filter(param => param); // 过滤无效参数
-//     selectedParams.value = params;
-//     console.log(selectedParams.value, ' selectedParams')
-//     // 更新输入框显示文本
-//     const paramLabels = params.map(param => paramLabelMap.value[param]);
-//     selectedParamsText.value = paramLabels.join('、');
-//     console.log(selectedParamsText.value, ' selectedParamsText')
-//   };
-
-
-function changeCascader(val) {
-  innerValue.value = val
-}
-function changeTime(val) {
-  dateRange.value[0] = val[0]
-  dateRange.value[1] = val[1]
-}
+  // // Tree Key 与参数名映射(关键:关联树形节点和实际参数)
+  //   const treeKeyToParamMap = computed(() => ({
+  //     '0-0-0': 'CO',
+  //     '0-0-1': 'CH4',
+  //     '0-0-2': 'C2H4',
+  //     '0-0-3': 'C2H2',
+  //     '0-0-4': 'CO2',
+  //     '0-0-5': 'O2',
+  //     '1-1-0': 'innerPressure',
+  //     '1-1-1': 'outerPressure',
+  //     '1-1-2': 'pressureDiff',
+  //     '2-2': 'temperature',
+  //   }));
+  //  // 树形选择事件处理
+  //   const handleTreeCheck = (checkedKeys) => {
+  //     checkedTreeKeys.value = checkedKeys;
+  //     // 转换为实际参数名
+  //     const params = checkedKeys
+  //       .map(key => treeKeyToParamMap.value[key])
+  //       .filter(param => param); // 过滤无效参数
+  //     selectedParams.value = params;
+  //     console.log(selectedParams.value, ' selectedParams')
+  //     // 更新输入框显示文本
+  //     const paramLabels = params.map(param => paramLabelMap.value[param]);
+  //     selectedParamsText.value = paramLabels.join('、');
+  //     console.log(selectedParamsText.value, ' selectedParamsText')
+  //   };
 
+  function changeCascader(val) {
+    innerValue.value = val;
+  }
+  function changeTime(val) {
+    dateRange.value[0] = val[0];
+    dateRange.value[1] = val[1];
+  }
 
-// 参数颜色映射
-const paramColorMap = computed(() => ({
-  coVal: '#f5222d', // 红色
-  ch4Val: '#1890ff', // 蓝色
-  c2h4Val: '#faad14', // 橙色
-  c2h2Val: '#52c41a', // 绿色
-  co2Val: '#722ed1', // 紫色
-  o2Val: '#13c2c2', // 青色
-  sourcePressure: '#ff4d4f', // 浅红
-  temperature: '#40a9ff', // 浅蓝
-}));
+  // 参数颜色映射
+  const paramColorMap = computed(() => ({
+    coVal: '#f5222d', // 红色
+    ch4Val: '#1890ff', // 蓝色
+    c2h4Val: '#faad14', // 橙色
+    c2h2Val: '#52c41a', // 绿色
+    co2Val: '#722ed1', // 紫色
+    o2Val: '#13c2c2', // 青色
+    sourcePressure: '#ff4d4f', // 浅红
+    temperature: '#40a9ff', // 浅蓝
+  }));
 
-// 参数标签映射(图表系列名称)
-const paramLabelMap = computed(() => (
-  {
+  // 参数标签映射(图表系列名称)
+  const paramLabelMap = computed(() => ({
     coVal: 'CO浓度(ppm)',
     ch4Val: 'CH4浓度(%)',
     c2h4Val: 'C2H4浓度(ppm)',
@@ -160,219 +161,218 @@ const paramLabelMap = computed(() => (
     o2Val: 'O2浓度(%)',
     sourcePressure: '压差(kPa)',
     temperature: '温度(℃)',
-  }
-));
-// 生成折线图核心逻辑
-async function generateChart() {
-  // showTree.value = false
-  //获取采空区历史数据列表
-  let startTime = dateRange.value[0].format('YYYY-MM-DD HH:mm:ss');
-  let endTime = dateRange.value[1].format('YYYY-MM-DD HH:mm:ss');
-  let res = await getGoafHistory({ pageNo: 1, pageSize: 100, startTime: startTime, endTime: endTime, goafId: goafId.value });
-  if (res && res.records) {
-    filteredData.value = res.records;
-    isChartGenerated.value = false;
-  } else {
-    filteredData.value.length = 0;
-    isChartGenerated.value = true;
-  }
-  console.log(filteredData.value, 'filteredData');
-
-  // 2. 构建图表数据结构(适配 CustomChart 的 line 类型)
-  const timeMap = new Map();
-  // 修复变量名:filteredRawData → filteredData(之前未定义)
-  filteredData.value.forEach((item) => {
-    const timeStr = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
-    if (!timeMap.has(timeStr)) {
-      timeMap.set(timeStr, { time: timeStr });
+  }));
+  // 生成折线图核心逻辑
+  async function generateChart() {
+    // showTree.value = false
+    //获取采空区历史数据列表
+    let startTime = dateRange.value[0].format('YYYY-MM-DD HH:mm:ss');
+    let endTime = dateRange.value[1].format('YYYY-MM-DD HH:mm:ss');
+    let res = await getGoafHistory({ pageNo: 1, pageSize: 100, startTime: startTime, endTime: endTime, goafId: goafId.value });
+    if (res && res.records) {
+      filteredData.value = res.records;
+      isChartGenerated.value = false;
+    } else {
+      filteredData.value.length = 0;
+      isChartGenerated.value = true;
     }
-    // 只保留选中的参数数据
-    selectedParams.value.forEach((param) => {
-      if (item[param] !== undefined) {
-        timeMap.get(timeStr)[param] = item[param];
+    console.log(filteredData.value, 'filteredData');
+
+    // 2. 构建图表数据结构(适配 CustomChart 的 line 类型)
+    const timeMap = new Map();
+    // 修复变量名:filteredRawData → filteredData(之前未定义)
+    filteredData.value.forEach((item) => {
+      const timeStr = dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss');
+      if (!timeMap.has(timeStr)) {
+        timeMap.set(timeStr, { time: timeStr });
       }
+      // 只保留选中的参数数据
+      selectedParams.value.forEach((param) => {
+        if (item[param] !== undefined) {
+          timeMap.get(timeStr)[param] = item[param];
+        }
+      });
     });
-  });
 
-  // 转换为数组并按时间排序
-  const chartData = Array.from(timeMap.values()).sort((a, b) => {
-    return dayjs(a.time).valueOf() - dayjs(b.time).valueOf();
-  });
-  generatedChartData.value = chartData;
-  console.log(generatedChartData.value, 'generatedChartData');
+    // 转换为数组并按时间排序
+    const chartData = Array.from(timeMap.values()).sort((a, b) => {
+      return dayjs(a.time).valueOf() - dayjs(b.time).valueOf();
+    });
+    generatedChartData.value = chartData;
+    console.log(generatedChartData.value, 'generatedChartData');
 
-  // 3. 构建图表配置(折线图类型,完善适配逻辑)
-  generatedChartConfig.value = {
-    type: 'line', // 折线图
-    clear: true, // 每次生成清空之前的图表
-    legend: { show: true, top: 10, right: 10, textStyle: { color: '#fff', fontSize: 14 } },
-    xAxis: [
-      {
-        type: 'category',
+    // 3. 构建图表配置(折线图类型,完善适配逻辑)
+    generatedChartConfig.value = {
+      type: 'line', // 折线图
+      clear: true, // 每次生成清空之前的图表
+      legend: { show: true, top: 10, right: 10, textStyle: { color: '#fff', fontSize: 14 } },
+      xAxis: [
+        {
+          type: 'category',
+          axisLabel: {
+            rotate: 30,
+            textStyle: {
+              color: '#fff',
+            },
+            formatter: (value) => dayjs(value).format('HH:mm:ss'),
+            interval: Math.max(1, Math.floor(chartData.length / 10)), // 控制x轴标签密度
+          },
+        },
+      ],
+      yAxis: selectedParams.value.map((param) => ({
+        type: 'value',
         axisLabel: {
-          rotate: 30,
           textStyle: {
             color: '#fff',
           },
-          formatter: (value) => dayjs(value).format('HH:mm:ss'),
-          interval: Math.max(1, Math.floor(chartData.length / 10)), // 控制x轴标签密度
         },
-      },
-    ],
-    yAxis: selectedParams.value.map((param) => ({
-      type: 'value',
-      axisLabel: {
-        textStyle: {
-          color: '#fff',
+        name: paramLabelMap.value[param].split('(')[1].replace(')', ''), // 显示单位
+        // nameTextStyle: { color: paramColorMap.value[param] },
+        nameTextStyle: { color: '#fff' },
+        axisLine: { lineStyle: { color: paramColorMap.value[param] } },
+        splitLine: { lineStyle: { opacity: 0.1 } },
+      })),
+      series: selectedParams.value.map((param, index) => ({
+        name: paramLabelMap.value[param],
+        type: 'line',
+        readFrom: '', // 适配 CustomChart 的 baseSeries 读取逻辑
+        label: paramLabelMap.value[param],
+        xprop: 'time', // 对应图表数据的 x 字段(时间)
+        yprop: param, // 对应图表数据的 y 字段(参数值)
+        color: paramColorMap.value[param],
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 4,
+        yAxisIndex: index,
+      })),
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: { type: 'cross' },
+        formatter: (params) => {
+          let tooltipHtml = `<div>${dayjs(params[0].axisValue).format('YYYY-MM-DD HH:mm:ss')}</div>`;
+          params.forEach((param) => {
+            tooltipHtml += `<div style="color: ${param.color}">${param.seriesName}: ${param.value[1]} ${param.seriesName.split('(')[1].replace(')', '')}</div>`;
+          });
+          return tooltipHtml;
         },
       },
-      name: paramLabelMap.value[param].split('(')[1].replace(')', ''), // 显示单位
-      // nameTextStyle: { color: paramColorMap.value[param] },
-      nameTextStyle: { color: '#fff' },
-      axisLine: { lineStyle: { color: paramColorMap.value[param] } },
-      splitLine: { lineStyle: { opacity: 0.1 } },
-    })),
-    series: selectedParams.value.map((param, index) => ({
-      name: paramLabelMap.value[param],
-      type: 'line',
-      readFrom: '', // 适配 CustomChart 的 baseSeries 读取逻辑
-      label: paramLabelMap.value[param],
-      xprop: 'time', // 对应图表数据的 x 字段(时间)
-      yprop: param, // 对应图表数据的 y 字段(参数值)
-      color: paramColorMap.value[param],
-      smooth: true,
-      symbol: 'circle',
-      symbolSize: 4,
-      yAxisIndex: index,
-    })),
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: { type: 'cross' },
-      formatter: (params) => {
-        let tooltipHtml = `<div>${dayjs(params[0].axisValue).format('YYYY-MM-DD HH:mm:ss')}</div>`;
-        params.forEach((param) => {
-          tooltipHtml += `<div style="color: ${param.color}">${param.seriesName}: ${param.value[1]} ${param.seriesName.split('(')[1].replace(')', '')}</div>`;
-        });
-        return tooltipHtml;
-      },
-    },
-    grid: { left: 60, top: 40, right: 60, bottom: 60 },
-  };
+      grid: { left: 60, top: 40, right: 60, bottom: 60 },
+    };
 
-  // 无数据提示
-  if (chartData.length === 0) {
-    message.info('当前筛选条件下无数据');
+    // 无数据提示
+    if (chartData.length === 0) {
+      message.info('当前筛选条件下无数据');
+    }
   }
-}
 
-//获取采空区列表
-async function getGoafListData() {
-  let res = await getGoafList({ mineCode: innerValue.value })
-  if (res.length) {
-    goafOption.value = res.map(el => {
-      return {
-        label: el.devicePos,
-        value: el.id,
-      }
-    })
-    goafId.value = goafId.value ? goafId.value : goafOption.value[0]['value']
+  //获取采空区列表
+  async function getGoafListData() {
+    let res = await getGoafList({ mineCode: innerValue.value });
+    if (res.length) {
+      goafOption.value = res.map((el) => {
+        return {
+          label: el.devicePos,
+          value: el.id,
+        };
+      });
+      goafId.value = goafId.value ? goafId.value : goafOption.value[0]['value'];
+    }
   }
-}
 
-watchEffect(() => {
-  innerValue.value && getGoafListData();
-});
+  watchEffect(() => {
+    innerValue.value && getGoafListData();
+  });
 </script>
 
 <style lang="less" scoped>
-.connectAnalysis {
-  padding: 16px;
+  .connectAnalysis {
+    padding: 16px;
 
-  .filter-area {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 16px;
-    margin-bottom: 20px;
-    padding: 20px;
-    border: 1px solid #f0f0f0;
-    border-radius: 10px;
-    background: @card-bg-color;
-    align-items: center;
-  }
+    .filter-area {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 16px;
+      margin-bottom: 20px;
+      padding: 20px;
+      border: 1px solid #f0f0f0;
+      border-radius: 10px;
+      background: @card-bg-color;
+      align-items: center;
+    }
 
-  .filter-section {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-  }
+    .filter-section {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
 
-  .filter-label {
-    color: #666;
-    min-width: 80px;
-    flex-shrink: 0;
-    font-weight: 500;
-  }
+    .filter-label {
+      color: #666;
+      min-width: 80px;
+      flex-shrink: 0;
+      font-weight: 500;
+    }
 
-  .param-section {
-    flex: 1;
-    // min-width: 300px;
-  }
+    .param-section {
+      flex: 1;
+      // min-width: 300px;
+    }
 
-  .param-selector {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    position: relative;
-  }
+    .param-selector {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      position: relative;
+    }
 
-  .tree-popup {
-    position: absolute;
-    top: 100%;
-    left: 0;
-    margin-top: 8px;
-    width: 300px;
-    max-height: 300px;
-    overflow-y: auto;
-    background: #fff;
-    border: 1px solid #e8e8e8;
-    border-radius: 4px;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-    z-index: 100;
-    padding: 8px;
-  }
+    .tree-popup {
+      position: absolute;
+      top: 100%;
+      left: 0;
+      margin-top: 8px;
+      width: 300px;
+      max-height: 300px;
+      overflow-y: auto;
+      background: #fff;
+      border: 1px solid #e8e8e8;
+      border-radius: 4px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+      z-index: 100;
+      padding: 8px;
+    }
 
-  .chart-area {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 16px;
-    padding: 20px;
-    border: 1px solid #f0f0f0;
-    border-radius: 10px;
-    background: @card-bg-color;
-  }
+    .chart-area {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 16px;
+      padding: 20px;
+      border: 1px solid #f0f0f0;
+      border-radius: 10px;
+      background: @card-bg-color;
+    }
 
-  .chart-item {
-    flex: 1;
-    min-width: 200px;
-  }
+    .chart-item {
+      flex: 1;
+      min-width: 200px;
+    }
 
-  .chart-placeholder {
-    width: 100%;
-    height: 300px;
-    border-radius: 4px;
-    overflow: hidden;
-    background: #333;
-    border: 1px solid #eee;
-  }
+    .chart-placeholder {
+      width: 100%;
+      height: 300px;
+      border-radius: 4px;
+      overflow: hidden;
+      background: #333;
+      border: 1px solid #eee;
+    }
 
-  .empty-chart {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: #999;
-    font-size: 14px;
+    .empty-chart {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: #999;
+      font-size: 14px;
+    }
   }
-}
 </style>

+ 27 - 30
src/views/analysis/warningAnalysis/fireAreaJudgeAnalysis/index.vue

@@ -4,15 +4,8 @@
   <Tabs v-model:activeKey="activeTab" type="line" class="common-page-tabs">
     <TabPane tab="实时监测" key="realtime">
       <div class="board-info">
-        <MiniBoard
-          :key="index"
-          v-for="(item, index) in boardData"
-          type="A"
-          :label="item.label"
-          :value="item.value"
-          layout="label-top"
-          class="board-item"
-        />
+        <MiniBoard :key="index" v-for="(item, index) in boardData" type="A" :label="item.label" :value="item.value"
+          layout="label-top" class="board-item" />
       </div>
       <!-- 实时数据表格 -->
       <BasicTable @register="registerTable" :scroll="{ x: 'max-content' }" :style="{ padding: 0 }">
@@ -41,16 +34,10 @@
     </TabPane>
   </Tabs>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="height: 400px"
-    v-model:visible="visibleresolveModal"
-    :width="600"
-    centered
-    title="密闭漏风处理情况"
-    @ok="handleOkEdit()"
-    @cancel="handleCancelEdit"
-  >
-    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue" placeholder="请输入解决情况" :rows="4" />
+  <a-modal style="height: 400px" v-model:visible="visibleresolveModal" :width="600" centered title="密闭漏风处理情况"
+    @ok="handleOkEdit()" @cancel="handleCancelEdit">
+    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue"
+      placeholder="请输入解决情况" :rows="4" />
   </a-modal>
 </template>
 
@@ -309,20 +296,30 @@ onUnmounted(() => {
 </script>
 
 <style lang="less" scoped>
+// .board-info {
+//   display: grid;
+//   grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
+//   gap: auto;
+//   justify-content: start;
+//   flex-wrap: wrap;
+//   box-sizing: border-box;
+//   background-color: @white;
+//   padding: 10px;
+//   gap: 100px;
+//   // margin: 0 10px;
+//   margin-bottom: 5px;
+// }
+// .board-item {
+//   box-sizing: border-box;
+// }
 .board-info {
-  display: grid;
-  grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
-  gap: auto;
-  justify-content: start;
+  width: 100%;
+  display: flex;
   flex-wrap: wrap;
-  box-sizing: border-box;
-  background-color: @white;
-  padding: 10px;
-  gap: 100px;
-  // margin: 0 10px;
-  margin-bottom: 5px;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  box-sizing: border-box;
+  margin-right: 80px;
 }
 </style>

+ 25 - 28
src/views/analysis/warningAnalysis/overlimitAlarm/index.vue

@@ -4,15 +4,8 @@
   <Tabs v-model:activeKey="activeTab" type="line" class="common-page-tabs">
     <TabPane tab="实时监测" key="realtime">
       <div class="board-info">
-        <MiniBoard
-          :key="index"
-          v-for="(item, index) in boardData"
-          type="A"
-          :label="item.label"
-          :value="item.value"
-          layout="label-top"
-          class="board-item"
-        />
+        <MiniBoard :key="index" v-for="(item, index) in boardData" type="A" :label="item.label" :value="item.value"
+          layout="label-top" class="board-item" />
       </div>
       <!-- 实时数据表格 -->
       <BasicTable @register="registerTable" :scroll="{ x: 'max-content' }" :style="{ padding: 0 }">
@@ -41,14 +34,8 @@
     </TabPane>
   </Tabs>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="top: 30%; left: 170px"
-    v-model:visible="visibleModal"
-    :width="450"
-    title="实时监测数据"
-    @ok="handleOkEdit"
-    @cancel="handleCancelEdit"
-  >
+  <a-modal style="top: 30%; left: 170px" v-model:visible="visibleModal" :width="450" title="实时监测数据" @ok="handleOkEdit"
+    @cancel="handleCancelEdit">
     <a-table></a-table>
   </a-modal>
 </template>
@@ -317,20 +304,30 @@ onUnmounted(() => {
 </script>
 
 <style lang="less" scoped>
+// .board-info {
+//   display: grid;
+//   grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
+//   gap: auto;
+//   justify-content: start;
+//   flex-wrap: wrap;
+//   box-sizing: border-box;
+//   background-color: @white;
+//   padding: 10px;
+//   gap: 100px;
+//   // margin: 0 10px;
+//   margin-bottom: 5px;
+// }
+// .board-item {
+//   box-sizing: border-box;
+// }
 .board-info {
-  display: grid;
-  grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
-  gap: auto;
-  justify-content: start;
+  width: 100%;
+  display: flex;
   flex-wrap: wrap;
-  box-sizing: border-box;
-  background-color: @white;
-  padding: 10px;
-  gap: 100px;
-  // margin: 0 10px;
-  margin-bottom: 5px;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  box-sizing: border-box;
+  margin-right: 80px;
 }
 </style>

+ 13 - 38
src/views/analysis/warningAnalysis/pressureDiffAnalysis/index.vue

@@ -4,15 +4,8 @@
   <Tabs v-model:activeKey="activeTab" type="line" class="common-page-tabs">
     <TabPane tab="实时监测" key="realtime">
       <div class="board-info">
-        <MiniBoard
-          :key="index"
-          v-for="(item, index) in boardData"
-          type="A"
-          :label="item.label"
-          :value="item.value"
-          layout="label-top"
-          class="board-item"
-        />
+        <MiniBoard :key="index" v-for="(item, index) in boardData" type="A" :label="item.label" :value="item.value"
+          layout="label-top" class="board-item" />
       </div>
       <!-- 实时数据表格 -->
       <BasicTable @register="registerTable" :scroll="{ x: 'max-content' }" :style="{ padding: 0 }">
@@ -45,27 +38,15 @@
     </TabPane>
   </Tabs>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="top: 30%; left: 170px"
-    v-model:visible="visibleModal"
-    :width="450"
-    title="实时监测数据"
-    @ok="handleOkEdit"
-    @cancel="handleCancelEdit"
-  >
+  <a-modal style="top: 30%; left: 170px" v-model:visible="visibleModal" :width="450" title="实时监测数据" @ok="handleOkEdit"
+    @cancel="handleCancelEdit">
     <a-table></a-table>
   </a-modal>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="height: 400px"
-    v-model:visible="visibleresolveModal"
-    :width="600"
-    centered
-    title="密闭漏风处理情况"
-    @ok="handleOkEdit()"
-    @cancel="handleCancelEdit"
-  >
-    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue" placeholder="请输入解决情况" :rows="4" />
+  <a-modal style="height: 400px" v-model:visible="visibleresolveModal" :width="600" centered title="密闭漏风处理情况"
+    @ok="handleOkEdit()" @cancel="handleCancelEdit">
+    <a-textarea style="width: 90%; margin-left: 20px; margin-right: 20px" v-model:value="resolveValue"
+      placeholder="请输入解决情况" :rows="4" />
   </a-modal>
 </template>
 
@@ -335,19 +316,13 @@ onUnmounted(() => {
 
 <style lang="less" scoped>
 .board-info {
-  display: grid;
-  grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
-  gap: auto;
-  justify-content: start;
+  width: 100%;
+  display: flex;
   flex-wrap: wrap;
-  box-sizing: border-box;
-  background-color: @white;
-  padding: 10px;
-  gap: 100px;
-  // margin: 0 10px;
-  margin-bottom: 5px;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  box-sizing: border-box;
+  margin-right: 80px;
 }
 </style>

+ 25 - 28
src/views/analysis/warningAnalysis/sealRiskJudgeAnalysis/index.vue

@@ -4,15 +4,8 @@
   <Tabs v-model:activeKey="activeTab" type="line" class="common-page-tabs">
     <TabPane tab="实时监测" key="realtime">
       <div class="board-info">
-        <MiniBoard
-          :key="index"
-          v-for="(item, index) in boardData"
-          type="A"
-          :label="item.label"
-          :value="item.value"
-          layout="label-top"
-          class="board-item"
-        />
+        <MiniBoard :key="index" v-for="(item, index) in boardData" type="A" :label="item.label" :value="item.value"
+          layout="label-top" class="board-item" />
       </div>
       <!-- 实时数据表格 -->
       <BasicTable @register="registerTable" :scroll="{ x: 'max-content' }" :style="{ padding: 0 }">
@@ -43,14 +36,8 @@
     </TabPane>
   </Tabs>
   <!-- 弹窗组件 -->
-  <a-modal
-    style="top: 30%; left: 170px"
-    v-model:visible="visibleModal"
-    :width="450"
-    title="实时监测数据"
-    @ok="handleOkEdit"
-    @cancel="handleCancelEdit"
-  >
+  <a-modal style="top: 30%; left: 170px" v-model:visible="visibleModal" :width="450" title="实时监测数据" @ok="handleOkEdit"
+    @cancel="handleCancelEdit">
     <a-table></a-table>
   </a-modal>
 </template>
@@ -320,20 +307,30 @@ onUnmounted(() => {
 </script>
 
 <style lang="less" scoped>
+// .board-info {
+//   display: grid;
+//   grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
+//   gap: auto;
+//   justify-content: start;
+//   flex-wrap: wrap;
+//   box-sizing: border-box;
+//   background-color: @white;
+//   padding: 10px;
+//   gap: 100px;
+//   // margin: 0 10px;
+//   margin-bottom: 5px;
+// }
+// .board-item {
+//   box-sizing: border-box;
+// }
 .board-info {
-  display: grid;
-  grid-template-columns: repeat(5, auto); /* 3列:改5则为5列 */
-  gap: auto;
-  justify-content: start;
+  width: 100%;
+  display: flex;
   flex-wrap: wrap;
-  box-sizing: border-box;
-  background-color: @white;
-  padding: 10px;
-  gap: 100px;
-  // margin: 0 10px;
-  margin-bottom: 5px;
+  margin-bottom: 8px;
 }
+
 .board-item {
-  box-sizing: border-box;
+  margin-right: 80px;
 }
 </style>

+ 66 - 68
src/views/dashboard/SealedGoaf/index.vue

@@ -2,85 +2,83 @@
 <template>
   <div class="company-home">
     <!-- 渲染所有模块 -->
-    <ModulePrimary
-      v-for="cfg in cfgs"
-      :key="cfg.deviceType + cfg.moduleName"
-      :show-style="cfg.showStyle"
-      :module-data="cfg.moduleData"
-      :module-name="cfg.moduleName"
-      :device-type="cfg.deviceType"
-      :data="data"
-      :visible="true"
-    />
+    <ModulePrimary v-for="cfg in cfgs" :key="cfg.deviceType + cfg.moduleName" :show-style="cfg.showStyle"
+      :module-data="cfg.moduleData" :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="data"
+      :visible="true" />
   </div>
 </template>
 <script lang="ts" setup>
-  import { computed, onMounted, onUnmounted, ref } from 'vue';
-  import { useInitConfigs, useInitPage } from '@/components/Configurable/hooks/useInit';
-  import { testConfigSealedGoaf } from './configurable.data.sealedGoaf';
-  import { getCoalSeamFireNum, getMineProductionStatusNum, getOverLimitNum, getGoafAlarmNum, getGoafAlarmLevel } from './sealedGoaf.api'
-  import ModulePrimary from '/@/components/Configurable/ModulePrimary.vue';
-  import { useGlobSetting } from '/@/hooks/setting';
+import { computed, onMounted, onUnmounted, ref } from 'vue';
+import { useInitConfigs, useInitPage } from '@/components/Configurable/hooks/useInit';
+import { testConfigSealedGoaf } from './configurable.data.sealedGoaf';
+import { getCoalSeamFireNum, getMineProductionStatusNum, getOverLimitNum, getGoafAlarmNum, getGoafAlarmLevel } from './sealedGoaf.api'
+import ModulePrimary from '/@/components/Configurable/ModulePrimary.vue';
+import { useGlobSetting } from '/@/hooks/setting';
 
-  const { title = '省局采空区密闭监测与分析系统' } = useGlobSetting();
-  const { data, updateData } = useInitPage(title);
+const { title = '采空区密闭监测与分析系统' } = useGlobSetting();
+const { data, updateData } = useInitPage(title);
 
-  const cfgs = computed(() => configs.value);
-  const { configs, fetchConfigs } = useInitConfigs();
-  const mineData = ref({}); // 所有数据汇总
+const cfgs = computed(() => configs.value);
+const { configs, fetchConfigs } = useInitConfigs();
+const mineData = ref({}); // 所有数据汇总
 
-  onMounted(async () => {
-    try {
-      // 1. 先获取基础配置(若有接口获取配置则保留,否则直接用本地testConfigSealedGoaf)
-      await fetchConfigs('sealed_goaf');
-      
-      // 2. 异步获取所有接口数据(并行请求提升性能)
-      const [coalSeamFireData, productionStatusData, overLimitData, goafAlarmData, goafAlarmLevel] = await Promise.all([
-        getCoalSeamFireNum(), // 煤层自燃倾向数据
-        getMineProductionStatusNum(), // 当日生产状态数据
-        getOverLimitNum(), // 超限数据(可按需处理)
-        getGoafAlarmNum({mineCode:'100008'}), // 执法处风险统计
-        getGoafAlarmLevel({mineCode:'100008'}),
-      ]);
+onMounted(async () => {
+  try {
+    // 1. 先获取基础配置(若有接口获取配置则保留,否则直接用本地testConfigSealedGoaf)
+    await fetchConfigs('sealed_goaf');
 
-      // 3. 把接口数据赋值给响应式变量(备用)
-      mineData.value = { coalSeamFireData, productionStatusData, overLimitData, goafAlarmData, goafAlarmLevel };
+    // 2. 异步获取所有接口数据(并行请求提升性能)
+    const [coalSeamFireData, productionStatusData, overLimitData, goafAlarmData, goafAlarmLevel] = await Promise.all([
+      getCoalSeamFireNum(), // 煤层自燃倾向数据
+      getMineProductionStatusNum(), // 当日生产状态数据
+      getOverLimitNum(), // 超限数据(可按需处理)
+      getGoafAlarmNum({ mineCode: '100008' }), // 执法处风险统计
+      getGoafAlarmLevel({ mineCode: '100008' }),
+    ]);
 
-      // 4. 赋值更新后的配置到configs(触发组件重新渲染)
-      configs.value = [...testConfigSealedGoaf]; // 解构触发响应式更新
-      
-      // 5. 更新页面数据
-      updateData(mineData.value);
+    // 3. 把接口数据赋值给响应式变量(备用)
+    mineData.value = { coalSeamFireData, productionStatusData, overLimitData, goafAlarmData, goafAlarmLevel };
 
-    } catch (error) {
-      console.error('数据获取/配置更新失败:', error);
-    }
-  });
+    // 4. 赋值更新后的配置到configs(触发组件重新渲染)
+    configs.value = [...testConfigSealedGoaf]; // 解构触发响应式更新
 
-  // 数据处理函数
-  onUnmounted(() => {});
+    // 5. 更新页面数据
+    updateData(mineData.value);
+
+  } catch (error) {
+    console.error('数据获取/配置更新失败:', error);
+  }
+});
+
+// 数据处理函数
+onUnmounted(() => { });
 </script>
 <style lang="less" scoped>
-  @import '/@/design/theme.less';
+@import '/@/design/theme.less';
 
-  @font-face {
-    font-family: 'douyuFont';
-    src: url('/@/assets/font/douyuFont.otf');
-  }
-  .company-home {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    color: @white;
-    background-image: linear-gradient(90deg, @map-bg 0%, @map-bg 14%, transparent 50%, @map-bg 86%, @map-bg 100%);
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    z-index: @layout-basic-z-index;
-    // 允许点击穿透以支持下面的地图进行交互
-    pointer-events: none;
-  }
-  :deep(.ant-select-selection-item){
-    display: flex;
-    align-items: center;
-  }
+@font-face {
+  font-family: 'douyuFont';
+  src: url('/@/assets/font/douyuFont.otf');
+}
+
+
+
+
+.company-home {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  color: @white;
+  background-image: linear-gradient(90deg, @map-bg 0%, @map-bg 14%, transparent 50%, @map-bg 86%, @map-bg 100%);
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  z-index: @layout-basic-z-index;
+  // 允许点击穿透以支持下面的地图进行交互
+  pointer-events: none;
+}
+
+:deep(.ant-select-selection-item) {
+  display: flex;
+  align-items: center;
+}
 </style>

+ 190 - 86
src/views/dashboard/basicInfo/dataQuality/components/DataQualityModal.vue

@@ -11,70 +11,87 @@
     destroyOnClose
     :bodyStyle="{ padding: '20px' }"
   >
-    <!-- 查看模式 -->
-    <div class="que-container" v-if="mode === 'view'">
-      <div class="que-status">
-        <div>矿井状态:</div>
-        <div>
-          <span :class="getStatusClass(currentRecord?.mineLinkStatus)" class="status-dot">
-            {{ getStatusText(currentRecord?.mineLinkStatus) }}
-          </span>
-        </div>
-      </div>
-      <div 
-        class="que-item" 
-        v-for="(item, index) in queList" 
-        :key="index"
-      >
-        <div class="que-details">
-          <div class="que-field">
-            <span class="que-value que-goafName">{{ item.goafName || '-' }}</span>
-          </div>
-          <div class="que-field">
-            <span class="que-label">问题描述:</span>
-            <span class="que-value">{{ item.queCon || '-' }}</span>
-          </div>
-          <div class="que-field time-field">
-            <span class="que-label">时间:</span>
-            <span class="que-value">{{ formatDate(item.startTime) || '-' }}</span>
-            <span class="time-separator">-----</span>
-            <span class="que-value">{{ formatDate(item.endTime) || '-' }}</span>
+    <a-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      layout="horizontal"
+      :label-col="{ span: 4 }"
+      :wrapper-col="{ span: 20 }"
+    >
+      <!-- 查看模式 -->
+      <div class="que-container" v-if="mode === 'view'">
+        <div class="que-status">
+          <div>矿井状态:</div>
+          <div>
+            <span :class="getStatusClass(currentRecord?.mineLinkStatus)" class="status-dot">
+              {{ getStatusText(currentRecord?.mineLinkStatus) }}
+            </span>
           </div>
-          <div class="que-field">
-            <span class="que-label">参数:</span>
-            <span class="que-value">{{ (item.param || '-').replace(/,/g, ' ') }}</span>
+        </div>
+        <div 
+          class="que-item" 
+          v-for="(item, index) in queList" 
+          :key="index"
+        >
+          <div class="que-details">
+            <div class="que-field">
+              <span class="que-value que-goafName">{{ item.goafName || '-' }}</span>
+            </div>
+            <div class="que-field">
+              <span class="que-label">问题描述:</span>
+              <span class="que-value">{{ item.queCon || '-' }}</span>
+            </div>
+            <div class="que-field time-field">
+              <span class="que-label">时间:</span>
+              <span class="que-value">{{ formatDate(item.startTime) || '-' }}</span>
+              <span class="time-separator">-----</span>
+              <span class="que-value">{{ formatDate(item.endTime) || '-' }}</span>
+            </div>
+            <div class="que-field">
+              <span class="que-label">参数:</span>
+              <span class="que-value">{{ (item.param || '-').replace(/,/g, ' ') }}</span>
+            </div>
           </div>
         </div>
       </div>
-    </div>
-
-    <!-- 编辑/新增模式 -->
-    <div v-else class="edit-container">
-      <!-- 动态渲染topFormSchema字段(编辑/新增模式) -->
-      <div class="mine-base-info" v-if="mode === 'add'">
-        <div class="form-item" v-for="schema in topFormSchema" :key="schema.field">
-          <div class="form-label">{{ schema.label }}:</div>
-          <component 
-            :is="getComponent(schema.component)"
-            v-model:value="currentRecord[schema.field]" 
-            v-bind="schema.componentProps"
-            :placeholder="`请输入${schema.label}`"
-            style="width: 100%"
-          />
+
+      <!-- 编辑/新增模式 -->
+      <div v-else class="edit-container">
+        <!-- 动态渲染topFormSchema字段(编辑/新增模式) -->
+        <div class="mine-base-info" v-if="mode === 'add'">
+          <a-form-item
+            v-for="schema in topFormSchema"
+            :key="schema.field"
+            :name="schema.field"
+            :label="schema.label"
+            :rules="schema.rules"
+          >
+            <component 
+              :is="getComponent(schema.component)"
+              v-model:value="currentRecord[schema.field]" 
+              v-bind="schema.componentProps"
+              :placeholder="`请输入${schema.label}`"
+              style="width: 100%"
+            />
+          </a-form-item>
         </div>
-      </div>
 
-      <!-- 问题项编辑区 -->
-      <div 
-        class="que-item" 
-        v-for="(item, index) in queList" 
-        :key="item.orderNum || index"
-      >
-        <div class="que-index">问题 {{ index + 1 }}</div>
-        <div class="edit-fields">
-          <template v-for="schema in formSchema" :key="schema.field">
-            <div class="form-item">
-              <div class="form-label">{{ schema.label }}:</div>
+        <!-- 问题项编辑区 -->
+        <div 
+          class="que-item" 
+          v-for="(item, index) in queList" 
+          :key="item.orderNum || index"
+        >
+          <div class="que-index">问题 {{ index + 1 }}</div>
+          <div class="edit-fields">
+            <a-form-item
+              v-for="schema in formSchema"
+              :key="`${schema.field}-${index}`"
+              :name="[`queList`, index, schema.field]"
+              :label="schema.label"
+              :rules="schema.rules"
+            >
               <component 
                 :is="getComponent(schema.component)"
                 v-model:value="item[schema.field]"
@@ -82,41 +99,41 @@
                 :placeholder="`请输入${schema.label}`"
                 style="width: 100%"
               />
+            </a-form-item>
+            <div class="form-actions">
+              <a-button 
+                type="text" 
+                danger 
+                @click="removeItem(index)"
+                :disabled="queList.length <= 1"
+              >
+                删除
+              </a-button>
             </div>
-          </template>
-          <div class="form-actions">
-            <a-button 
-              type="text" 
-              danger 
-              @click="removeItem(index)"
-              :disabled="queList.length <= 1"
-            >
-              删除
-            </a-button>
           </div>
         </div>
-      </div>
 
-      <a-button 
-        type="dashed" 
-        class="add-btn"
-        @click="addNewItem"
-      >
-        <plus-outlined /> 新增问题
-      </a-button>
-    </div>
+        <a-button 
+          type="dashed" 
+          class="add-btn"
+          @click="addNewItem"
+        >
+          <plus-outlined /> 新增问题
+        </a-button>
+      </div>
+    </a-form>
   </BasicModal>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue';
+import { ref, computed, reactive, watch } from 'vue';
 import { BasicModal, useModalInner } from '/@/components/Modal';
 import { formSchema, topFormSchema } from '../dataQuality.data';
-import { Button as AButton, Select, Input, DatePicker } from 'ant-design-vue';
+import { Select, Input, DatePicker, message, } from 'ant-design-vue';
 import { PlusOutlined } from '@ant-design/icons-vue';
 import dayjs, { Dayjs } from 'dayjs';
-// 请根据实际项目路径调整 MineCascader 引入路径
 import MineCascader from '/@/components/Form/src/jeecg/components/MineCascader/MineCascader.vue';
+import type { FormInstance, RuleObject } from 'ant-design-vue/es/form';
 
 // 组件映射表
 const componentMap = {
@@ -143,6 +160,75 @@ const currentRecord = ref<any>({
 // 问题列表
 const queList = ref<any[]>([]);
 
+// 表单实例(用于校验)
+const formRef = ref<FormInstance>();
+// 表单数据聚合(适配Form组件的model)
+const formData = reactive({
+  mineCode: '',
+  queList: queList.value
+});
+
+// 合并表单规则(从schema中提取)
+const formRules = reactive({
+  mineCode: topFormSchema.find(item => item.field === 'mineCode')?.rules || [],
+  queList: {
+    type: 'array',
+    required: true,
+    validator: (rule: RuleObject, value: any[], callback: Function) => {
+      // 校验每个问题项
+      if (value.length === 0) {
+        callback(new Error('至少需要填写一条问题信息'));
+        return;
+      }
+      // 逐个校验问题项的必填字段
+      for (let i = 0; i < value.length; i++) {
+        const item = value[i];
+        // 校验工作面名称
+        if (!item.goafName) {
+          callback(new Error(`第${i+1}条问题:工作面名称不能为空`));
+          return;
+        }
+        // 校验问题描述
+        if (!item.queCon) {
+          callback(new Error(`第${i+1}条问题:问题描述不能为空`));
+          return;
+        }
+        // 校验开始时间
+        if (!item.startTime) {
+          callback(new Error(`第${i+1}条问题:开始时间不能为空`));
+          return;
+        }
+        // 校验结束时间
+        if (!item.endTime) {
+          callback(new Error(`第${i+1}条问题:结束时间不能为空`));
+          return;
+        }
+        // 校验结束时间不能早于开始时间
+        if (dayjs(item.endTime).isBefore(dayjs(item.startTime))) {
+          callback(new Error(`第${i+1}条问题:结束时间不能早于开始时间`));
+          return;
+        }
+        // 校验参数
+        if (!item.param) {
+          callback(new Error(`第${i+1}条问题:参数不能为空`));
+          return;
+        }
+      }
+      callback();
+    }
+  }
+});
+
+// 监听queList变化,同步到formData
+watch(queList, (newVal) => {
+  formData.queList = newVal;
+}, { deep: true });
+
+// 监听currentRecord变化,同步mineCode到formData
+watch(() => currentRecord.value.mineCode, (newVal) => {
+  formData.mineCode = newVal;
+});
+
 // 根据组件名获取对应组件
 const getComponent = (componentName: string) => {
   return componentMap[componentName as keyof typeof componentMap];
@@ -179,6 +265,8 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
     updateTime: '',
     createTime: ''
   };
+  // 同步mineCode到formData
+  formData.mineCode = currentRecord.value.mineCode;
   
   // 初始化问题列表
   if (mode.value === 'add') {
@@ -205,6 +293,8 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
         startTime: item.startTime ? dayjs(item.startTime) : null,
         endTime: item.endTime ? dayjs(item.endTime) : null
       }));
+      // 同步到formData
+      formData.queList = queList.value;
     } catch (error) {
       console.error('解析问题数据失败:', error);
       queList.value = [{
@@ -217,6 +307,10 @@ const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data
       }];
     }
   }
+  // 重置表单校验状态
+  if (formRef.value) {
+    formRef.value.resetFields();
+  }
 });
 
 // 模态框标题计算属性
@@ -253,30 +347,40 @@ const removeItem = (index: number) => {
 // 提交表单处理
 async function handleSubmit() {
   try {
+    // 查看模式直接提交
+    if (mode.value === 'view') {
+      closeModal();
+      return;
+    }
+
+    // 1. 表单校验
+    if (!formRef.value) return;
+    const validateResult = await formRef.value.validate();
+    if (!validateResult) return;
+
     setModalProps({ confirmLoading: true });
-    // 1. 处理问题列表数据(空日期字段置空,避免空字符串)
+    // 2. 处理问题列表数据(空日期字段置空,避免空字符串)
     const submitQueList = queList.value.map((item) => ({
       ...item,
       startTime: item.startTime ? item.startTime.format('YYYY-MM-DD HH:mm:ss') : null,
       endTime: item.endTime ? item.endTime.format('YYYY-MM-DD HH:mm:ss') : null,
     }));
-    // 2. 构造完整提交数据(修正 createTime/updateTime 逻辑)
+    // 3. 构造完整提交数据
     const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
     const result = {
       ...currentRecord.value,
       queJson: JSON.stringify(submitQueList),
-      // 新增模式:创建时间=当前时间,状态=未解决;编辑模式:保留原有创建时间
       createTime: mode.value === 'add' ? now : (currentRecord.value.createTime || now),
-      // 无论新增/编辑,更新时间都为当前时间
       updateTime: now,
-      // 新增模式默认未解决,编辑模式保留原有状态
       isOk: mode.value === 'add' ? false : currentRecord.value.isOk,
     };
     console.log('最终提交数据:', result);
     emit('success', result);
     closeModal();
-  } catch (error) {
+  } catch (error: any) {
     console.error('提交失败:', error);
+    // 显示校验错误提示
+    message.error(error.message || '表单校验失败,请检查必填项');
   } finally {
     setModalProps({ confirmLoading: false });
   }

+ 30 - 2
src/views/dashboard/basicInfo/dataQuality/dataQuality.data.ts

@@ -130,26 +130,44 @@ export function getSearchFormSchema(dynamicStatusOptions: Ref<{ label: string; v
   ];
   return searchFormSchema;
 }
-export const topFormSchema: FormSchema[] = [
+export const topFormSchema :FormSchema[] = [
   {
     field: 'mineCode',
     label: '煤矿名称',
     component: 'MineCascader',
+    componentProps: {
+      changeOnSelect: false,
+      initFromStore: false,
+      syncToStore: false
+    },
     required: true,
+    rules: [
+      { required: true, message: '请选择煤矿名称', trigger: 'change' },
+    ],
   },
 ];
 
 /** 弹框表单配置 */
-export const formSchema = [
+export const formSchema :FormSchema[] = [
   {
     field: 'goafName',
     label: '工作面名称',
     component: 'Input',
+    required: true,
+    rules: [
+      { required: true, message: '请输入工作面名称', trigger: 'blur' },
+      { max: 100, message: '工作面名称长度不能超过100个字符', trigger: 'blur' },
+    ],
   },
   {
     field: 'queCon',
     label: '问题描述',
     component: 'Input',
+    required: true,
+    rules: [
+      { required: true, message: '请输入问题描述', trigger: 'blur' },
+      { max: 500, message: '问题描述长度不能超过500个字符', trigger: 'blur' },
+    ],
   },
   {
     field: 'startTime',
@@ -160,6 +178,9 @@ export const formSchema = [
       format: 'YYYY-MM-DD HH:mm:ss',
     },
     required: true,
+    rules: [
+      { required: true, message: '请选择开始时间', trigger: 'change' },
+    ],
   },
   {
     field: 'endTime',
@@ -170,11 +191,18 @@ export const formSchema = [
       format: 'YYYY-MM-DD HH:mm:ss',
     },
     required: true,
+    rules: [
+      { required: true, message: '请选择结束时间', trigger: 'change' },
+    ],
   },
   {
     field: 'param',
     label: '参数',
     component: 'Input',
     required: true,
+    rules: [
+      { required: true, message: '请输入参数', trigger: 'blur' },
+      { max: 200, message: '参数长度不能超过200个字符', trigger: 'blur' },
+    ],
   },
 ];

+ 1 - 1
src/views/demo/charts/china.json

@@ -654,7 +654,7 @@
       "properties": {
         "id": "610000",
         "cp": [108.948024, 34.263161],
-        "name": "陕西",
+        "name": "XX",
         "childNum": 1
       },
       "geometry": {

+ 1 - 1
src/views/demo/charts/data.ts

@@ -105,7 +105,7 @@ export const mapData: any = [
     tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
   },
   {
-    name: '陕西',
+    name: 'XX',
     value: Math.round(Math.random() * 1000),
     tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
   },

+ 68 - 77
src/views/demo/comp/qrcode/index.vue

@@ -10,108 +10,99 @@
       </CollapseContainer>
 
       <CollapseContainer title="配置样式示例" class="text-center mb-6 qrcode-demo-item">
-        <QrCode
-          :value="qrCodeUrl"
-          :options="{
-            color: { dark: '#55D187' },
-          }"
-        />
+        <QrCode :value="qrCodeUrl" :options="{
+          color: { dark: '#55D187' },
+        }" />
       </CollapseContainer>
-
+      <!-- 
       <CollapseContainer title="本地logo示例" class="text-center mb-6 qrcode-demo-item">
         <QrCode :value="qrCodeUrl" :logo="LogoImg" />
-      </CollapseContainer>
+      </CollapseContainer> -->
 
-      <CollapseContainer title="在线logo示例" class="text-center mb-6 qrcode-demo-item">
-        <QrCode
-          :value="qrCodeUrl"
-          logo="http://jeecg.com/images/logo.png"
-          :options="{
-            color: { dark: '#55D187' },
-          }"
-        />
-      </CollapseContainer>
+      <!-- <CollapseContainer title="在线logo示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" logo="http://jeecg.com/images/logo.png" :options="{
+          color: { dark: '#55D187' },
+        }" />
+      </CollapseContainer> -->
 
       <CollapseContainer title="logo配置示例" class="text-center mb-6 qrcode-demo-item">
-        <QrCode
-          :value="qrCodeUrl"
-          :logo="{
-            src: 'http://jeecg.com/images/logo.png',
-            logoSize: 0.2,
-            borderSize: 0.05,
-            borderRadius: 50,
-            bgColor: 'blue',
-          }"
-        />
+        <QrCode :value="qrCodeUrl" :logo="{
+          src: 'http://jeecg.com/images/logo.png',
+          logoSize: 0.2,
+          borderSize: 0.05,
+          borderRadius: 50,
+          bgColor: 'blue',
+        }" />
       </CollapseContainer>
 
-      <CollapseContainer title="下载示例" class="text-center qrcode-demo-item">
+      <!-- <CollapseContainer title="下载示例" class="text-center qrcode-demo-item">
         <QrCode :value="qrCodeUrl" ref="qrRef" :logo="LogoImg" />
         <a-button class="mb-2" type="primary" @click="download"> 下载 </a-button>
         <div class="msg"> (在线logo会导致图片跨域,需要下载图片需要自行解决跨域问题) </div>
-      </CollapseContainer>
+      </CollapseContainer> -->
 
       <CollapseContainer title="配置大小示例" class="text-center qrcode-demo-item">
         <QrCode :value="qrCodeUrl" :width="300" />
       </CollapseContainer>
 
-      <CollapseContainer title="扩展绘制示例" class="text-center qrcode-demo-item">
-        <QrCode :value="qrCodeUrl" :width="200" :options="{ margin: 5 }" ref="qrDiyRef" :logo="LogoImg" @done="onQrcodeDone" />
+      <!-- <CollapseContainer title="扩展绘制示例" class="text-center qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" :width="200" :options="{ margin: 5 }" ref="qrDiyRef" :logo="LogoImg"
+          @done="onQrcodeDone" />
         <a-button class="mb-2" type="primary" @click="downloadDiy"> 下载 </a-button>
         <div class="msg"> 要进行扩展绘制则不能将tag设为img </div>
-      </CollapseContainer>
+      </CollapseContainer> -->
     </div>
   </PageWrapper>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, unref } from 'vue';
-  import { QrCode, QrCodeActionType } from '/@/components/Qrcode/index';
-  import LogoImg from '/@/assets/images/logo.png';
-  import { CollapseContainer } from '/@/components/Container/index';
-  import { PageWrapper } from '/@/components/Page';
+import { defineComponent, ref, unref } from 'vue';
+import { QrCode, QrCodeActionType } from '/@/components/Qrcode/index';
+// import LogoImg from '/@/assets/images/logo.png';
+import { CollapseContainer } from '/@/components/Container/index';
+import { PageWrapper } from '/@/components/Page';
 
-  const qrCodeUrl = 'https://www.vvbin.cn';
-  export default defineComponent({
-    components: { CollapseContainer, QrCode, PageWrapper },
-    setup() {
-      const qrRef = ref<Nullable<QrCodeActionType>>(null);
-      const qrDiyRef = ref<Nullable<QrCodeActionType>>(null);
-      function download() {
-        const qrEl = unref(qrRef);
-        if (!qrEl) return;
-        qrEl.download('文件名');
-      }
-      function downloadDiy() {
-        const qrEl = unref(qrDiyRef);
-        if (!qrEl) return;
-        qrEl.download('Qrcode');
-      }
+const qrCodeUrl = 'https://www.vvbin.cn';
+export default defineComponent({
+  components: { CollapseContainer, QrCode, PageWrapper },
+  setup() {
+    const qrRef = ref<Nullable<QrCodeActionType>>(null);
+    const qrDiyRef = ref<Nullable<QrCodeActionType>>(null);
+    function download() {
+      const qrEl = unref(qrRef);
+      if (!qrEl) return;
+      qrEl.download('文件名');
+    }
+    function downloadDiy() {
+      const qrEl = unref(qrDiyRef);
+      if (!qrEl) return;
+      qrEl.download('Qrcode');
+    }
 
-      function onQrcodeDone({ ctx }: any) {
-        if (ctx instanceof CanvasRenderingContext2D) {
-          // 额外绘制
-          ctx.fillStyle = 'black';
-          ctx.font = '16px "微软雅黑"';
-          ctx.textBaseline = 'bottom';
-          ctx.textAlign = 'center';
-          ctx.fillText('你帅你先扫', 100, 195, 200);
-        }
+    function onQrcodeDone({ ctx }: any) {
+      if (ctx instanceof CanvasRenderingContext2D) {
+        // 额外绘制
+        ctx.fillStyle = 'black';
+        ctx.font = '16px "微软雅黑"';
+        ctx.textBaseline = 'bottom';
+        ctx.textAlign = 'center';
+        ctx.fillText('你帅你先扫', 100, 195, 200);
       }
-      return {
-        onQrcodeDone,
-        qrCodeUrl,
-        LogoImg,
-        download,
-        downloadDiy,
-        qrRef,
-        qrDiyRef,
-      };
-    },
-  });
+    }
+    return {
+      onQrcodeDone,
+      qrCodeUrl,
+      // LogoImg,
+      download,
+      downloadDiy,
+      qrRef,
+      qrDiyRef,
+    };
+  },
+});
 </script>
 <style scoped>
-  .qrcode-demo-item {
-    width: 30%;
-    margin-right: 1%;
-  }
+.qrcode-demo-item {
+  width: 30%;
+  margin-right: 1%;
+}
 </style>

+ 193 - 189
src/views/sys/login/TokenLoginPage.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="app-loading">
     <div class="app-loading-wrap">
-      <img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
+      <!-- <img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" /> -->
       <div class="app-loading-dots">
         <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
       </div>
@@ -11,204 +11,208 @@
 </template>
 
 <script lang="ts">
-  /**
-   * 地址中携带token,跳转至此页面进行登录操作
-   */
-  import { useRoute, useRouter } from 'vue-router';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { useUserStore } from '/@/store/modules/user';
-  import { useI18n } from '/@/hooks/web/useI18n';
-
-  export default {
-    name: 'TokenLogin',
-    setup() {
-      const route = useRoute();
-      let router = useRouter();
-      const { createMessage, notification } = useMessage();
-      const { t } = useI18n();
-      const routeQuery: any = route.query;
-      if (!routeQuery) {
-        createMessage.warning('参数无效');
-      }
+/**
+ * 地址中携带token,跳转至此页面进行登录操作
+ */
+import { useRoute, useRouter } from 'vue-router';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { useUserStore } from '/@/store/modules/user';
+import { useI18n } from '/@/hooks/web/useI18n';
+
+export default {
+  name: 'TokenLogin',
+  setup() {
+    const route = useRoute();
+    let router = useRouter();
+    const { createMessage, notification } = useMessage();
+    const { t } = useI18n();
+    const routeQuery: any = route.query;
+    if (!routeQuery) {
+      createMessage.warning('参数无效');
+    }
 
-      const token = routeQuery['loginToken'];
-      if (!token) {
-        createMessage.warning('token无效');
+    const token = routeQuery['loginToken'];
+    if (!token) {
+      createMessage.warning('token无效');
+    }
+    const userStore = useUserStore();
+    userStore.ThirdLogin({ token, thirdType: 'email', goHome: false }).then((res) => {
+      console.log('res====>doThirdLogin', res);
+      if (res && res.userInfo) {
+        requestSuccess(res);
+      } else {
+        requestFailed(res);
       }
-      const userStore = useUserStore();
-      userStore.ThirdLogin({ token, thirdType: 'email', goHome: false }).then((res) => {
-        console.log('res====>doThirdLogin', res);
-        if (res && res.userInfo) {
-          requestSuccess(res);
+    });
+
+    function requestFailed(err) {
+      notification.error({
+        message: '登录失败',
+        description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试',
+        duration: 4,
+      });
+    }
+
+    function requestSuccess(res) {
+      let info = routeQuery.info;
+      if (info) {
+        let query = JSON.parse(info);
+
+        // 代码逻辑说明: QQYUN-4882【简流】节点消息通知 邮箱 点击办理跳到了应用首页
+        let path = '';
+        if (query.isLowApp === 1) {
+          path = '/myapps/personalOffice/myTodo';
         } else {
-          requestFailed(res);
+          let taskId = query.taskId;
+          path = '/task/handle/' + taskId;
         }
-      });
 
-      function requestFailed(err) {
+        router.replace({ path, query });
+        notification.success({
+          message: t('sys.login.loginSuccessTitle'),
+          description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`,
+          duration: 3,
+        });
+      } else {
         notification.error({
-          message: '登录失败',
-          description: ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试',
+          message: '参数失效',
+          description: '页面跳转参数丢失,请查看日志',
           duration: 4,
         });
       }
-
-      function requestSuccess(res) {
-        let info = routeQuery.info;
-        if (info) {
-          let query = JSON.parse(info);
-
-          // 代码逻辑说明: QQYUN-4882【简流】节点消息通知 邮箱 点击办理跳到了应用首页
-          let path = '';
-          if (query.isLowApp === 1) {
-            path = '/myapps/personalOffice/myTodo';
-          } else {
-            let taskId = query.taskId;
-            path = '/task/handle/' + taskId;
-          }
-
-          router.replace({ path, query });
-          notification.success({
-            message: t('sys.login.loginSuccessTitle'),
-            description: `${t('sys.login.loginSuccessDesc')}: ${res.userInfo.realname}`,
-            duration: 3,
-          });
-        } else {
-          notification.error({
-            message: '参数失效',
-            description: '页面跳转参数丢失,请查看日志',
-            duration: 4,
-          });
-        }
-      }
-    },
-  };
+    }
+  },
+};
 </script>
 
 <style scoped>
-  html[data-theme='dark'] .app-loading {
-    background-color: #2c344a;
-  }
-
-  html[data-theme='dark'] .app-loading .app-loading-title {
-    color: rgba(255, 255, 255, 0.85);
-  }
-
-  .app-loading {
-    display: flex;
-    width: 100%;
-    height: 100%;
-    justify-content: center;
-    align-items: center;
-    flex-direction: column;
-    background-color: #f4f7f9;
-  }
-
-  .app-loading .app-loading-wrap {
-    position: absolute;
-    top: 50%;
-    left: 50%;
-    display: flex;
-    -webkit-transform: translate3d(-50%, -50%, 0);
-    transform: translate3d(-50%, -50%, 0);
-    justify-content: center;
-    align-items: center;
-    flex-direction: column;
-  }
-
-  .app-loading .dots {
-    display: flex;
-    padding: 98px;
-    justify-content: center;
-    align-items: center;
-  }
-
-  .app-loading .app-loading-title {
-    display: flex;
-    margin-top: 30px;
-    font-size: 30px;
-    color: rgba(0, 0, 0, 0.85);
-    justify-content: center;
-    align-items: center;
-  }
-
-  .app-loading .app-loading-logo {
-    display: block;
-    width: 90px;
-    margin: 0 auto;
-    margin-bottom: 20px;
-  }
-
-  .dot {
-    position: relative;
-    display: inline-block;
-    width: 48px;
-    height: 48px;
-    margin-top: 30px;
-    font-size: 32px;
-    transform: rotate(45deg);
-    box-sizing: border-box;
-    animation: antRotate 1.2s infinite linear;
-  }
-
-  .dot i {
-    position: absolute;
-    display: block;
-    width: 20px;
-    height: 20px;
-    background-color: #0065cc;
-    border-radius: 100%;
-    opacity: 0.3;
-    transform: scale(0.75);
-    animation: antSpinMove 1s infinite linear alternate;
-    transform-origin: 50% 50%;
-  }
-
-  .dot i:nth-child(1) {
-    top: 0;
-    left: 0;
-  }
-
-  .dot i:nth-child(2) {
-    top: 0;
-    right: 0;
-    -webkit-animation-delay: 0.4s;
-    animation-delay: 0.4s;
-  }
-
-  .dot i:nth-child(3) {
-    right: 0;
-    bottom: 0;
-    -webkit-animation-delay: 0.8s;
-    animation-delay: 0.8s;
-  }
-
-  .dot i:nth-child(4) {
-    bottom: 0;
-    left: 0;
-    -webkit-animation-delay: 1.2s;
-    animation-delay: 1.2s;
-  }
-  @keyframes antRotate {
-    to {
-      -webkit-transform: rotate(405deg);
-      transform: rotate(405deg);
-    }
-  }
-  @-webkit-keyframes antRotate {
-    to {
-      -webkit-transform: rotate(405deg);
-      transform: rotate(405deg);
-    }
-  }
-  @keyframes antSpinMove {
-    to {
-      opacity: 1;
-    }
-  }
-  @-webkit-keyframes antSpinMove {
-    to {
-      opacity: 1;
-    }
-  }
+html[data-theme='dark'] .app-loading {
+  background-color: #2c344a;
+}
+
+html[data-theme='dark'] .app-loading .app-loading-title {
+  color: rgba(255, 255, 255, 0.85);
+}
+
+.app-loading {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  background-color: #f4f7f9;
+}
+
+.app-loading .app-loading-wrap {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  display: flex;
+  -webkit-transform: translate3d(-50%, -50%, 0);
+  transform: translate3d(-50%, -50%, 0);
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+
+.app-loading .dots {
+  display: flex;
+  padding: 98px;
+  justify-content: center;
+  align-items: center;
+}
+
+.app-loading .app-loading-title {
+  display: flex;
+  margin-top: 30px;
+  font-size: 30px;
+  color: rgba(0, 0, 0, 0.85);
+  justify-content: center;
+  align-items: center;
+}
+
+.app-loading .app-loading-logo {
+  display: block;
+  width: 90px;
+  margin: 0 auto;
+  margin-bottom: 20px;
+}
+
+.dot {
+  position: relative;
+  display: inline-block;
+  width: 48px;
+  height: 48px;
+  margin-top: 30px;
+  font-size: 32px;
+  transform: rotate(45deg);
+  box-sizing: border-box;
+  animation: antRotate 1.2s infinite linear;
+}
+
+.dot i {
+  position: absolute;
+  display: block;
+  width: 20px;
+  height: 20px;
+  background-color: #0065cc;
+  border-radius: 100%;
+  opacity: 0.3;
+  transform: scale(0.75);
+  animation: antSpinMove 1s infinite linear alternate;
+  transform-origin: 50% 50%;
+}
+
+.dot i:nth-child(1) {
+  top: 0;
+  left: 0;
+}
+
+.dot i:nth-child(2) {
+  top: 0;
+  right: 0;
+  -webkit-animation-delay: 0.4s;
+  animation-delay: 0.4s;
+}
+
+.dot i:nth-child(3) {
+  right: 0;
+  bottom: 0;
+  -webkit-animation-delay: 0.8s;
+  animation-delay: 0.8s;
+}
+
+.dot i:nth-child(4) {
+  bottom: 0;
+  left: 0;
+  -webkit-animation-delay: 1.2s;
+  animation-delay: 1.2s;
+}
+
+@keyframes antRotate {
+  to {
+    -webkit-transform: rotate(405deg);
+    transform: rotate(405deg);
+  }
+}
+
+@-webkit-keyframes antRotate {
+  to {
+    -webkit-transform: rotate(405deg);
+    transform: rotate(405deg);
+  }
+}
+
+@keyframes antSpinMove {
+  to {
+    opacity: 1;
+  }
+}
+
+@-webkit-keyframes antSpinMove {
+  to {
+    opacity: 1;
+  }
+}
 </style>

+ 1 - 1
src/views/system/loginmini/MiniCodelogin.vue

@@ -45,7 +45,7 @@ import { getLoginQrcode, getQrcodeToken } from '/@/api/sys/user';
 import { useUserStore } from '/@/store/modules/user';
 import { QrCode } from '/@/components/Qrcode/index';
 import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
-import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
+// import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
 import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
 import { useI18n } from '/@/hooks/web/useI18n';
 import { useDesign } from '/@/hooks/web/useDesign';

+ 186 - 183
src/views/system/loginmini/MiniForgotpad.vue

@@ -42,7 +42,8 @@
                     <a-form-item>
                       <a-input type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
                     </a-form-item>
-                    <div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{ t('component.countdown.normalText') }}</div>
+                    <div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{
+                      t('component.countdown.normalText') }}</div>
                     <div v-else class="aui-code-line">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</div>
                   </div>
                 </div>
@@ -53,12 +54,14 @@
                 <div class="aui-account aui-account-line aui-forgot">
                   <a-form-item>
                     <div class="aui-input-line">
-                      <a-input type="password" :placeholder="t('sys.login.passwordPlaceholder')" v-model:value="pwdFormData.password" />
+                      <a-input type="password" :placeholder="t('sys.login.passwordPlaceholder')"
+                        v-model:value="pwdFormData.password" />
                     </div>
                   </a-form-item>
                   <a-form-item>
                     <div class="aui-input-line">
-                      <a-input type="password" :placeholder="t('sys.login.confirmPassword')" v-model:value="pwdFormData.confirmPassword" />
+                      <a-input type="password" :placeholder="t('sys.login.confirmPassword')"
+                        v-model:value="pwdFormData.confirmPassword" />
                     </div>
                   </a-form-item>
                 </div>
@@ -93,201 +96,201 @@
   <CaptchaModal @register="captchaRegisterModal" @ok="getLoginCode" />
 </template>
 <script lang="ts" name="mini-forgotpad" setup>
-  import { reactive, ref, toRaw, unref } from 'vue';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { SmsEnum, useFormRules, useFormValid, useLoginState } from '/@/views/sys/login/useLogin';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { getCaptcha, passwordChange, phoneVerify } from '/@/api/sys/user';
-  import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
-  import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
-  import successImg from '/@/assets/loginmini/icon/icon-success.png';
-  import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
-  import { useModal } from '@/components/Modal';
-  import { ExceptionEnum } from '@/enums/exceptionEnum';
-  const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
+import { reactive, ref, toRaw, unref } from 'vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { SmsEnum, useFormRules, useFormValid, useLoginState } from '/@/views/sys/login/useLogin';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { getCaptcha, passwordChange, phoneVerify } from '/@/api/sys/user';
+// import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
+import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
+import successImg from '/@/assets/loginmini/icon/icon-success.png';
+import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
+import { useModal } from '@/components/Modal';
+import { ExceptionEnum } from '@/enums/exceptionEnum';
+const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
 
-  //下一步控制
-  const activeKey = ref<number>(1);
-  const { t } = useI18n();
-  const { handleBackLogin } = useLoginState();
-  const { notification, createMessage, createErrorModal } = useMessage();
-  //是否显示获取验证码
-  const showInterval = ref<boolean>(true);
-  //60s
-  const timeRuning = ref<number>(60);
-  //定时器
-  const timer = ref<any>(null);
-  const formRef = ref();
-  const pwdFormRef = ref();
-  //账号数据
-  const accountInfo = reactive<any>({});
-  //手机号表单
-  const formData = reactive({
-    mobile: '',
-    smscode: '',
-  });
-  //密码表单
-  const pwdFormData = reactive<any>({
-    password: '',
-    confirmPassword: '',
-  });
-  const emit = defineEmits(['go-back', 'success', 'register']);
+//下一步控制
+const activeKey = ref<number>(1);
+const { t } = useI18n();
+const { handleBackLogin } = useLoginState();
+const { notification, createMessage, createErrorModal } = useMessage();
+//是否显示获取验证码
+const showInterval = ref<boolean>(true);
+//60s
+const timeRuning = ref<number>(60);
+//定时器
+const timer = ref<any>(null);
+const formRef = ref();
+const pwdFormRef = ref();
+//账号数据
+const accountInfo = reactive<any>({});
+//手机号表单
+const formData = reactive({
+  mobile: '',
+  smscode: '',
+});
+//密码表单
+const pwdFormData = reactive<any>({
+  password: '',
+  confirmPassword: '',
+});
+const emit = defineEmits(['go-back', 'success', 'register']);
 
-  /**
-   * 下一步
-   */
-  async function handleNext() {
-    if (!formData.mobile) {
-      createMessage.warn(t('sys.login.mobilePlaceholder'));
-      return;
-    }
-    if (!formData.smscode) {
-      createMessage.warn(t('sys.login.smsPlaceholder'));
-      return;
-    }
-    const resultInfo = await phoneVerify(
-      toRaw({
-        phone: formData.mobile,
-        smscode: formData.smscode,
-      })
-    );
-    if (resultInfo.success) {
-      Object.assign(accountInfo, {
-        username: resultInfo.result.username,
-        phone: formData.mobile,
-        smscode: formData.smscode,
-      });
-      activeKey.value = 2;
-      setTimeout(() => {
-        pwdFormRef.value.resetFields();
-      }, 300);
-    } else {
-      notification.error({
-        message: '错误提示',
-        description: resultInfo.message || t('sys.api.networkExceptionMsg'),
-        duration: 3,
-      });
-    }
+/**
+ * 下一步
+ */
+async function handleNext() {
+  if (!formData.mobile) {
+    createMessage.warn(t('sys.login.mobilePlaceholder'));
+    return;
   }
-
-  /**
-   * 完成修改密码
-   */
-  async function finishedPwd() {
-    if (!pwdFormData.password) {
-      createMessage.warn(t('sys.login.passwordPlaceholder'));
-      return;
-    }
-    if (!pwdFormData.confirmPassword) {
-      createMessage.warn(t('sys.login.confirmPassword'));
-      return;
-    }
-    if (pwdFormData.password !== pwdFormData.confirmPassword) {
-      createMessage.warn(t('sys.login.diffPwd'));
-      return;
-    }
-    const resultInfo = await passwordChange(
-      toRaw({
-        username: accountInfo.username,
-        password: pwdFormData.password,
-        smscode: accountInfo.smscode,
-        phone: accountInfo.phone,
-      })
-    );
-    if (resultInfo.success) {
-      accountInfo.password = pwdFormData.password;
-      //修改密码
-      activeKey.value = 3;
-    } else {
-      //错误提示
-      createErrorModal({
-        title: t('sys.api.errorTip'),
-        content: resultInfo.message || t('sys.api.networkExceptionMsg'),
-      });
-    }
+  if (!formData.smscode) {
+    createMessage.warn(t('sys.login.smsPlaceholder'));
+    return;
   }
-  /**
-   * 下一步
-   */
-  function nextStepClick() {
-    if (unref(activeKey) == 1) {
-      handleNext();
-    } else if (unref(activeKey) == 2) {
-      finishedPwd();
-    }
+  const resultInfo = await phoneVerify(
+    toRaw({
+      phone: formData.mobile,
+      smscode: formData.smscode,
+    })
+  );
+  if (resultInfo.success) {
+    Object.assign(accountInfo, {
+      username: resultInfo.result.username,
+      phone: formData.mobile,
+      smscode: formData.smscode,
+    });
+    activeKey.value = 2;
+    setTimeout(() => {
+      pwdFormRef.value.resetFields();
+    }, 300);
+  } else {
+    notification.error({
+      message: '错误提示',
+      description: resultInfo.message || t('sys.api.networkExceptionMsg'),
+      duration: 3,
+    });
   }
+}
 
-  /**
-   * 去登录
-   */
-  function toLogin() {
-    emit('success', { username: accountInfo.username, password: accountInfo.password });
-    initForm();
+/**
+ * 完成修改密码
+ */
+async function finishedPwd() {
+  if (!pwdFormData.password) {
+    createMessage.warn(t('sys.login.passwordPlaceholder'));
+    return;
   }
-
-  /**
-   * 返回
-   */
-  function goBack() {
-    emit('go-back');
-    initForm();
+  if (!pwdFormData.confirmPassword) {
+    createMessage.warn(t('sys.login.confirmPassword'));
+    return;
+  }
+  if (pwdFormData.password !== pwdFormData.confirmPassword) {
+    createMessage.warn(t('sys.login.diffPwd'));
+    return;
+  }
+  const resultInfo = await passwordChange(
+    toRaw({
+      username: accountInfo.username,
+      password: pwdFormData.password,
+      smscode: accountInfo.smscode,
+      phone: accountInfo.phone,
+    })
+  );
+  if (resultInfo.success) {
+    accountInfo.password = pwdFormData.password;
+    //修改密码
+    activeKey.value = 3;
+  } else {
+    //错误提示
+    createErrorModal({
+      title: t('sys.api.errorTip'),
+      content: resultInfo.message || t('sys.api.networkExceptionMsg'),
+    });
   }
+}
+/**
+ * 下一步
+ */
+function nextStepClick() {
+  if (unref(activeKey) == 1) {
+    handleNext();
+  } else if (unref(activeKey) == 2) {
+    finishedPwd();
+  }
+}
+
+/**
+ * 去登录
+ */
+function toLogin() {
+  emit('success', { username: accountInfo.username, password: accountInfo.password });
+  initForm();
+}
+
+/**
+ * 返回
+ */
+function goBack() {
+  emit('go-back');
+  initForm();
+}
 
-  /**
-   * 获取手机验证码
-   */
-  async function getLoginCode() {
-    if (!formData.mobile) {
-      createMessage.warn(t('sys.login.mobilePlaceholder'));
-      return;
+/**
+ * 获取手机验证码
+ */
+async function getLoginCode() {
+  if (!formData.mobile) {
+    createMessage.warn(t('sys.login.mobilePlaceholder'));
+    return;
+  }
+  // 代码逻辑说明: 【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
+  const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD }).catch((res) => {
+    if (res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE) {
+      openCaptchaModal(true, {});
     }
-    // 代码逻辑说明: 【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
-    const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD }).catch((res) => {
-      if (res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE) {
-        openCaptchaModal(true, {});
-      }
-    });
-    if (result) {
-      const TIME_COUNT = 60;
-      if (!unref(timer)) {
-        timeRuning.value = TIME_COUNT;
-        showInterval.value = false;
-        timer.value = setInterval(() => {
-          if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
-            timeRuning.value = timeRuning.value - 1;
-          } else {
-            showInterval.value = true;
-            clearInterval(unref(timer));
-            timer.value = null;
-          }
-        }, 1000);
-      }
+  });
+  if (result) {
+    const TIME_COUNT = 60;
+    if (!unref(timer)) {
+      timeRuning.value = TIME_COUNT;
+      showInterval.value = false;
+      timer.value = setInterval(() => {
+        if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
+          timeRuning.value = timeRuning.value - 1;
+        } else {
+          showInterval.value = true;
+          clearInterval(unref(timer));
+          timer.value = null;
+        }
+      }, 1000);
     }
   }
+}
 
-  /**
-   * 初始化表单
-   */
-  function initForm() {
-    activeKey.value = 1;
-    Object.assign(formData, { phone: '', smscode: '' });
-    Object.assign(pwdFormData, { password: '', confirmPassword: '' });
-    Object.assign(accountInfo, {});
-    if (unref(timer)) {
-      clearInterval(unref(timer));
-      timer.value = null;
-      showInterval.value = true;
-    }
-    setTimeout(() => {
-      formRef.value.resetFields();
-    }, 300);
+/**
+ * 初始化表单
+ */
+function initForm() {
+  activeKey.value = 1;
+  Object.assign(formData, { phone: '', smscode: '' });
+  Object.assign(pwdFormData, { password: '', confirmPassword: '' });
+  Object.assign(accountInfo, {});
+  if (unref(timer)) {
+    clearInterval(unref(timer));
+    timer.value = null;
+    showInterval.value = true;
   }
+  setTimeout(() => {
+    formRef.value.resetFields();
+  }, 300);
+}
 
-  defineExpose({
-    initForm,
-  });
+defineExpose({
+  initForm,
+});
 </script>
 <style lang="less" scoped>
-  @import '/@/assets/loginmini/style/home.less';
-  @import '/@/assets/loginmini/style/base.less';
+@import '/@/assets/loginmini/style/home.less';
+@import '/@/assets/loginmini/style/base.less';
 </style>

+ 1 - 1
src/views/system/loginmini/MiniLogin.vue

@@ -141,7 +141,7 @@ import ThirdModal from '/@/views/sys/login/ThirdModal.vue';
 import MiniForgotpad from './MiniForgotpad.vue';
 import MiniRegister from './MiniRegister.vue';
 import MiniCodelogin from './MiniCodelogin.vue';
-import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
+// import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
 import adTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
 import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
 import { useLocaleStore } from '/@/store/modules/locale';

+ 188 - 187
src/views/system/loginmini/MiniRegister.vue

@@ -18,32 +18,33 @@
                   <a-form-item>
                     <div class="aui-input-line">
                       <Icon class="aui-icon" icon="ant-design:user-outlined" />
-                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.userName')" v-model:value="formData.username" />
+                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.userName')"
+                        v-model:value="formData.username" />
                     </div>
                   </a-form-item>
                   <a-form-item>
                     <div class="aui-input-line">
                       <Icon class="aui-icon" icon="ant-design:mobile-outlined" />
-                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.mobile')" v-model:value="formData.mobile" />
+                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.mobile')"
+                        v-model:value="formData.mobile" />
                     </div>
                   </a-form-item>
                   <a-form-item>
                     <div class="aui-input-line">
                       <Icon class="aui-icon" icon="ant-design:mail-outlined" />
-                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.smsCode')" v-model:value="formData.smscode" />
-                      <div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{ t('component.countdown.normalText') }}</div>
-                      <div v-else class="aui-code-line">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</div>
+                      <a-input class="fix-auto-fill" type="text" :placeholder="t('sys.login.smsCode')"
+                        v-model:value="formData.smscode" />
+                      <div v-if="showInterval" class="aui-code-line" @click="getLoginCode">{{
+                        t('component.countdown.normalText') }}</div>
+                      <div v-else class="aui-code-line">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}
+                      </div>
                     </div>
                   </a-form-item>
                   <a-form-item>
                     <div class="aui-input-line">
                       <Icon class="aui-icon" icon="ant-design:lock-outlined" />
-                      <a-input
-                        class="fix-auto-fill"
-                        :type="pwdIndex === 'close' ? 'password' : 'text'"
-                        :placeholder="t('sys.login.password')"
-                        v-model:value="formData.password"
-                      />
+                      <a-input class="fix-auto-fill" :type="pwdIndex === 'close' ? 'password' : 'text'"
+                        :placeholder="t('sys.login.password')" v-model:value="formData.password" />
                       <div class="aui-eye">
                         <img :src="eyeKImg" alt="开启" v-if="pwdIndex === 'open'" @click="pwdClick('close')" />
                         <img :src="eyeGImg" alt="关闭" v-else-if="pwdIndex === 'close'" @click="pwdClick('open')" />
@@ -53,15 +54,13 @@
                   <a-form-item>
                     <div class="aui-input-line">
                       <Icon class="aui-icon" icon="ant-design:lock-outlined" />
-                      <a-input
-                        class="fix-auto-fill"
-                        :type="confirmPwdIndex === 'close' ? 'password' : 'text'"
-                        :placeholder="t('sys.login.confirmPassword')"
-                        v-model:value="formData.confirmPassword"
-                      />
+                      <a-input class="fix-auto-fill" :type="confirmPwdIndex === 'close' ? 'password' : 'text'"
+                        :placeholder="t('sys.login.confirmPassword')" v-model:value="formData.confirmPassword" />
                       <div class="aui-eye">
-                        <img :src="eyeKImg" alt="开启" v-if="confirmPwdIndex === 'open'" @click="confirmPwdClick('close')" />
-                        <img :src="eyeGImg" alt="关闭" v-else-if="confirmPwdIndex === 'close'" @click="confirmPwdClick('open')" />
+                        <img :src="eyeKImg" alt="开启" v-if="confirmPwdIndex === 'open'"
+                          @click="confirmPwdClick('close')" />
+                        <img :src="eyeGImg" alt="关闭" v-else-if="confirmPwdIndex === 'close'"
+                          @click="confirmPwdClick('open')" />
                       </div>
                     </div>
                   </a-form-item>
@@ -79,7 +78,8 @@
               </div>
               <div class="aui-formButton">
                 <div class="aui-flex">
-                  <a class="aui-link-login aui-flex-box" @click="registerHandleClick"> {{ t('sys.login.registerButton') }}</a>
+                  <a class="aui-link-login aui-flex-box" @click="registerHandleClick"> {{ t('sys.login.registerButton')
+                  }}</a>
                 </div>
                 <div class="aui-flex">
                   <a class="aui-linek-code aui-flex-box" @click="goBackHandleClick">{{ t('sys.login.backSignIn') }}</a>
@@ -96,192 +96,193 @@
 </template>
 
 <script lang="ts" setup name="mini-register">
-  import { ref, reactive, unref, toRaw } from 'vue';
-  import { getCaptcha, register } from '/@/api/sys/user';
-  import { SmsEnum } from '/@/views/sys/login/useLogin';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
-  import jeecgAdTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
-  import eyeKImg from '/@/assets/loginmini/icon/icon-eye-k.png';
-  import eyeGImg from '/@/assets/loginmini/icon/icon-eye-g.png';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
-  import { useModal } from '@/components/Modal';
-  import { ExceptionEnum } from '@/enums/exceptionEnum';
+import { ref, reactive, unref, toRaw } from 'vue';
+import { getCaptcha, register } from '/@/api/sys/user';
+import { SmsEnum } from '/@/views/sys/login/useLogin';
+import { useMessage } from '/@/hooks/web/useMessage';
+// import logoImg from '/@/assets/loginmini/icon/jeecg_logo.png';
+import jeecgAdTextImg from '/@/assets/loginmini/icon/jeecg_ad_text.png';
+import eyeKImg from '/@/assets/loginmini/icon/icon-eye-k.png';
+import eyeGImg from '/@/assets/loginmini/icon/icon-eye-g.png';
+import { useI18n } from '/@/hooks/web/useI18n';
+import CaptchaModal from '@/components/jeecg/captcha/CaptchaModal.vue';
+import { useModal } from '@/components/Modal';
+import { ExceptionEnum } from '@/enums/exceptionEnum';
 
-  const { t } = useI18n();
-  const { notification, createErrorModal, createMessage } = useMessage();
-  const emit = defineEmits(['go-back', 'success', 'register']);
-  const formRef = ref();
-  const formData = reactive<any>({
-    username: '',
-    mobile: '',
-    smscode: '',
-    password: '',
-    confirmPassword: '',
-    policy: false,
-  });
-  //是否显示获取验证码
-  const showInterval = ref<boolean>(true);
-  //60s
-  const timeRuning = ref<number>(60);
-  //定时器
-  const timer = ref<any>(null);
-  //密码眼睛打开关闭
-  const pwdIndex = ref<string>('close');
-  //确认密码眼睛打开关闭
-  const confirmPwdIndex = ref<string>('close');
-  const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
+const { t } = useI18n();
+const { notification, createErrorModal, createMessage } = useMessage();
+const emit = defineEmits(['go-back', 'success', 'register']);
+const formRef = ref();
+const formData = reactive<any>({
+  username: '',
+  mobile: '',
+  smscode: '',
+  password: '',
+  confirmPassword: '',
+  policy: false,
+});
+//是否显示获取验证码
+const showInterval = ref<boolean>(true);
+//60s
+const timeRuning = ref<number>(60);
+//定时器
+const timer = ref<any>(null);
+//密码眼睛打开关闭
+const pwdIndex = ref<string>('close');
+//确认密码眼睛打开关闭
+const confirmPwdIndex = ref<string>('close');
+const [captchaRegisterModal, { openModal: openCaptchaModal }] = useModal();
 
-  /**
-   * 返回
-   */
-  function goBackHandleClick() {
-    emit('go-back');
-    initForm();
-  }
+/**
+ * 返回
+ */
+function goBackHandleClick() {
+  emit('go-back');
+  initForm();
+}
 
-  /**
-   * 获取手机验证码
-   */
-  async function getLoginCode() {
-    if (!formData.mobile) {
-      createMessage.warn(t('sys.login.mobilePlaceholder'));
-      return;
+/**
+ * 获取手机验证码
+ */
+async function getLoginCode() {
+  if (!formData.mobile) {
+    createMessage.warn(t('sys.login.mobilePlaceholder'));
+    return;
+  }
+  // 代码逻辑说明: 【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
+  const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.REGISTER }).catch((res) => {
+    if (res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE) {
+      openCaptchaModal(true, {});
     }
-    // 代码逻辑说明: 【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
-    const result = await getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.REGISTER }).catch((res) => {
-      if (res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE) {
-        openCaptchaModal(true, {});
-      }
-    });
-    if (result) {
-      const TIME_COUNT = 60;
-      if (!unref(timer)) {
-        timeRuning.value = TIME_COUNT;
-        showInterval.value = false;
-        timer.value = setInterval(() => {
-          if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
-            timeRuning.value = timeRuning.value - 1;
-          } else {
-            showInterval.value = true;
-            clearInterval(unref(timer));
-            timer.value = null;
-          }
-        }, 1000);
-      }
+  });
+  if (result) {
+    const TIME_COUNT = 60;
+    if (!unref(timer)) {
+      timeRuning.value = TIME_COUNT;
+      showInterval.value = false;
+      timer.value = setInterval(() => {
+        if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
+          timeRuning.value = timeRuning.value - 1;
+        } else {
+          showInterval.value = true;
+          clearInterval(unref(timer));
+          timer.value = null;
+        }
+      }, 1000);
     }
   }
+}
 
-  function registerHandleClick() {
-    if (!formData.username) {
-      createMessage.warn(t('sys.login.accountPlaceholder'));
-      return;
-    }
-    if (!formData.mobile) {
-      createMessage.warn(t('sys.login.mobilePlaceholder'));
-      return;
-    }
-    if (!formData.smscode) {
-      createMessage.warn(t('sys.login.smsPlaceholder'));
-      return;
-    }
-    if (!formData.password) {
-      createMessage.warn(t('sys.login.passwordPlaceholder'));
-      return;
-    }
-    if (!formData.confirmPassword) {
-      createMessage.warn(t('sys.login.confirmPassword'));
-      return;
-    }
-    if (formData.password !== formData.confirmPassword) {
-      createMessage.warn(t('sys.login.diffPwd'));
-      return;
-    }
-    if (!formData.policy) {
-      createMessage.warn(t('sys.login.policyPlaceholder'));
-      return;
-    }
-    registerAccount();
+function registerHandleClick() {
+  if (!formData.username) {
+    createMessage.warn(t('sys.login.accountPlaceholder'));
+    return;
+  }
+  if (!formData.mobile) {
+    createMessage.warn(t('sys.login.mobilePlaceholder'));
+    return;
   }
+  if (!formData.smscode) {
+    createMessage.warn(t('sys.login.smsPlaceholder'));
+    return;
+  }
+  if (!formData.password) {
+    createMessage.warn(t('sys.login.passwordPlaceholder'));
+    return;
+  }
+  if (!formData.confirmPassword) {
+    createMessage.warn(t('sys.login.confirmPassword'));
+    return;
+  }
+  if (formData.password !== formData.confirmPassword) {
+    createMessage.warn(t('sys.login.diffPwd'));
+    return;
+  }
+  if (!formData.policy) {
+    createMessage.warn(t('sys.login.policyPlaceholder'));
+    return;
+  }
+  registerAccount();
+}
 
-  /**
-   * 注册账号
-   */
-  async function registerAccount() {
-    try {
-      const resultInfo = await register(
-        toRaw({
-          username: formData.username,
-          password: formData.password,
-          phone: formData.mobile,
-          smscode: formData.smscode,
-        })
-      );
-      if (resultInfo && resultInfo.data.success) {
-        notification.success({
-          description: resultInfo.data.message || t('sys.api.registerMsg'),
-          duration: 3,
-        });
-        emit('success', { username: formData.username, password: formData.password });
-        initForm();
-      } else {
-        notification.warning({
-          message: t('sys.api.errorTip'),
-          description: resultInfo.data.message || t('sys.api.networkExceptionMsg'),
-          duration: 3,
-        });
-      }
-    } catch (error) {
-      notification.error({
+/**
+ * 注册账号
+ */
+async function registerAccount() {
+  try {
+    const resultInfo = await register(
+      toRaw({
+        username: formData.username,
+        password: formData.password,
+        phone: formData.mobile,
+        smscode: formData.smscode,
+      })
+    );
+    if (resultInfo && resultInfo.data.success) {
+      notification.success({
+        description: resultInfo.data.message || t('sys.api.registerMsg'),
+        duration: 3,
+      });
+      emit('success', { username: formData.username, password: formData.password });
+      initForm();
+    } else {
+      notification.warning({
         message: t('sys.api.errorTip'),
-        description: error.message || t('sys.api.networkExceptionMsg'),
+        description: resultInfo.data.message || t('sys.api.networkExceptionMsg'),
         duration: 3,
       });
     }
+  } catch (error) {
+    notification.error({
+      message: t('sys.api.errorTip'),
+      description: error.message || t('sys.api.networkExceptionMsg'),
+      duration: 3,
+    });
   }
+}
 
-  /**
-   * 初始化表单
-   */
-  function initForm() {
-    Object.assign(formData, { username: '', mobile: '', smscode: '', password: '', confirmPassword: '', policy: false });
-    if (!unref(timer)) {
-      showInterval.value = true;
-      clearInterval(unref(timer));
-      timer.value = null;
-    }
-    formRef.value.resetFields();
+/**
+ * 初始化表单
+ */
+function initForm() {
+  Object.assign(formData, { username: '', mobile: '', smscode: '', password: '', confirmPassword: '', policy: false });
+  if (!unref(timer)) {
+    showInterval.value = true;
+    clearInterval(unref(timer));
+    timer.value = null;
   }
+  formRef.value.resetFields();
+}
 
-  /**
-   * 密码打开或关闭
-   * @param value
-   */
-  function pwdClick(value) {
-    pwdIndex.value = value;
-  }
+/**
+ * 密码打开或关闭
+ * @param value
+ */
+function pwdClick(value) {
+  pwdIndex.value = value;
+}
 
-  /**
-   * 确认密码打开或关闭
-   * @param value
-   */
-  function confirmPwdClick(value) {
-    confirmPwdIndex.value = value;
-  }
+/**
+ * 确认密码打开或关闭
+ * @param value
+ */
+function confirmPwdClick(value) {
+  confirmPwdIndex.value = value;
+}
 
-  defineExpose({
-    initForm,
-  });
+defineExpose({
+  initForm,
+});
 </script>
 <style lang="less" scoped>
-  @import '/@/assets/loginmini/style/home.less';
-  @import '/@/assets/loginmini/style/base.less';
-  .aui-input-line .aui-icon {
-    position: absolute;
-    z-index: @layout-basic-z-index;
-    top: 10px;
-    left: 10px;
-    font-size: 20px !important;
-  }
+@import '/@/assets/loginmini/style/home.less';
+@import '/@/assets/loginmini/style/base.less';
+
+.aui-input-line .aui-icon {
+  position: absolute;
+  z-index: @layout-basic-z-index;
+  top: 10px;
+  left: 10px;
+  font-size: 20px !important;
+}
 </style>