From 69e99d849eb8b7ea9da6679f9a9d8fd03aca415c Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Tue, 25 Aug 2026 14:03:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/components/Sidebar/Link.vue | 14 +- src/layout/components/TopNav/index.vue | 4 +- src/permission.js | 5 +- src/router/index.js | 9 +- src/utils/jsencrypt.js | 2 +- src/views/index.vue | 1070 ++++++++++++++++++++++++ src/views/inspection/point/index.vue | 2 +- vite.config.js | 17 + vite/plugins/auto-import.js | 1 + 9 files changed, 1112 insertions(+), 12 deletions(-) create mode 100644 src/views/index.vue diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue index 8011431..23d874e 100644 --- a/src/layout/components/Sidebar/Link.vue +++ b/src/layout/components/Sidebar/Link.vue @@ -18,19 +18,25 @@ const isExt = computed(() => { return isExternal(props.to) }) +const isNewWindowRoute = computed(() => { + const path = typeof props.to === 'string' ? props.to : props.to?.path + return path === '/inspection/cockpit' +}) + const type = computed(() => { - if (isExt.value) { + if (isExt.value || isNewWindowRoute.value) { return 'a' } return 'router-link' }) function linkProps() { - if (isExt.value) { + if (isExt.value || isNewWindowRoute.value) { + const href = typeof props.to === 'string' ? props.to : props.to?.path return { - href: props.to, + href, target: '_blank', - rel: 'noopener' + rel: 'noopener noreferrer' } } return { diff --git a/src/layout/components/TopNav/index.vue b/src/layout/components/TopNav/index.vue index 3810cfe..c637d17 100644 --- a/src/layout/components/TopNav/index.vue +++ b/src/layout/components/TopNav/index.vue @@ -120,9 +120,9 @@ function setVisibleNumber() { function handleSelect(key, keyPath) { currentIndex.value = key const route = routers.value.find(item => item.path === key) - if (isHttp(key)) { + if (isHttp(key) || key === '/inspection/cockpit') { // http(s):// 路径新窗口打开 - window.open(key, "_blank") + window.open(key, '_blank', 'noopener,noreferrer') } else if (!route || !route.children) { // 没有子路由路径内部打开 const routeMenu = childrenMenus.value.find(item => item.path === key) diff --git a/src/permission.js b/src/permission.js index 5e08a49..3ea5cc7 100644 --- a/src/permission.js +++ b/src/permission.js @@ -19,11 +19,12 @@ const isWhiteList = (path) => { } router.beforeEach(async (to, from) => { - if (to.meta?.noLogin) { + const token = getToken() + if (to.meta?.noLogin && !token) { return true } NProgress.start() - if (getToken()) { + if (token) { to.meta.title && useSettingsStore().setTitle(to.meta.title) const isLock = useLockStore().isLock if (to.path === '/login') { diff --git a/src/router/index.js b/src/router/index.js index 090aa16..9c6e4ab 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -15,9 +15,14 @@ export const constantRoutes = [ { path: '/401', component: commonView('error/401'), hidden: true }, { path: '/:pathMatch(.*)*', component: commonView('error/404'), hidden: true }, { - path: '/', component: Layout, redirect: '/inspection/cockpit', - children: [{ path: '/inspection/cockpit', component: () => import('@/views/inspection/cockpit/index.vue'), name: 'InspectionCockpit', meta: { title: '巡检驾驶舱', noLogin: true } }] + path: '/', component: Layout, redirect: '/index', + children: [{ path: 'index', component: () => import('@/views/index.vue'), name: 'Index', meta: { title: '首页', icon: 'dashboard', affix: true } }] }, + { + path: '/inspection/cockpit', component: () => import('@/views/inspection/cockpit/index.vue'), name: 'InspectionCockpit', + meta: { title: '巡检驾驶舱', noLogin: true } + }, + { path: '/flow', component: commonView('flow/index'), hidden: true, meta: { title: '流程编排', noLogin: true } }, { path: '/lock', component: () => import('@/views/lock'), hidden: true }, { path: '/user', component: Layout, hidden: true, redirect: 'noredirect', diff --git a/src/utils/jsencrypt.js b/src/utils/jsencrypt.js index 78d9523..91eeff6 100644 --- a/src/utils/jsencrypt.js +++ b/src/utils/jsencrypt.js @@ -1,4 +1,4 @@ -import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' +import JSEncrypt from 'jsencrypt' // 密钥对生成 http://web.chacuo.net/netrsakeypair diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..e7eb9db --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,1070 @@ + + + + + + + CMVR 智能测试平台 + + 招商车研机器人智能测试管理系统 + + 连接设备、编排流程、执行测试与巡检任务,集中管理机器人全生命周期测试业务。 + + + + + 流程编排 + + + + 巡检驾驶舱 + + + + + + {{ greeting }},{{ displayName }} + {{ currentTime }} + {{ currentDate }} + + + + + + + SYSTEM OVERVIEW + 系统概览 + + + + + + + + + + + + + + + {{ item.label }} + {{ displayMetric(item.key) }} + {{ item.description }} + + 无权限 + + + + + + + + + LIVE OPERATIONS + 任务运行态 + + + 查看全部 + + + + + + + + + {{ task.taskName || '未命名任务' }} + {{ task.robotName || '暂未分配机器人' }} + + + + {{ inspectionStatus(task.status).label }} + + {{ formatTime(task.startTime || task.createTime) }} + + + + + + + + {{ activeTasksRestricted ? '暂无任务查看权限' : '当前没有运行中的任务' }} + {{ activeTasksRestricted ? '请联系管理员分配巡检任务权限' : '新的巡检任务启动后会显示在这里' }} + + + + + + + CAPABILITIES + 能力中心 + + + + + + + + + + {{ feature.title }} + {{ feature.description }} + + + + + + + + + + + RECENT EXECUTIONS + 最近测试实例 + + + 执行记录 + + + + + + + {{ String(instance.order).padStart(2, '0') }} + + {{ instance.taskName || '未命名测试实例' }} + {{ instance.remark || '机器人测试任务' }} + + {{ formatTime(instance.createTime) }} + + {{ testStatus(instance.status).label }} + + + + + + + {{ recentInstancesRestricted ? '暂无测试实例查看权限' : '暂无测试执行记录' }} + + + + + + + + diff --git a/src/views/inspection/point/index.vue b/src/views/inspection/point/index.vue index eed1f12..6d1fb13 100644 --- a/src/views/inspection/point/index.vue +++ b/src/views/inspection/point/index.vue @@ -406,7 +406,7 @@ function handleDelete(row) { function handleDeploy(row) { const params = { itemId: row.detectItemId, name: row.waypointName, state: row.status }; const fullPath = appendParamsToPath("/flow", params); - const p = window.open(fullPath, "_blank"); + window.open(fullPath, "_blank", "noopener,noreferrer"); } diff --git a/vite.config.js b/vite.config.js index 39bc171..678a256 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,18 @@ import { defineConfig, loadEnv, normalizePath } from 'vite' import path from 'path' +import { realpathSync } from 'node:fs' import createVitePlugins from './vite/plugins' +const isCommonLinked = (() => { + try { + const linkedPath = realpathSync(path.resolve('node_modules/@cmvr/iot-common')) + const localPath = realpathSync(path.resolve('../cmvr-iot-common')) + return path.relative(linkedPath, localPath) === '' + } catch { + return false + } +})() + // https://vitejs.dev/config/ export default defineConfig(({ mode, command }) => { const env = loadEnv(mode, process.cwd()) @@ -12,6 +23,7 @@ export default defineConfig(({ mode, command }) => { plugins: createVitePlugins(env, command === 'build'), optimizeDeps: { exclude: [ + ...(isCommonLinked ? ['@cmvr/iot-common'] : []), 'monaco-editor', 'monaco-editor/esm/vs/base/worker/workerMain.js', 'monaco-editor/esm/vs/editor/editor.main.js', @@ -57,6 +69,11 @@ export default defineConfig(({ mode, command }) => { port: Number(VITE_PORT || 80), host: true, open: true, + ...(isCommonLinked ? { + watch: { + ignored: ['!**/node_modules/@cmvr/iot-common/**'] + } + } : {}), proxy: { // https://cn.vitejs.dev/config/#server-proxy '/dev-api': { diff --git a/vite/plugins/auto-import.js b/vite/plugins/auto-import.js index 1b6ab1d..723d4dc 100644 --- a/vite/plugins/auto-import.js +++ b/vite/plugins/auto-import.js @@ -11,6 +11,7 @@ export default function createAutoImport() { '@/utils/ruoyi': ['selectDictLabel'] } ], + exclude: [/[\\/]cmvr-iot-common[\\/]dist[\\/]/], dts: false }) }
+ 连接设备、编排流程、执行测试与巡检任务,集中管理机器人全生命周期测试业务。 +