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

增加权限控制和优化云台逻辑

liujintao 4 дней назад
Родитель
Сommit
a10cc61c7f

+ 1 - 1
.env.development

@@ -5,7 +5,7 @@ VITE_PUBLIC_PATH='/'
 ## 路由模式 hash 或 html5
 VITE_ROUTER_HISTORY='html5'
 
-VITE_HOST_IP = '192.168.1.103'
+VITE_HOST_IP = '192.168.1.107'
 
 VITE_VIDEO_PORT = '8000'
 

+ 10 - 0
src/api/permission.ts

@@ -0,0 +1,10 @@
+import { request } from '@/utils/request'
+import type { FunctionListData } from './types/permission'
+
+/** 获取设备功能列表(权限) */
+export function getFunctionList() {
+  return request<{ data: FunctionListData }>({
+    url: 'API/V1.0/Device/FunctionList',
+    method: 'get'
+  })
+}

+ 37 - 0
src/api/types/permission.ts

@@ -0,0 +1,37 @@
+/** FunctionList 接口返回的权限数据类型 */
+
+/** Tab 级别权限 */
+export interface TabPermission {
+  is_show: boolean
+}
+
+/** 二级目录(Settings 子菜单)权限,包含自身 is_show 和下属 tab */
+export interface SubMenuPermission {
+  is_show: boolean
+  [tabKey: string]: boolean | TabPermission
+}
+
+/** 一级目录权限 */
+export interface MenuPermission {
+  is_show: boolean
+  [subMenuKey: string]: boolean | SubMenuPermission
+}
+
+/** Settings 下的所有二级目录 */
+export interface SettingsPermission {
+  System_Settings: SubMenuPermission
+  Image_Display: SubMenuPermission
+  Audio_Video: SubMenuPermission
+  Network_Settings: SubMenuPermission
+  Alarm_Settings: SubMenuPermission
+  [key: string]: SubMenuPermission
+}
+
+/** FunctionList 接口返回数据 */
+export interface FunctionListData {
+  DeviceName: string
+  Settings: SettingsPermission
+  Remote: MenuPermission
+  Protocol: MenuPermission
+  [key: string]: string | SettingsPermission | MenuPermission
+}

+ 2 - 2
src/layouts/components/SideBar/index.vue

@@ -3,9 +3,9 @@
 import SideBarItem from './sidebarItem.vue'
 import { useRouter } from 'vue-router'
 import { useAppStore } from '@/stores/modules/app'
-// 拿到路由列表,过滤我们不想要的
+// 拿到路由列表,过滤我们不想要的(使用 computed 保持响应式)
 const router = useRouter()
-const routerList = router.getRoutes().filter((v) => v.meta && v.meta.isShow)
+const routerList = computed(() => router.getRoutes().filter((v) => v.meta && v.meta.isShow))
 const appStore = useAppStore()
 const title = import.meta.env.VITE_APP_TITLE
 

+ 4 - 4
src/main.ts

@@ -1,7 +1,6 @@
 import { createApp } from 'vue'
-import { createPinia } from 'pinia'
 import App from './App.vue'
