Kaynağa Gözat

feat(layout): add PlugIcon for LSS management and optimize input/table formatting

- Introduced a new PlugIcon component for LSS management in the layout menu.
- Refactored input and table components in the LSS view for improved readability and performance.
yb 1 hafta önce
ebeveyn
işleme
6fd6dfec18
2 değiştirilmiş dosya ile 33 ekleme ve 230 silme
  1. 27 224
      src/layout/index.vue
  2. 6 6
      src/views/lss/index.vue

+ 27 - 224
src/layout/index.vue

@@ -42,7 +42,7 @@
               :class="{ 'layout__nav-item--active': isGroupActive(item) }"
               @click="toggleSubMenu(item.path)"
             >
-              <component :is="item.icon" class="layout__nav-icon" />
+              <Icon :icon="item.icon" class="layout__nav-icon" />
               <span v-show="sidebarOpened || isMobile">{{ t(item.title) }}</span>
               <svg
                 v-show="sidebarOpened || isMobile"
@@ -64,7 +64,7 @@
                 :class="{ 'layout__nav-item--active': isActive(child.path) }"
                 @click="isMobile && closeSidebar()"
               >
-                <component :is="child.icon" class="layout__nav-icon" />
+                <Icon :icon="child.icon" class="layout__nav-icon" />
                 <span v-show="sidebarOpened || isMobile">{{ t(child.title) }}</span>
               </router-link>
             </div>
@@ -77,7 +77,7 @@
             :class="{ 'layout__nav-item--active': isActive(item.path) }"
             @click="isMobile && closeSidebar()"
           >
-            <component :is="item.icon" class="layout__nav-icon" />
+            <Icon :icon="item.icon" class="layout__nav-icon" />
             <span v-show="sidebarOpened || isMobile">{{ t(item.title) }}</span>
           </router-link>
         </template>
@@ -182,7 +182,8 @@
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, onUnmounted, ref, reactive, h, type Component } from 'vue'
+import { computed, onMounted, onUnmounted, ref, reactive } from 'vue'
+import { Icon } from '@iconify/vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
 import LangDropdown from '@/components/LangDropdown.vue'
@@ -203,245 +204,47 @@ const userMenuOpen = ref(false)
 const isMobile = ref(false)
 const expandedMenus = ref<string[]>([])
 
-// Icon components
-const DashboardIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'
-      })
-    ])
-}
-
-const MachineIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z'
-      })
-    ])
-}
-
-const CameraIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z'
-      })
-    ])
-}
-
-const UserIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z'
-      })
-    ])
-}
-
-const StatsIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z'
-      })
-    ])
-}
-
-const AuditIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'
-      })
-    ])
-}
-
-const StreamIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z'
-      }),
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M21 12a9 9 0 11-18 0 9 9 0 0118 0z'
-      })
-    ])
-}
-
-const CloudIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z'
-      })
-    ])
-}
-
-const ConnectionIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0'
-      })
-    ])
-}
-
-const VideoTestIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z'
-      }),
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M21 12a9 9 0 11-18 0 9 9 0 0118 0z'
-      })
-    ])
-}
-
-const LinkIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1'
-      })
-    ])
-}
-
-const FilmIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z'
-      })
-    ])
-}
-
-const LiveIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z'
-      })
-    ])
-}
-
-const SettingIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z'
-      }),
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z'
-      })
-    ])
-}
-
-const TestIcon = {
-  render: () =>
-    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
-      h('path', {
-        'stroke-linecap': 'round',
-        'stroke-linejoin': 'round',
-        'stroke-width': '2',
-        d: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z'
-      })
-    ])
-}
-
+// Menu item interface
 interface MenuItem {
   path: string
   title: string
-  icon: Component
+  icon: string
   children?: MenuItem[]
 }
 
