Просмотр исходного кода

feat(auth): 添加企业账户过期检查和跳转功能

- 在App.vue中添加企业过期事件监听和路由跳转逻辑
- 在路由守卫authGuard.ts中添加企业过期检查和自动跳转
- 添加getCompanyStatus API用于获取企业状态
- 在用户store中添加企业过期状态检查、定时检查和事件触发功能
- 创建新的过期页面photography/expired.vue用于显示过期信息
- 添加企业状态定期检查机制,每10分钟检查一次
- 实现过期时自动跳转到过期页面的功能
panqiuyao 1 день назад
Родитель
Сommit
fc02d32d66

+ 22 - 0
frontend/src/App.vue

@@ -11,6 +11,7 @@
 </template>
 <script setup lang="ts">
 import { useRoute, useRouter } from 'vue-router'
+import { onMounted, onUnmounted } from 'vue'
 import Login from '@/components/login/index.vue';
 import useUserInfo from "@/stores/modules/user";
 const useUserInfoStore = useUserInfo();
@@ -20,12 +21,33 @@ listenerOnline();
 
 const router = useRouter()
 const route = useRoute()
+
 /* keep alive 的路由名称 */
 const keepAliveIncludes = router
     .getRoutes()
     .filter((router) => router.meta?.keepAlive)
     .map((router) => router.name as string)
 
+/* 监听企业过期事件 */
+const handleCompanyExpired = (event: CustomEvent) => {
+  const { message } = event.detail
+  console.log('企业过期事件触发:', message)
+
+  // 如果当前不在过期页面,进行跳转
+  if (route.path !== '/photography/expired') {
+    console.log('跳转到过期页面')
+    router.replace('/photography/expired')
+  }
+}
+
+onMounted(() => {
+  window.addEventListener('companyExpired', handleCompanyExpired as EventListener)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('companyExpired', handleCompanyExpired as EventListener)
+})
+
 
 
 </script>

+ 6 - 0
frontend/src/apis/user.ts

@@ -44,3 +44,9 @@ export function selectCompany(data) {
     return POST('/api/backend/account/select_company',data)
 }
 
+
+// 获取企业状态
+export function getCompanyStatus(data) {
+    return GET('/api/ai_image/auto_photo/get_company_status',data)
+}
+

+ 9 - 0
frontend/src/router/index.ts

