|
|
@@ -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(() => {
|