Ver código fonte

Merge remote-tracking branch 'origin/dev-ljx' into dev-wyf

ai0197 4 meses atrás
pai
commit
1c5978fac2

+ 4 - 0
public/app.config.js

@@ -5,6 +5,10 @@ window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
   // 接口前缀
   VITE_GLOB_API_URL_PREFIX: './safety_api/api',
 
+  VITE_GLOB_SKYEYE_PLATFORM: "http://wwww.baidu.com",
+
+  VITE_GLOB_TIANSUO_PLATFORM: "http://www.bing.com"
+
 };
 
 Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);

+ 1 - 0
src/App.vue

@@ -15,6 +15,7 @@
   import { storeToRefs } from 'pinia';
   import { useUserStore } from '@/store/modules/user';
   import Nav from '@/components/Nav.vue';
+  import WatermarkWrapper from '@/components/Waterwrapper.vue';
 
   const userStore = useUserStore();
   const { getUserInfo } = storeToRefs(userStore);

+ 190 - 172
src/components/Nav.vue

@@ -3,25 +3,14 @@
     <img :src="logo" alt="logo" class="header__logo" />
     <span class="platform-name">{{ title }}</span>
     <nav class="header__nav">
-      <div
-        class="header__nav--item"
-        v-for="item in NAV_LIST"
-        :key="item.path"
-        :class="{ active: selectedKey === item.name }"
-        @click="handleNavClick(item)"
-      >
+      <div class="header__nav--item" v-for="item in NAV_LIST" :key="item.path"
+        :class="{ active: selectedKey === item.name }" @click="handleNavClick(item)">
         <span>{{ item.meta?.title }}</span>
       </div>
     </nav>
+
     <SwitchTenant v-if="userStore.info.tenantId === SYS_TENANT_ID" />
     <div class="platform__right">
-      <div class="platform__right__search">
-        <el-input v-model="searchValue" placeholder="搜索您想了解的" class="input-with-icon" clearable>
-          <template #prepend>
-            <img :src="searchIcon" alt="search" class="search-icon" @click="handleSearch" />
-          </template>
-        </el-input>
-      </div>
       <div class="platform__right__login">
         <span @click="userStore.showLogin = true" v-if="!userStore.info?.id">登录</span>
         <UserInfo v-else />
@@ -34,177 +23,206 @@
 </template>
 
 <script lang="ts" setup>
-  import { ref, computed } from 'vue';
-  import { useRouter, useRoute } from 'vue-router';
-  import UpdatePwd from '@/components/UpdatePwd.vue';
-  import UserInfo from '@/components/UserInfo.vue';
-  import Login from '@/components/Login/Login.vue';
-  import SwitchAccount from '@/components/Login/SwitchAccount.vue';
-  import SwitchTenant from '@/layout/components/SwitchTenant.vue';
-  import { useUserStore } from '@/store/modules/user';
-  import { useGlobSetting } from '@/hooks/setting';
-  import { SYS_TENANT_ID } from '@/utils/useTargetTenantIdSetting';
-  import { NAV_LIST } from '@/constant/nav';
-  import logo from 'assets/images/home/comac-logo@1X.png';
-  import searchIcon from 'assets/svg/search.svg';
-
-  const userStore = useUserStore();
-  const activeNav = ref(NAV_LIST[0].name);
-  const router = useRouter();
-  const searchValue = ref('');
-
-  const props = withDefaults(defineProps<{ usePx?: boolean }>(), { usePx: false });
-  const usePx = computed(() => props.usePx === true);
-
-  const handleSearch = () => {
-    console.log('searchValue', searchValue.value);
-  };
-
-  const currentRoute = useRoute();
-  const { title } = useGlobSetting();
-
-  const handleNavClick = (item: { name: string; path: string }) => {
-    if (!item.path) {
-      router.replace({ name: 'StayTune' });
-      // ElMessage.warning({ message: `${item.name}功能建设中,暂无法访问`, offset: 100, grouping: true });
-      return;
-    }
-    router.push(item.path);
-  };
+import { ref, computed } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import UpdatePwd from '@/components/UpdatePwd.vue';
+import UserInfo from '@/components/UserInfo.vue';
+import Login from '@/components/Login/Login.vue';
+import SwitchAccount from '@/components/Login/SwitchAccount.vue';
+import SwitchTenant from '@/layout/components/SwitchTenant.vue';
+import { useUserStore } from '@/store/modules/user';
+import { useGlobSetting } from '@/hooks/setting';
+import { SYS_TENANT_ID } from '@/utils/useTargetTenantIdSetting';
+import { NAV_LIST } from '@/constant/nav';
+import logo from 'assets/images/home/comac-logo@1X.png';
+import searchIcon from 'assets/svg/search.svg';
 