@@ -69,6 +69,15 @@ const routes: RouteRecordRaw[] = [
         }
     },
     {
+        path: "/photography/expired",
+        name: "PhotographyExpired",
+        component: () => import("@/views/Photography/expired.vue"),
+        meta: {
+            title: '企业账户过期',
+            noAuth: true
+        }
+    },
+    {
         path: "/remote_control",
         name: "RemoteControl",
         component: () => import("@/views/RemoteControl/index.vue"),

+ 6 - 0
frontend/src/router/plugins/authGuard.ts

@@ -23,6 +23,12 @@ export function authGuard(router: Router) {
       if(!useUserInfoStore.userInfo.id){
           await useUserInfoStore.getInfo()
       }
+
+      // 检查企业是否过期,如果过期跳转到到期页面
+      if (useUserInfoStore.isExpired && to.path !== '/photography/expired') {
+        return next('/photography/expired')
+      }
+
       return next()
     } else {
       if(to.meta.noAuth)  return next()

+ 87 - 2
frontend/src/stores/modules/user.ts

@@ -1,9 +1,9 @@
 import { defineStore } from 'pinia';
 import { ref, computed } from 'vue';
-import { getUserInfo, login } from '@/apis/user';
+import { getUserInfo, login, getCompanyStatus } from '@/apis/user';
 import tokenInfo from '@/stores/modules/token';
 import client from '@/stores/modules/client';
-import { useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
 
 
 export const useUserInfo = defineStore('userInfo', () => {
@@ -13,6 +13,12 @@ export const useUserInfo = defineStore('userInfo', () => {
 
   const loginShow = ref(false); // 控制登录弹窗的显示状态
   const userInfo = ref({}); // 用户的详细信息
+  const isExpired = ref(false); // 企业账户是否过期
+  const expiredMessage = ref(''); // 过期提示消息
+
+  // 定时检查相关
+  let checkInterval: NodeJS.Timeout | null = null; // 定时器
+  const CHECK_INTERVAL = 10 * 60 * 1000; // 10分钟检查一次
 
   // Actions(操作)
 
@@ -83,6 +89,9 @@ export const useUserInfo = defineStore('userInfo', () => {
         // 关闭窗口失败不影响退出登录流程
       }
 
+      // 停止定期检查
+      stopPeriodicCheck();
+
       // 清空用户信息
       try {
         tokenInfoStore.clearToken()
@@ -112,6 +121,70 @@ export const useUserInfo = defineStore('userInfo', () => {
   };
 
   /**
+   * 检查企业状态并处理跳转
+   * @returns {Promise<boolean>} 是否过期
+   */
+  const checkCompanyStatus = async (): Promise<boolean> => {
+    try {
+      const statusRes = await getCompanyStatus({});
+      const statusData = statusRes.data;
+      if (statusData && statusData.status === 0) {
+        // 已过期,设置过期状态
+        isExpired.value = true;
+        expiredMessage.value = statusData.message || '企业账户已过期,请联系管理员';
+
+        // 触发自定义事件,通知组件进行跳转
+        if (typeof window !== 'undefined') {
+          window.dispatchEvent(new CustomEvent('companyExpired', {
+            detail: { message: expiredMessage.value }
+          }));
+        }
+
+        return true;
+      } else {
+        // 未过期,重置状态
+        isExpired.value = false;
+        expiredMessage.value = '';
+        return false;
+      }
+    } catch (statusError) {
+      console.error('获取企业状态失败:', statusError);
+      // 企业状态获取失败不影响正常使用,只记录错误
+      isExpired.value = false;
+      expiredMessage.value = '';
+      return false;
+    }
+  };
+
+  /**
+   * 启动定期检查企业状态
+   */
+  const startPeriodicCheck = () => {
+    // 清除已有的定时器
+    if (checkInterval) {
+      clearInterval(checkInterval);
+    }
+
+    // 设置新的定时器,每5分钟检查一次
+    checkInterval = setInterval(async () => {
+      await checkCompanyStatus();
+    }, CHECK_INTERVAL);
+
+    console.log('企业状态定期检查已启动');
+  };
+
+  /**
+   * 停止定期检查企业状态
+   */
+  const stopPeriodicCheck = () => {
+    if (checkInterval) {
+      clearInterval(checkInterval);
+      checkInterval = null;
+      console.log('企业状态定期检查已停止');
+    }
+  };
+
+  /**
    * 获取用户信息并更新状态。
    *
    * @returns {Promise<any>} 用户信息数据。
@@ -132,6 +205,13 @@ export const useUserInfo = defineStore('userInfo', () => {
         throw new Error('请重新登录!');
       }
       updateUserInfo(data); // 更新用户信息
+
+      // 获取企业状态,检查是否过期
+      await checkCompanyStatus();
+
+      // 启动定期检查
+      startPeriodicCheck();
+
       return data;
     } catch (error) {
       console.error('获取用户信息失败:', error);
@@ -142,11 +222,16 @@ export const useUserInfo = defineStore('userInfo', () => {
   return {
     loginShow,
     userInfo,
+    isExpired,
+    expiredMessage,
     updateUserInfo,
     updateLoginShow,
     loginAction,
     loginOut,
     getInfo,
+    checkCompanyStatus,
+    startPeriodicCheck,
+    stopPeriodicCheck,
   };
 });
 

+ 0 - 1
frontend/src/views/Photography/detail.vue

@@ -518,7 +518,6 @@
 
 import { getCompanyTemplatesApi } from '@/apis/other'
 import tokenInfo from '@/stores/modules/token';
-import useUserInfo from "@/stores/modules/user";
 import { useRoute, useRouter } from 'vue-router'
 import { clickLog, setLogInfo } from '@/utils/log'
 

+ 292 - 0
frontend/src/views/Photography/expired.vue

@@ -0,0 +1,292 @@
+<template>
+  <div class="expired-page">
+    <div class="expired-container">
+      <!-- 顶部警告图标区域 -->
+      <div class="expired-header">
+        <div class="expired-icon">
+          <el-icon size="80" color="#FF4D4F">
+            <Warning />
+          </el-icon>
+        </div>
+        <h1 class="expired-title">企业账户已过期</h1>
+      </div>
+
+      <!-- 主要内容区域 -->
+      <div class="expired-content">
+        <div class="expired-message">
+          <p class="message-text">{{ expiredMessage }}</p>
+<!--          <p class="message-hint">请联系管理员处理后重新登录使用</p>-->
+        </div>
+
+        <!-- 操作按钮 -->
+        <div class="expired-actions">
+<!--          <el-button
+            type="primary"
+            size="large"
+            @click="handleContactAdmin"
+            class="action-btn"
+          >
+            联系管理员
+          </el-button>-->
+          <el-button
+            type="default"
+            size="large"
+            @click="handleLogout"
+            class="action-btn logout-btn"
+          >
+            重新登录
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 底部装饰 -->
+      <div class="expired-footer">
+        <div class="footer-decoration">
+          <div class="decoration-line"></div>
+          <div class="decoration-dot"></div>
+          <div class="decoration-line"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { Warning } from '@element-plus/icons-vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import useUserInfo from '@/stores/modules/user'
+
+const router = useRouter()
+const useUserInfoStore = useUserInfo()
+const { expiredMessage, loginOut } = useUserInfoStore
+
+// 获取过期消息
+const expiredMessageText = computed(() => {
+  return expiredMessage.value || '您的企业账户已过期,无法继续使用系统功能。'
+})
+
+// 联系管理员
+const handleContactAdmin = () => {
+  ElMessageBox({
+    title: '联系方式',
+    message: '请通过以下方式联系管理员:\n\n电话:400-xxx-xxxx\n邮箱:admin@company.com\n\n或扫描下方二维码添加客服微信',
+    showCancelButton: false,
+    confirmButtonText: '知道了',
+    type: 'info'
+  })
+}
+
+// 重新登录
+const handleLogout = async () => {
+  try {
+    await loginOut()
+    // 退出登录后会自动跳转到首页
+  } catch (error) {
+    console.error('退出登录失败:', error)
+    ElMessage.error('退出登录失败,请重试')
+  }
+}
+
+onMounted(() => {
+  // 页面加载时的埋点或其他初始化逻辑
+  console.log('企业过期页面已加载')
+})
+</script>
+
+<style lang="scss" scoped>
+.expired-page {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+}
+
+.expired-container {
+  background: #fff;
+  border-radius: 20px;
+  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+  padding: 60px;
+  max-width: 600px;
+  width: 100%;
+  text-align: center;
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    background: linear-gradient(90deg, #FF4D4F 0%, #FF7875 100%);
+  }
+}
+
+.expired-header {
+  margin-bottom: 40px;
+
+  .expired-icon {
+    margin-bottom: 24px;
+    display: flex;
+    justify-content: center;
+  }
+
+  .expired-title {
+    font-size: 32px;
+    font-weight: 700;
+    color: #1D2129;
+    margin: 0;
+    letter-spacing: 1px;
+  }
+}
+
+.expired-content {
+  .expired-message {
+    margin-bottom: 50px;
+
+    .message-text {
+      font-size: 18px;
+      color: #4E5969;
+      line-height: 1.6;
+      margin-bottom: 16px;
+      font-weight: 500;
+    }
+
+    .message-hint {
+      font-size: 16px;
+      color: #86909C;
+      line-height: 1.5;
+    }
+  }
+
+  .expired-actions {
+    display: flex;
+    gap: 20px;
+    justify-content: center;
+    flex-wrap: wrap;
+
+    .action-btn {
+      min-width: 140px;
+      height: 48px;
+      border-radius: 8px;
+      font-size: 16px;
+      font-weight: 600;
+
+      &.logout-btn {
+        border-color: #D9D9D9;
+        color: #666;
+        background: #FAFAFA;
+
+        &:hover {
+          border-color: #BFBFBF;
+          background: #F5F5F5;
+        }
+      }
+    }
+  }
+}
+
+.expired-footer {
+  margin-top: 50px;
+
+  .footer-decoration {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 12px;
+
+    .decoration-line {
+      width: 40px;
+      height: 2px;
+      background: #E5E6EB;
+      border-radius: 1px;
+    }
+
+    .decoration-dot {
+      width: 8px;
+      height: 8px;
+      background: #E5E6EB;
+      border-radius: 50%;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .expired-page {
+    padding: 10px;
+  }
+
+  .expired-container {
+    padding: 40px 30px;
+    max-width: 100%;
+  }
+
+  .expired-header {
+    .expired-title {
+      font-size: 28px;
+    }
+  }
+
+  .expired-content {
+    .expired-message {
+      margin-bottom: 40px;
+
+      .message-text {
+        font-size: 16px;
+      }
+
+      .message-hint {
+        font-size: 14px;
+      }
+    }
+
+    .expired-actions {
+      flex-direction: column;
+      align-items: center;
+
+      .action-btn {
+        width: 100%;
+        max-width: 280px;
+      }
+    }
+  }
+
+  .expired-footer {
+    margin-top: 40px;
+  }
+}
+
+@media (max-width: 480px) {
+  .expired-container {
+    padding: 30px 20px;
+  }
+
+  .expired-header {
+    margin-bottom: 30px;
+
+    .expired-icon .el-icon {
+      font-size: 60px;
+    }
+
+    .expired-title {
+      font-size: 24px;
+    }
+  }
+
+  .expired-content {
+    .expired-message {
+      margin-bottom: 30px;
+    }
+
+    .expired-actions {
+      gap: 12px;
+    }
+  }
+}
+</style>