-import router, { setRouter } from './router'
+import router from './router'
 import '@/router/permission'
 
 import { View, Setting, User, Lock,
@@ -23,7 +22,8 @@ app.component('Refresh', Refresh)
 app.component('Document', Document)
 app.component('Monitor', Monitor)
 
-app.use(createPinia())
-setRouter()
+import store from '@/stores'
+
+app.use(store)
 app.use(router)
 app.mount('#app')

+ 98 - 1
src/router/index.ts

@@ -3,13 +3,36 @@ import {
   createWebHashHistory,
   type RouteRecordRaw
 } from 'vue-router'
+import { usePermissionStoreHook } from '@/stores/modules/permission'
 
 
 const Layouts = () => import('@/layouts/index.vue')
 
+/**
+ * 路由 name 与 FunctionList key 的映射
+ * 一级菜单: route name → FunctionList 一级 key
+ * 二级菜单(Settings 子路由): route name → FunctionList Settings 下的 key
+ */
+const menuPermissionMap: Record<string, string> = {
+  // 一级菜单
+  Settings: 'Settings',
+  RemoteViewing: 'Remote',
+  StandardProtocol: 'Protocol',
+}
+
+const subMenuPermissionMap: Record<string, string> = {
+  // Settings 下的二级菜单
+  SystemSettings: 'System_Settings',
+  ImageDisplay: 'Image_Display',
+  AudioVideo: 'Audio_Video',
+  NetSettings: 'Network_Settings',
+  AlarmSettings: 'Alarm_Settings',
+}
+
 export const constantRoutes: RouteRecordRaw[] = [
   {
     path: '/',
+    name: 'PreviewLayout',
     component: Layouts,
     redirect: '/preview',
     children: [
@@ -80,6 +103,7 @@ export const constantRoutes: RouteRecordRaw[] = [
   },
   {
     path: '/',
+    name: 'RemoteLayout',
     component: Layouts,
     redirect: '/remoteViewing',
     children: [
@@ -97,6 +121,7 @@ export const constantRoutes: RouteRecordRaw[] = [
   },
   {
     path: '/',
+    name: 'ProtocolLayout',
     component: Layouts,
     redirect: '/standardProtocol',
     children: [
@@ -129,8 +154,80 @@ const router = createRouter({
 
 export default router
 
+/** 清除所有动态注册的路由 */
+function resetRouter() {
+  // 移除所有 constantRoutes 中定义的路由(按 name 移除)
+  const routeNames: string[] = []
+  constantRoutes.forEach((route) => {
+    if (route.name) {
+      routeNames.push(route.name as string)
+    }
+    if (route.children) {
+      route.children.forEach((child) => {
+        if (child.name) {
+          routeNames.push(child.name as string)
+        }
+      })
+    }
+  })
+  routeNames.forEach((name) => {
+    if (router.hasRoute(name)) {
+      router.removeRoute(name)
+    }
+  })
+}
+
+/** 根据权限过滤路由并注册 */
 export function setRouter() {
-  constantRoutes.forEach((item) => {
+  // 先清除旧路由,避免重复注册
+  resetRouter()
+
+  const permissionStore = usePermissionStoreHook()
+
+  const filteredRoutes = constantRoutes
+    .filter((route) => {
+      // 对于有单个子路由的一级菜单(Preview / Remote / Protocol)
+      if (route.children && route.children.length === 1) {
+        const child = route.children[0]
+        const routeName = child.name as string
+        const permKey = menuPermissionMap[routeName]
+        if (permKey && !permissionStore.isMenuVisible(permKey)) {
+          return false
+        }
+      }
+      // 对于 Settings 这种有多个子路由的一级菜单
+      if (route.name && menuPermissionMap[route.name as string]) {
+        const permKey = menuPermissionMap[route.name as string]
+        if (!permissionStore.isMenuVisible(permKey)) {
+          return false
+        }
+      }
+      return true
+    })
+    .map((route) => {
+      // 过滤 Settings 的子路由
+      if (route.name === 'Settings' && route.children) {
+        const filteredChildren = route.children.filter((child) => {
+          const childName = child.name as string
+          const subPermKey = subMenuPermissionMap[childName]
+          if (subPermKey) {
+            return permissionStore.isSubMenuVisible(subPermKey)
+          }
+          return true
+        })
+        return { ...route, children: filteredChildren }
+      }
+      return route
+    })
+    // 如果 Settings 的子路由全被过滤掉了,移除 Settings 本身
+    .filter((route) => {
+      if (route.name === 'Settings' && route.children && route.children.length === 0) {
+        return false
+      }
+      return true
+    })
+
+  filteredRoutes.forEach((item) => {
     router.addRoute(item)
   })
 }

+ 13 - 2
src/router/permission.ts

@@ -1,11 +1,13 @@
 import router from '@/router'
 import { getToken } from '@/utils/cache/cookies'
+import { usePermissionStoreHook } from '@/stores/modules/permission'
+import { setRouter } from '@/router'
 import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
 
 NProgress.configure({ showSpinner: false })
 
-router.beforeEach(async (to, _form, next) => {
+router.beforeEach(async (to, _from, next) => {
   NProgress.start()
   const token = getToken()
   if (!token) {
@@ -18,7 +20,16 @@ router.beforeEach(async (to, _form, next) => {
     if (to.path === '/login') {
       next({ path: '/' })
     } else {
-      next()
+      // 有 token 但权限数据还没加载过,先拉取权限再注册路由
+      const permissionStore = usePermissionStoreHook()
+      if (!permissionStore.loaded) {
+        await permissionStore.fetchFunctionList()
+        setRouter()
+        // 路由刚注册完,用 replace 重新导航到目标路由以确保匹配
+        next({ ...to, replace: true })
+      } else {
+        next()
+      }
     }
   }
 })

+ 106 - 0
src/stores/modules/permission.ts

@@ -0,0 +1,106 @@
+import { ref } from 'vue'
+import store from '@/stores'
+import { defineStore } from 'pinia'
+import { getFunctionList } from '@/api/permission'
+import type { FunctionListData } from '@/api/types/permission'
+
+/**
+ * FunctionList 接口 key 与路由/tab 的映射关系
+ *
+ * 一级目录映射(key → route name):
+ *   Settings        → Settings(包含子路由)
+ *   Remote          → RemoteViewing
+ *   Protocol        → StandardProtocol
+ *
+ * 二级目录映射(Settings 下的 key → route name):
+ *   System_Settings  → SystemSettings
+ *   Image_Display    → ImageDisplay
+ *   Audio_Video      → AudioVideo
+ *   Network_Settings → NetSettings
+ *   Alarm_Settings   → AlarmSettings
+ *
+ * Tab 映射见各 settings 页面组件内部使用 isTabVisible()
+ */
+
+export const usePermissionStore = defineStore('permission', () => {
+  const functionList = ref<FunctionListData | null>(null)
+  const loaded = ref(false)
+
+  /** 从接口拉取权限数据 */
+  async function fetchFunctionList() {
+    try {
+      const res = await getFunctionList()
+      const rawData = res.data
+      if (rawData && typeof rawData === 'object' && 'Settings' in rawData) {
+        functionList.value = rawData as unknown as FunctionListData
+      } else if (rawData && typeof rawData === 'object' && 'data' in rawData) {
+        functionList.value = (rawData as any).data as FunctionListData
+      }
+      loaded.value = true
+    } catch (e) {
+      console.error('[Permission] 获取 FunctionList 失败', e)
+      loaded.value = true
+    }
+  }
+
+  /** 清空权限(登出时调用) */
+  function resetPermission() {
+    functionList.value = null
+    loaded.value = false
+  }
+
+  /**
+   * 判断一级菜单是否可见
+   * @param key FunctionList 中的一级 key,如 'Settings' | 'Remote' | 'Protocol'
+   */
+  function isMenuVisible(key: string): boolean {
+    if (!functionList.value) return true // 未加载时默认显示
+    const menu = functionList.value[key]
+    if (!menu || typeof menu === 'string') return true
+    return (menu as any).is_show !== false
+  }
+
+  /**
+   * 判断二级菜单(Settings 子路由)是否可见
+   * @param key Settings 下的 key,如 'System_Settings' | 'Image_Display' 等
+   */
+  function isSubMenuVisible(key: string): boolean {
+    if (!functionList.value) return true
+    const settings = functionList.value.Settings
+    if (!settings) return true
+    const sub = settings[key]
+    if (!sub) return true
+    return sub.is_show !== false
+  }
+
+  /**
+   * 判断 Tab 是否可见
+   * @param subMenuKey 二级目录 key,如 'System_Settings'
+   * @param tabKey Tab key,如 'Basic_Information'
+   */
+  function isTabVisible(subMenuKey: string, tabKey: string): boolean {
+    if (!functionList.value) return true
+    const settings = functionList.value.Settings
+    if (!settings) return true
+    const sub = settings[subMenuKey]
+    if (!sub) return true
+    const tab = sub[tabKey]
+    if (!tab || typeof tab === 'boolean') return true
+    return (tab as any).is_show !== false
+  }
+
+  return {
+    functionList,
+    loaded,
+    fetchFunctionList,
+    resetPermission,
+    isMenuVisible,
+    isSubMenuVisible,
+    isTabVisible
+  }
+})
+
+/** 在 setup 外使用 */
+export function usePermissionStoreHook() {
+  return usePermissionStore(store)
+}

+ 2 - 0
src/stores/modules/user.ts

@@ -4,6 +4,7 @@ import { defineStore } from 'pinia'
 import { getToken, removeToken, removeUsername, setUsername } from '@/utils/cache/cookies'
 import { loginApi } from '@/api/login'
 import { type LoginRequestData } from '@/api/types/login'
+import { usePermissionStoreHook } from '@/stores/modules/permission'
 
 export const useUserStore = defineStore('user', () => {
   const token = ref<string>(getToken() || '')
@@ -26,6 +27,7 @@ export const useUserStore = defineStore('user', () => {
     removeToken()
     removeUsername(username.value)
     token.value = ''
+    usePermissionStoreHook().resetPermission()
   }
   /** 重置 Token */
   const resetToken = () => {

+ 5 - 1
src/views/login/index.vue

@@ -108,6 +108,7 @@ import { User, Lock } from '@element-plus/icons-vue'
 import { type LoginRequestData } from '@/api/types/login'
 import { setToken } from '@/utils/cache/cookies'
 import { setRouter } from '@/router'
+import { usePermissionStore } from '@/stores/modules/permission'
 import type { TimeParaData } from '@/api/types/setting'
 import { format } from 'date-fns'
 import { getCameraDeviceInfo, PutTimePara } from '@/api/setting'
@@ -171,11 +172,14 @@ const handleLogin = () => {
       loading.value = true
       useUserStore()
         .login(loginFormData)
-        .then((res: string) => {
+        .then(async (res: string) => {
           if (res == 'ok\n') {
             ElMessage.success('Login Successful')
             const token = useUserStore().token
             setToken(token)
+            // 登录后拉取权限数据,再根据权限注册路由
+            const permissionStore = usePermissionStore()
+            await permissionStore.fetchFunctionList()
             setRouter()
             router.push({ path: '/' })
             const timeParaData: TimeParaData = {

+ 11 - 7
src/views/preview/index.vue

@@ -220,6 +220,8 @@ const handleKeydown = (event: KeyboardEvent) => {
   }
 }
 
+const PTZ_STOP_DELAY = 500 // 松开后延迟 500ms 再发 stop
+
 const handlePtzStart = async (direction: number) => {
   if (isPtzMoving.value) return
   isPtzMoving.value = true
@@ -230,14 +232,16 @@ const handlePtzStart = async (direction: number) => {
   }
 }
 
-const handlePtzStop = async () => {
+const handlePtzStop = () => {
   if (!isPtzMoving.value) return
-  isPtzMoving.value = false
-  try {
-    await ptzControl({ PtzCmd: PTZDirection.Stop })
-  } catch (error) {
-    console.error('PTZ stop failed:', error)
-  }
+  setTimeout(async () => {
+    isPtzMoving.value = false
+    try {
+      await ptzControl({ PtzCmd: PTZDirection.Stop })
+    } catch (error) {
+      console.error('PTZ stop failed:', error)
+    }
+  }, PTZ_STOP_DELAY)
 }
 
 const handlePtzCenter = async () => {

+ 18 - 7
src/views/settings/alarmSettings/index.vue

@@ -1,19 +1,19 @@
 <template>
   <div class="settings-container">
     <el-tabs v-model="activeName" class="demo-tabs">
-      <el-tab-pane label="Motion Detection" name="first">
+      <el-tab-pane v-if="isTabVisible('Alarm_Settings', 'Motion_Detection')" label="Motion Detection" name="first">
         <MotionDetection/>
       </el-tab-pane>
 
-      <el-tab-pane label="Human Detection" name="second">
+      <el-tab-pane v-if="isTabVisible('Alarm_Settings', 'Human_Detection')" label="Human Detection" name="second">
         <HumanDetection/>
       </el-tab-pane>
 
-      <el-tab-pane label="Intrusion Detection" name="third">
+      <el-tab-pane v-if="isTabVisible('Alarm_Settings', 'Intrusion_Detection')" label="Intrusion Detection" name="third">
         <IntrusionDetection/>
       </el-tab-pane>
 
-      <el-tab-pane label="Line Crossing Detection" name="fourth">
+      <el-tab-pane v-if="isTabVisible('Alarm_Settings', 'Line_Crossing_Detection')" label="Line Crossing Detection" name="fourth">
         <LineCrossingDetection/>
       </el-tab-pane>
 
@@ -21,7 +21,7 @@
           <VideoTampering />
       </el-tab-pane> -->
 
-      <el-tab-pane label="Manual Alarm" name="sixth">
+      <el-tab-pane v-if="isTabVisible('Alarm_Settings', 'Manual_Alarm')" label="Manual Alarm" name="sixth">
         <ManualAlarm/>
       </el-tab-pane>
 
@@ -37,8 +37,19 @@ import IntrusionDetection from './components/intrusionDetection/index.vue'
 import LineCrossingDetection from './components/lineCrossingDetection/index.vue'
 // import VideoTampering from './components/videoTampering/index.vue'
 import ManualAlarm from './components/manualAlarm/index.vue'
+import { usePermissionStore } from '@/stores/modules/permission'
 
-const activeName = ref('first')
+const { isTabVisible } = usePermissionStore()
+
+const tabOrder = [
+  { name: 'first', key: 'Motion_Detection' },
+  { name: 'second', key: 'Human_Detection' },
+  { name: 'third', key: 'Intrusion_Detection' },
+  { name: 'fourth', key: 'Line_Crossing_Detection' },
+  { name: 'sixth', key: 'Manual_Alarm' },
+]
+const firstVisible = tabOrder.find(t => isTabVisible('Alarm_Settings', t.key))
+const activeName = ref(firstVisible?.name || 'first')
 
 </script>
 
@@ -49,4 +60,4 @@ const activeName = ref('first')
   flex-direction: column;
   padding: 20px;
 }
-</style>
+</style>

+ 14 - 5
src/views/settings/audioVideo/index.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="settings-container">
     <el-tabs v-model="activeName" class="tabs">
-      <el-tab-pane label="Video" name="first">
+      <el-tab-pane v-if="isTabVisible('Audio_Video', 'Video')" label="Video" name="first">
         <Video/>
       </el-tab-pane>
-      <el-tab-pane label="Audio" name="second">
+      <el-tab-pane v-if="isTabVisible('Audio_Video', 'Audio')" label="Audio" name="second">
         <Audio/>
       </el-tab-pane>
-      <el-tab-pane label="Night Vision Lights" name="third">
+      <el-tab-pane v-if="isTabVisible('Audio_Video', 'Night_Vision_Lights')" label="Night Vision Lights" name="third">
         <NightVisionIlluminator/>
       </el-tab-pane>
     </el-tabs>
@@ -20,8 +20,17 @@ import {ref} from "vue";
 import Video from './components/video/index.vue'
 import Audio from './components/audio/index.vue'
 import NightVisionIlluminator from './components/nightVisionIlluminator/index.vue'
+import { usePermissionStore } from '@/stores/modules/permission'
 
-const activeName = ref('first')
+const { isTabVisible } = usePermissionStore()
+
+const tabOrder = [
+  { name: 'first', key: 'Video' },
+  { name: 'second', key: 'Audio' },
+  { name: 'third', key: 'Night_Vision_Lights' },
+]
+const firstVisible = tabOrder.find(t => isTabVisible('Audio_Video', t.key))
+const activeName = ref(firstVisible?.name || 'first')
 
 </script>
 
@@ -32,4 +41,4 @@ const activeName = ref('first')
   flex-direction: column;
   padding: 20px;
 }
-</style>
+</style>

+ 14 - 5
src/views/settings/imageDisplay/index.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="settings-container">
     <el-tabs v-model="activeName" class="tabs">
-      <el-tab-pane label="Image" name="first">
+      <el-tab-pane v-if="isTabVisible('Image_Display', 'Image')" label="Image" name="first">
         <Image v-if="activeName === 'first'" />
       </el-tab-pane>
-      <el-tab-pane label="OSD Settings" name="second">
+      <el-tab-pane v-if="isTabVisible('Image_Display', 'OSD_Settings')" label="OSD Settings" name="second">
         <OSD v-if="activeName === 'second'" />
       </el-tab-pane>
-      <el-tab-pane label="Privacy Masking " name="third">
+      <el-tab-pane v-if="isTabVisible('Image_Display', 'Privacy_Masking')" label="Privacy Masking " name="third">
         <PrivacyMasking v-if="activeName === 'third'" />
       </el-tab-pane>
     </el-tabs>
@@ -19,8 +19,17 @@ import {ref} from "vue";
 import Image from './components/image/index.vue'
 import OSD from './components/osd/index.vue'
 import PrivacyMasking from './components/privacyMasking/index.vue'
+import { usePermissionStore } from '@/stores/modules/permission'
 
-const activeName = ref('first')
+const { isTabVisible } = usePermissionStore()
+
+const tabOrder = [
+  { name: 'first', key: 'Image' },
+  { name: 'second', key: 'OSD_Settings' },
+  { name: 'third', key: 'Privacy_Masking' },
+]
+const firstVisible = tabOrder.find(t => isTabVisible('Image_Display', t.key))
+const activeName = ref(firstVisible?.name || 'first')
 
 </script>
 
@@ -31,4 +40,4 @@ const activeName = ref('first')
   flex-direction: column;
   padding: 20px;
 }
-</style>
+</style>

+ 13 - 4
src/views/settings/netSettings/index.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="settings-container">
     <el-tabs v-model="activeName" class="demo-tabs">
-      <el-tab-pane label="IP" name="first">
+      <el-tab-pane v-if="isTabVisible('Network_Settings', 'IP')" label="IP" name="first">
         <IP/>
       </el-tab-pane>
-      <el-tab-pane label="Ports" name="second">
+      <el-tab-pane v-if="isTabVisible('Network_Settings', 'Ports')" label="Ports" name="second">
         <div class="port-settings">
           <div class="section">
             <div class="section-title">Port Settings</div>
@@ -23,7 +23,7 @@
           <el-button type="primary" round @click="handleSave">Save</el-button>
         </div>
       </el-tab-pane>
-      <el-tab-pane label="Network Diagnostics" name="third">
+      <el-tab-pane v-if="isTabVisible('Network_Settings', 'Network_Diagnostics')" label="Network Diagnostics" name="third">
           <NetworkDiagnostics />
       </el-tab-pane>
     </el-tabs>
@@ -36,8 +36,17 @@ import IP from './components/IP/index.vue'
 import NetworkDiagnostics from './components/networkDiagnostics/index.vue'
 import { getNetworkPort, putNetworkPort } from '@/api/setting'
 import { ElMessage } from 'element-plus';
+import { usePermissionStore } from '@/stores/modules/permission'
 
-const activeName = ref('first')
+const { isTabVisible } = usePermissionStore()
+
+const tabOrder = [
+  { name: 'first', key: 'IP' },
+  { name: 'second', key: 'Ports' },
+  { name: 'third', key: 'Network_Diagnostics' },
+]
+const firstVisible = tabOrder.find(t => isTabVisible('Network_Settings', t.key))
+const activeName = ref(firstVisible?.name || 'first')
 const Port = ref('')
 
 function getPortSetting() {

+ 19 - 7
src/views/settings/systemSettings/index.vue

@@ -1,23 +1,23 @@
 <template>
   <div class="settings-container">
     <el-tabs v-model="activeName" class="demo-tabs">
-      <el-tab-pane label="Basic Information" name="first">
+      <el-tab-pane v-if="isTabVisible('System_Settings', 'Basic_Information')" label="Basic Information" name="first">
         <CameraInfo />
       </el-tab-pane>
 
-      <el-tab-pane label="System Maintenance" name="second">
+      <el-tab-pane v-if="isTabVisible('System_Settings', 'System_Maintenance')" label="System Maintenance" name="second">
         <SystemMaintenance />
       </el-tab-pane>
 
-      <el-tab-pane label="Time Settings" name="fourth">
+      <el-tab-pane v-if="isTabVisible('System_Settings', 'Time_Settings')" label="Time Settings" name="fourth">
         <TimeSettings />
       </el-tab-pane>
 
-      <el-tab-pane label="Password Management" name="fifth">
+      <el-tab-pane v-if="isTabVisible('System_Settings', 'Password_Management')" label="Password Management" name="fifth">
         <PasswordManagement />
       </el-tab-pane>
 
-      <el-tab-pane label="System Update" name="third">
+      <el-tab-pane v-if="isTabVisible('System_Settings', 'System_Update')" label="System Update" name="third">
         <SystemUpgrade />
       </el-tab-pane>
     </el-tabs>
@@ -25,14 +25,26 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import CameraInfo from './components/cameraInfo/index.vue'
 import PasswordManagement from './components/passwordManagement/index.vue'
 import TimeSettings from './components/timeSettings/index.vue'
 import SystemMaintenance from './components/systemMaintenance/index.vue'
 import SystemUpgrade from './components/systemUpgrade/index.vue'
+import { usePermissionStore } from '@/stores/modules/permission'
 
-const activeName = ref('first')
+const { isTabVisible } = usePermissionStore()
+
+// 所有 tab 按顺序,自动选中第一个可见的
+const tabOrder = [
+  { name: 'first', key: 'Basic_Information' },
+  { name: 'second', key: 'System_Maintenance' },
+  { name: 'fourth', key: 'Time_Settings' },
+  { name: 'fifth', key: 'Password_Management' },
+  { name: 'third', key: 'System_Update' },
+]
+const firstVisible = tabOrder.find(t => isTabVisible('System_Settings', t.key))
+const activeName = ref(firstVisible?.name || 'first')
 </script>
 
 <style scoped lang="scss">