-  const selectedKey = computed(() => {
-    return currentRoute.matched[0]?.name;
-  });
-</script>
+const userStore = useUserStore();
+const activeNav = ref(NAV_LIST[0].name);
+const router = useRouter();
+const searchValue = ref('');
 
-<style lang="scss" scoped>
-  .header {
-    display: flex;
-    align-items: center;
-    position: relative;
-    width: 100%;
-    height: 78cpx;
-    background: url('assets/images/home/nav-bg@1X.png') no-repeat center center / cover;
-    z-index: 2;
-    &__nav {
-      @include flex-center;
-      gap: 20cpx;
-      height: 100%;
-      margin-left: 32cpx;
-      &--item {
-        @include flex-center;
-        height: 45cpx;
-        padding: 10cpx 20cpx;
-        gap: 8cpx;
-        font-size: 18cpx;
-        color: #333;
-        border-radius: 4cpx;
-        cursor: pointer;
-        border: 1px solid transparent;
-        border-radius: 4px;
-        // transition: all 0.3s ease-in-out;
-        &.active,
-        &:hover {
-          background: linear-gradient(180deg, #33afff, $primary-color);
-          color: $white-color;
-        }
-      }
-    }
-  }
-  .header__logo {
-    width: 34cpx;
-    height: 34cpx;
-    margin-left: 38cpx;
+const props = withDefaults(defineProps<{ usePx?: boolean }>(), { usePx: false });
+const usePx = computed(() => props.usePx === true);
+
+const handleSearch = () => {
+  console.log('searchValue', searchValue.value);
+};
+
+const currentRoute = useRoute();
+const { title } = useGlobSetting();
+
+const handleNavClick = (item: { name: string; path: string; isRedrect?: boolean }) => {
+
+  if (!item.path) {
+    router.replace({ name: 'StayTune' });
+    // ElMessage.warning({ message: `${item.name}功能建设中,暂无法访问`, offset: 100, grouping: true });
+    return;
   }
-  .platform-name {
-    font-size: 18cpx;
-    font-weight: 550;
-    margin-left: 9cpx;
+
+  if (item.isRedrect) {
+    window.open(item.path, '_blank');
+    return;
   }
-  .platform__right {
+  router.push(item.path);
+};
+
+const selectedKey = computed(() => {
+  return currentRoute.matched[0]?.name;
+});
+
+</script>
+
+<style lang="scss" scoped>
+.header {
+  display: flex;
+  align-items: center;
+  position: relative;
+  width: 100%;
+  height: 78cpx;
+  background: url('assets/images/home/nav-bg@1X.png') no-repeat center center / cover;
+  z-index: 2;
+
+  &__nav {
     @include flex-center;
-    position: absolute;
-    top: 0;
-    right: 0;
-    height: 73cpx;
-    &__search {
-      @include flex-center;
-      width: 210cpx;
-      height: 100%;
-      padding: 28cpx 26cpx;
-      background: rgba($white-color, 0.4);
-    }
-    &__login {
+    gap: 20cpx;
+    height: 100%;
+    margin-left: 32cpx;
+
+    &--item {
       @include flex-center;
-      gap: 10cpx;
-      height: 100%;
-      padding: 27cpx 40cpx 26cpx 40cpx;
+      height: 45cpx;
+      padding: 10cpx 20cpx;
+      gap: 8cpx;
       font-size: 18cpx;
-      color: $primary-color;
-      cursor: pointer;
-    }
-  }
-  .input-with-icon {
-    :deep(.el-input__wrapper),
-    :deep(.el-input-group__prepend) {
-      background-color: transparent;
-      box-shadow: none;
-      padding: 0;
-    }
-    :deep(.el-input__inner) {
-      width: 120cpx;
-      margin-left: 5cpx;
-      font-size: 16cpx;
-      color: #909399;
-    }
-    .search-icon {
-      width: 28cpx;
-      height: 28cpx;
+      color: #333;
+      border-radius: 4cpx;
       cursor: pointer;
+      border: 1px solid transparent;
+      border-radius: 4px;
+
+      // transition: all 0.3s ease-in-out;
+      &.active,
+      &:hover {
+        background: linear-gradient(180deg, #33afff, $primary-color);
+        color: $white-color;
+      }
     }
   }
-  /* 使用 px 的样式覆盖(用于固定画布布局) */
-  .header.use-px {
-    height: 78px;
-  }
-  .header.use-px .header__nav {
-    gap: 14px;
-    margin-left: 32px;
-  }
-  .header.use-px .header__nav--item {
-    height: 45px;
-    padding: 10px 20px;
-    font-size: 18px;
-    border-radius: 4px;
-  }
-  .header.use-px .header__logo {
-    width: 34px;
-    height: 34px;
-    margin-left: 38px;
-  }
-  .header.use-px .platform-name {
-    font-size: 18px;
-    margin-left: 9px;
-  }
-  .header.use-px .platform__right {
-    height: 73px;
+}
+
+.header__logo {
+  width: 34cpx;
+  height: 34cpx;
+  margin-left: 38cpx;
+}
+
+.platform-name {
+  font-size: 18cpx;
+  font-weight: 550;
+  margin-left: 9cpx;
+}
+
+.platform__right {
+  @include flex-center;
+  position: absolute;
+  top: 0;
+  right: 0;
+  height: 73cpx;
+
+  &__search {
+    @include flex-center;
+    width: 210cpx;
+    height: 100%;
+    padding: 28cpx 26cpx;
+    background: rgba($white-color, 0.4);
   }
-  .header.use-px .platform__right__search {
-    width: 210px;
-    padding: 28px 26px;
+
+  &__login {
+    @include flex-center;
+    gap: 10cpx;
+    height: 100%;
+    padding: 27cpx 40cpx 26cpx 40cpx;
+    font-size: 18cpx;
+    color: $primary-color;
+    cursor: pointer;
   }
-  .header.use-px .platform__right__login {
-    gap: 10px;
-    padding: 27px 40px 26px 40px;
-    font-size: 18px;
+}
+
+.input-with-icon {
+
+  :deep(.el-input__wrapper),
+  :deep(.el-input-group__prepend) {
+    background-color: transparent;
+    box-shadow: none;
+    padding: 0;
   }
-  .header.use-px .input-with-icon :deep(.el-input__inner) {
-    width: 120px;
-    margin-left: 5px;
-    font-size: 16px;
+
+  :deep(.el-input__inner) {
+    width: 120cpx;
+    margin-left: 5cpx;
+    font-size: 16cpx;
+    color: #909399;
   }
-  .header.use-px .input-with-icon .search-icon {
-    width: 28px;
-    height: 28px;
+
+  .search-icon {
+    width: 28cpx;
+    height: 28cpx;
+    cursor: pointer;
   }
+}
+
+/* 使用 px 的样式覆盖(用于固定画布布局) */
+.header.use-px {
+  height: 78px;
+}
+
+.header.use-px .header__nav {
+  gap: 14px;
+  margin-left: 32px;
+}
+
+.header.use-px .header__nav--item {
+  height: 45px;
+  padding: 10px 20px;
+  font-size: 18px;
+  border-radius: 4px;
+}
+
+.header.use-px .header__logo {
+  width: 34px;
+  height: 34px;
+  margin-left: 38px;
+}
+
+.header.use-px .platform-name {
+  font-size: 18px;
+  margin-left: 9px;
+}
+
+.header.use-px .platform__right {
+  height: 73px;
+}
+
+.header.use-px .platform__right__search {
+  width: 210px;
+  padding: 28px 26px;
+}
+
+.header.use-px .platform__right__login {
+  gap: 10px;
+  padding: 27px 40px 26px 40px;
+  font-size: 18px;
+}
+
+.header.use-px .input-with-icon :deep(.el-input__inner) {
+  width: 120px;
+  margin-left: 5px;
+  font-size: 16px;
+}
+
+.header.use-px .input-with-icon .search-icon {
+  width: 28px;
+  height: 28px;
+}
 </style>

+ 156 - 0
src/components/Waterwrapper.vue

@@ -0,0 +1,156 @@
+<template>
+  <!-- 水印容器:根据传入的 props 适配全局/局部水印 -->
+  <div 
+    class="watermark-wrapper"
+    :style="{
+      position: position,
+      top: 0,
+      left: 0,
+      width: width,
+      height: height,
+      zIndex: zIndex,
+      pointerEvents: 'none' // 核心:不遮挡交互
+    }"
+  ></div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, watch } from 'vue';
+// import {useUserInfoHook} from '@/hooks/useUserInfoHook'
+
+// 定义组件 props
+const props = defineProps({
+  // 定位方式:fixed(全局)/ absolute(局部)
+  position: {
+    type: String,
+    default: 'fixed',
+    validator: (val) => ['fixed', 'absolute'].includes(val)
+  },
+  // 宽度:全局用 100vw,局部用 100%
+  width: {
+    type: String,
+    default: '100vw'
+  },
+  // 高度:全局用 100vh,局部用 100%
+  height: {
+    type: String,
+    default: '100vh'
+  },
+  // 层级:默认 999(低于 el-dialog 的 2000)
+  zIndex: {
+    type: Number,
+    default: 999
+  },
+  // 是否固定屏幕模式(适配你的业务逻辑)
+  isFixedScreen: {
+    type: Boolean,
+    default: false
+  },
+  username:{
+    type: String,
+  },
+    realname:{
+    type: String,
+  }
+});
+
+// 移除水印的方法引用
+let removeWatermarkFn = null;
+
+// 获取用户仓库
+
+// const {realname,staffNo} = useUserInfoHook();
+// 生成水印的核心方法
+const createWatermark = () => {
+  
+  // 未登录/无用户信息时不生成水印
+  if (!props.username || !props.realname) return;
+
+  // 拼接水印文本
+  const watermarkText = `${props.username}-${props.realname}`;
+
+  // 1. 创建 Canvas 绘制水印
+  const canvas = document.createElement('canvas');
+  const ctx = canvas.getContext('2d');
+  if (!ctx) return;
+
+  // 配置水印样式
+  const font = '16px Microsoft YaHei';
+  const color = 'rgba(64, 158, 255, 0.15)';
+  const rotate = -15;
+
+  // 计算文本尺寸
+  ctx.font = font;
+  const textWidth = ctx.measureText(watermarkText).width;
+  canvas.width = textWidth + 120; // 水平间距
+  canvas.height = textWidth + 80; // 垂直间距
+
+  // 绘制水印
+  ctx.font = font;
+  ctx.fillStyle = color;
+  ctx.rotate((rotate * Math.PI) / 180); // 角度转弧度
+  ctx.fillText(watermarkText, 0, canvas.height / 2);
+
+  // 2. 生成 Base64 图片
+  const watermarkBase64 = canvas.toDataURL('image/png');
+
+  // 3. 给水印容器设置背景
+  const wrapper = document.querySelector('.watermark-wrapper');
+  if (wrapper) {
+    wrapper.style.backgroundImage = `url(${watermarkBase64})`;
+    wrapper.style.backgroundRepeat = 'repeat';
+    wrapper.style.backgroundSize = `${canvas.width}px ${canvas.height}px`;
+  }
+};
+
+// 移除水印
+const removeWatermark = () => {
+  const wrapper = document.querySelector('.watermark-wrapper');
+  if (wrapper) {
+    wrapper.style.backgroundImage = 'none';
+  }
+};
+
+// 监听用户信息变化(登录/退出时重新生成水印)
+// watch(
+//   () => userId,
+//   () => {
+//     removeWatermark();
+//     createWatermark();
+//   },
+//   { immediate: true, deep: true }
+// );
+
+// 监听 isFixedScreen 变化,适配布局
+// watch(
+//   () => props.isFixedScreen,
+//   () => {
+//     removeWatermark();
+//     createWatermark();
+//   }
+// );
+
+// 页面挂载时生成水印
+onMounted(() => {
+  createWatermark();
+});
+
+// 页面销毁时移除水印
+onUnmounted(() => {
+  removeWatermark();
+  if (removeWatermarkFn) {
+    removeWatermarkFn();
+  }
+});
+</script>
+
+<style scoped>
+.watermark-wrapper {
+    width: 100vw;
+    height: 100vh;
+  margin: 0;
+  padding: 0;
+  border: none;
+  background: transparent;
+}
+</style>

+ 18 - 1
src/constant/nav.ts

@@ -3,7 +3,8 @@
  */
 
 import { HOME_PAGE } from '@/router/full-routes';
-
+import { useGlobSetting } from '@/hooks/setting';
+const { skyeyeUrl, tiansuoUrl } = useGlobSetting();
 // 首页导航列表 根据后端返回权限控制(首页永远存在)
 export const NAV_LIST = [
   {
@@ -58,4 +59,20 @@ export const NAV_LIST = [
       title: '平台管理',
     },
   },
+  {
+    name: 'Skyeye',
+    path: skyeyeUrl,
+    isRedrect: true,
+    meta: {
+      title: '实时监控',
+    },
+  },
+  {
+    name: 'Tiansuo',
+    path: tiansuoUrl,
+    isRedrect: true,
+    meta: {
+      title: '物联集成',
+    },
+  },
 ];

+ 7 - 1
src/hooks/setting/index.ts

@@ -8,7 +8,9 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
     VITE_GLOB_APP_TITLE,
     VITE_GLOB_API_URL_PREFIX,
     VITE_GLOB_DRILL_SIGN_URL,
-    // VITE_GLOB_TENANT_CODE
+    // VITE_GLOB_TENANT_CODE,
+    VITE_GLOB_SKYEYE_PLATFORM,
+    VITE_GLOB_TIANSUO_PLATFORM,
   } = getAppEnvConfig();
 
   // Take global configuration
@@ -20,6 +22,10 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
     drillSignUrl: VITE_GLOB_DRILL_SIGN_URL || '',
 
     // tenantCode: VITE_GLOB_TENANT_CODE,
+
+    skyeyeUrl: VITE_GLOB_SKYEYE_PLATFORM || '',
+
+    tiansuoUrl: VITE_GLOB_TIANSUO_PLATFORM || '',
   };
   return glob as Readonly<GlobConfig>;
 };

+ 3 - 1
src/main.ts

@@ -18,10 +18,12 @@ dayjs.locale('zh-cn');
 
 async function bootstrap() {
   const app = createApp(App);
+  
   app.use(notivue);
-
+   
   // 全局完整引入 element 组件
   setupElement(app);
+
   app.component('BreadcrumbBack', BreadcrumbBack); //全局注册面包屑返回按钮
 
   // 注册全局自定义指令,如:v-permission权限指令

+ 1 - 1
src/utils/env.ts

@@ -16,8 +16,8 @@ export function getStorageShortName() {
 
 export function getAppEnvConfig() {
   const ENV_NAME = '__PRODUCTION__SKYEYEADMIN__CONF__';
-
   const ENV = window[ENV_NAME as any] as unknown as GlobEnvConfig;
+
   if (!ENV) {
     throw new Error('config not found');
   }

+ 13 - 0
types/config.d.ts

@@ -56,8 +56,15 @@ export interface GlobConfig {
   urlPrefix: string;
   // 签到二维码地址
   drillSignUrl: string;
+
   // 租户code
   // tenantCode?: string;
+
+  // 天眼外链地址
+  skyeyeUrl: string;
+
+  // 天梭外链地址
+  tiansuoUrl: string;
 }
 
 export interface GlobEnvConfig {
@@ -69,6 +76,12 @@ export interface GlobEnvConfig {
   VITE_GLOB_DRILL_SIGN_URL: string;
   // 租户code
   VITE_GLOB_TENANT_CODE: string;
+
+  // 天眼外链地址
+  VITE_GLOB_SKYEYE_PLATFORM: string;
+
+  // 天梭外链地址
+  VITE_GLOB_TIANSUO_PLATFORM: string;
 }
 
 // export interface GlobConfig {

+ 4 - 0
utils/devProxy/staff/app.config.js

@@ -5,6 +5,10 @@ window.__PRODUCTION__SKYEYEADMIN__CONF__ = {
   // 接口前缀
   VITE_GLOB_API_URL_PREFIX: './safety_api/api',
 
+  VITE_GLOB_SKYEYE_PLATFORM: "wwww.baidu.com",
+
+  VITE_GLOB_TIANSUO_PLATFORM: "www.bing.com"
+
 };
 
 Object.freeze(window.__PRODUCTION__SKYEYEADMIN__CONF__);