+// Menu configuration with Iconify icon names
 const menuItems: MenuItem[] = [
-  { path: '/', title: '仪表盘', icon: DashboardIcon },
-  { path: '/machine', title: '机器管理', icon: MachineIcon },
-  { path: '/camera', title: '摄像头管理', icon: CameraIcon },
-  { path: '/lss', title: 'LSS 管理', icon: ConnectionIcon },
-  // { path: '/user', title: '用户管理', icon: UserIcon },
-  { path: '/cc', title: 'Cloudflare Stream', icon: CloudIcon },
-  { path: '/webrtc', title: 'WebRTC 流', icon: ConnectionIcon },
-  { path: '/monitor', title: '多视频监控', icon: CameraIcon },
+  { path: '/', title: '仪表盘', icon: 'mdi:view-dashboard' },
+  { path: '/machine', title: '机器管理', icon: 'mdi:monitor' },
+  { path: '/camera', title: '摄像头管理', icon: 'mdi:video' },
+  { path: '/lss', title: 'LSS 管理', icon: 'mdi:power-plug' },
+  { path: '/cc', title: 'Cloudflare Stream', icon: 'mdi:cloud' },
+  { path: '/webrtc', title: 'WebRTC 流', icon: 'mdi:wifi' },
+  { path: '/monitor', title: '多视频监控', icon: 'mdi:video' },
   {
     path: '/demo',
     title: '视频测试',
-    icon: VideoTestIcon,
+    icon: 'mdi:play-circle-outline',
     children: [
-      { path: '/demo/directurl', title: '直接 URL', icon: LinkIcon },
-      { path: '/demo/rtsp', title: 'RTSP 流', icon: ConnectionIcon },
-      { path: '/demo/samples', title: '测试视频', icon: FilmIcon },
-      { path: '/demo/hls', title: 'M3U8/HLS', icon: StreamIcon }
+      { path: '/demo/directurl', title: '直接 URL', icon: 'mdi:link' },
+      { path: '/demo/rtsp', title: 'RTSP 流', icon: 'mdi:wifi' },
+      { path: '/demo/samples', title: '测试视频', icon: 'mdi:filmstrip' },
+      { path: '/demo/hls', title: 'M3U8/HLS', icon: 'mdi:play-circle' }
     ]
   },
   {
     path: '/stream',
     title: 'Stream 管理',
-    icon: StreamIcon,
+    icon: 'mdi:play-circle',
     children: [
-      { path: '/stream/videos', title: '视频管理', icon: FilmIcon },
-      { path: '/stream/live', title: '直播管理', icon: LiveIcon },
-      { path: '/stream/config', title: 'Stream 配置', icon: SettingIcon },
-      { path: '/streamtest', title: '快速测试', icon: TestIcon }
+      { path: '/stream/videos', title: '视频管理', icon: 'mdi:filmstrip' },
+      { path: '/stream/live', title: '直播管理', icon: 'mdi:video-wireless' },
+      { path: '/stream/config', title: 'Stream 配置', icon: 'mdi:cog' },
+      { path: '/streamtest', title: '快速测试', icon: 'mdi:test-tube' }
     ]
   },
-  { path: '/stats', title: '观看统计', icon: StatsIcon },
-  { path: '/audit', title: '审计日志', icon: AuditIcon }
+  { path: '/stats', title: '观看统计', icon: 'mdi:chart-bar' },
+  { path: '/audit', title: '审计日志', icon: 'mdi:file-document' }
 ]
 
 const userInitial = computed(() => {

+ 6 - 6
src/views/lss/index.vue

@@ -89,11 +89,11 @@
     </el-drawer>
 
     <!-- 设备列表抽屉 -->
-    <el-drawer v-model="deviceDrawerVisible" title="設備列表" direction="rtl" size="80%" destroy-on-close>
+    <el-drawer v-model="deviceDrawerVisible" title="设备列表" direction="rtl" size="80%" destroy-on-close>
       <template #header>
         <div class="drawer-header">
-          <span>設備列表</span>
-          <el-button type="primary" :icon="Plus" size="small" @click="handleAddDevice">新增</el-button>
+          <span>设备列表</span>
+          <el-button type="primary" :icon="Plus" @click="handleAddDevice">新增</el-button>
         </div>
       </template>
       <el-table :data="deviceList" stripe size="default" height="100%">
@@ -270,7 +270,7 @@ const mockData: LssDTO[] = [
   {
     id: 2,
     lssId: 'L002',
-    name: '现场-�的谷2',
+    name: '现场-谷2',
     address: '涩谷区道玄坂1-2-3',
     publicIp: '10.72.44.57',
     heartbeat: 'active',
@@ -291,7 +291,7 @@ const mockData: LssDTO[] = [
     id: 4,
     lssId: 'L004',
     name: '现场-池袋4',
-    address: '�的島区南池袋2-5-6',
+    address: '島区南池袋2-5-6',
     publicIp: '10.72.44.59',
     heartbeat: 'dead',
     heartbeatTime: '2026-01-18T12:00:00',
@@ -632,13 +632,13 @@ onMounted(() => {
 // 抽屉样式
 .drawer-header {
   display: flex;
-  justify-content: space-between;
   align-items: center;
   width: 100%;
 
   span {
     font-size: 16px;
     font-weight: 600;
+    margin-right: 10px;
   }
 
   :deep(.el-button--primary) {