diff --git a/src/assets/images/robot.png b/src/assets/images/robot.png index 3d8bae6..21d510f 100644 Binary files a/src/assets/images/robot.png and b/src/assets/images/robot.png differ diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue index 888da01..e3204de 100644 --- a/src/layout/components/AppMain.vue +++ b/src/layout/components/AppMain.vue @@ -37,7 +37,7 @@ function addIframe() { @import "@/assets/styles/variables.module"; .app-main { /* 50= navbar 50 */ - min-height: calc(100vh - 50px); + height: calc(100vh - 84px); padding: 20px; width: 100%; position: relative; @@ -54,7 +54,7 @@ function addIframe() { .hasTagsView { .app-main { /* 84 = navbar + tags-view = 50 + 34 */ - min-height: calc(100vh - 84px); + height: calc(100vh - 84px); } .fixed-header + .app-main { diff --git a/src/main.js b/src/main.js index 58595ea..f52695b 100644 --- a/src/main.js +++ b/src/main.js @@ -1,96 +1,96 @@ -import { createApp } from 'vue' - -import Cookies from 'js-cookie' - -import ElementPlus from 'element-plus' -import 'element-plus/dist/index.css' -import 'element-plus/theme-chalk/dark/css-vars.css' -import locale from 'element-plus/es/locale/lang/zh-cn' - -import '@/assets/styles/index.scss' // global css - -import App from './App' -import store from './store' -import router from './router' -import directive from './directive' // directive -import WebSocketPlugin from './plugins/websocket'; - -// 注册指令 -import plugins from './plugins' // plugins -import { download } from '@/utils/request' - -// svg图标 -import 'virtual:svg-icons-register' -import SvgIcon from '@/components/SvgIcon' -import elementIcons from '@/components/SvgIcon/svgicon' - -import './permission' // permission control - -import { useDict } from '@/utils/dict' -import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' - -// 分页组件 -import Pagination from '@/components/Pagination' -// 自定义表格工具组件 -import RightToolbar from '@/components/RightToolbar' -// 富文本组件 -import Editor from "@/components/Editor" -// 文件上传组件 -import FileUpload from "@/components/FileUpload" -// 图片上传组件 -import ImageUpload from "@/components/ImageUpload" -// 图片预览组件 -import ImagePreview from "@/components/ImagePreview" -// 字典标签组件 -import DictTag from '@/components/DictTag' - -// ace.config.js(独立配置文件) -import ace from 'ace-builds'; -import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url'; -ace.config.setModuleUrl('ace/mode/json', modeJsonUrl); // 动态绑定语法模块路径 - -const app = createApp(App) - -// 全局方法挂载 -app.config.globalProperties.useDict = useDict - app.config.globalProperties.download = download -app.config.globalProperties.parseTime = parseTime -app.config.globalProperties.resetForm = resetForm -app.config.globalProperties.handleTree = handleTree -app.config.globalProperties.addDateRange = addDateRange -app.config.globalProperties.selectDictLabel = selectDictLabel -app.config.globalProperties.selectDictLabels = selectDictLabels - -// 全局组件挂载 -app.component('DictTag', DictTag) -app.component('Pagination', Pagination) -app.component('FileUpload', FileUpload) -app.component('ImageUpload', ImageUpload) -app.component('ImagePreview', ImagePreview) -app.component('RightToolbar', RightToolbar) -app.component('Editor', Editor) - -app.use(router) -app.use(store) -app.use(plugins) -app.use(elementIcons) -app.component('svg-icon', SvgIcon) - -directive(app) - -// 使用element-plus 并且设置全局的大小 -app.use(ElementPlus, { - locale: locale, - // 支持 large、default、small - size: Cookies.get('size') || 'default' -}) - - -app.use(WebSocketPlugin, { - // url: import.meta.env.VITE_WS_URL, - url: 'ws://192.168.0.100:13080/ws', - // url: 'ws://10.148.209.5:13080/ws', - userId: 'your_user_id' -}); - -app.mount('#app') +import { createApp } from 'vue' + +import Cookies from 'js-cookie' + +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import locale from 'element-plus/es/locale/lang/zh-cn' + +import '@/assets/styles/index.scss' // global css + +import App from './App' +import store from './store' +import router from './router' +import directive from './directive' // directive +import WebSocketPlugin from './plugins/websocket'; + +// 注册指令 +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +// svg图标 +import 'virtual:svg-icons-register' +import SvgIcon from '@/components/SvgIcon' +import elementIcons from '@/components/SvgIcon/svgicon' + +import './permission' // permission control + +import { useDict } from '@/utils/dict' +import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi' + +// 分页组件 +import Pagination from '@/components/Pagination' +// 自定义表格工具组件 +import RightToolbar from '@/components/RightToolbar' +// 富文本组件 +import Editor from "@/components/Editor" +// 文件上传组件 +import FileUpload from "@/components/FileUpload" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" +// 字典标签组件 +import DictTag from '@/components/DictTag' + +// ace.config.js(独立配置文件) +import ace from 'ace-builds'; +import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url'; +ace.config.setModuleUrl('ace/mode/json', modeJsonUrl); // 动态绑定语法模块路径 + +const app = createApp(App) + +// 全局方法挂载 +app.config.globalProperties.useDict = useDict + app.config.globalProperties.download = download +app.config.globalProperties.parseTime = parseTime +app.config.globalProperties.resetForm = resetForm +app.config.globalProperties.handleTree = handleTree +app.config.globalProperties.addDateRange = addDateRange +app.config.globalProperties.selectDictLabel = selectDictLabel +app.config.globalProperties.selectDictLabels = selectDictLabels + +// 全局组件挂载 +app.component('DictTag', DictTag) +app.component('Pagination', Pagination) +app.component('FileUpload', FileUpload) +app.component('ImageUpload', ImageUpload) +app.component('ImagePreview', ImagePreview) +app.component('RightToolbar', RightToolbar) +app.component('Editor', Editor) + +app.use(router) +app.use(store) +app.use(plugins) +app.use(elementIcons) +app.component('svg-icon', SvgIcon) + +directive(app) + +// 使用element-plus 并且设置全局的大小 +app.use(ElementPlus, { + locale: locale, + // 支持 large、default、small + size: Cookies.get('size') || 'default' +}) + + +app.use(WebSocketPlugin, { + // url: import.meta.env.VITE_WS_URL, + url: 'ws://192.168.0.100:13080/ws', + // url: 'ws://10.148.209.5:13080/ws', + userId: 'your_user_id' +}); + +app.mount('#app') diff --git a/src/router/index.js b/src/router/index.js index 851112d..87c52a6 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,249 +1,242 @@ -import { createWebHistory, createRouter } from "vue-router"; -/* Layout */ -import Layout from "@/layout"; - -/** - * Note: 路由配置项 - * - * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 - * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 - * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 - * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 - * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 - * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 - * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 - * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 - * roles: ['admin', 'common'] // 访问路由的角色权限 - * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 - * meta : { - noCache: true // 如果设置为true,则不会被 缓存(默认 false) - title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 - icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg - breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 - activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 - } - */ - -// 公共路由 -export const constantRoutes = [ - { - path: "/redirect", - component: Layout, - hidden: true, - children: [ - { - path: "/redirect/:path(.*)", - component: () => import("@/views/redirect/index.vue"), - }, - ], - }, - { - path: "/login", - component: () => import("@/views/login"), - hidden: true, - }, - { - path: "/register", - component: () => import("@/views/register"), - hidden: true, - }, - { - path: "/zhtest", - component: import("@/views/system/user/profile/index"), - // children: [ - // { - // path: "profilettt", - // component: () => import("@/views/system/user/profile/index"), - // name: "Profile", - // meta: { title: "个人中心", icon: "user", hidden: true }, - // }, - // ], - }, - { - path: "/:pathMatch(.*)*", - component: () => import("@/views/error/404"), - hidden: true, - }, - { - path: "/401", - component: () => import("@/views/error/401"), - hidden: true, - }, - { - //机械臂路由 - path: "/mechanical_arm/:id", - component: () => - import("@/views/device/register/components/MechanicalArm/index.vue"), - hidden: true, - }, - // router/index.js - { - path: "/device", - component: Layout, - children: [ - { - path: "camera/:id", - component: () => - import("@/views/device/register/components/Camera/index.vue"), - name: "摄像头示教", - meta: { title: "摄像头详情" }, - hidden: true, - }, - { - path: "hand/:id", - component: () => - import("@/views/device/register/components/DexHand/index.vue"), - name: "灵巧手示教", - meta: { title: "灵巧手详情" }, - hidden: true, - }, - { - path: "speaker/:id", - component: () => - import("@/views/device/register/components/Speaker/index.vue"), - name: "扬声器示教", - meta: { title: "扬声器详情" }, - hidden: true, - }, - ], - }, - { - //语音合成 - path: "/speech_synthesis", - component: () => - import("@/views/device/register/components/SpeechSynthesis/index.vue"), - meta: { title: "语音合成", noLogin: true }, - hidden: true, - }, - { - //触摸交互 - path: "/touch_interaction", - component: () => - import("@/views/device/register/components/TouchInteraction/index.vue"), - meta: { title: "触摸交互", noLogin: true }, - hidden: true, - }, - { - //logicFlow - path: "/logic_flow", - component: () => - import("@/views/device/register/components/LogicFlow/index.vue"), - meta: { title: "logicFlow", noLogin: true }, - hidden: true, - }, - { - //logicFlow - path: "/flow", - component: () => import("@/views/flow/index.vue"), - meta: { title: "flow", noLogin: true }, - hidden: true, - }, - { - //电机路由 - path: "/Motor/:id", - component: () => - import("@/views/device/register/components/Motor/index.vue"), - hidden: true, - }, - { - path: "", - component: Layout, - redirect: "/index", - children: [ - { - path: "/index", - component: () => import("@/views/index"), - name: "Index", - meta: { title: "首页", icon: "dashboard", affix: true }, - }, - ], - }, - -]; - -// 动态路由,基于用户权限动态去加载 -export const dynamicRoutes = [ - { - path: "/system/user-auth", - component: Layout, - hidden: true, - permissions: ["system:user:edit"], - children: [ - { - path: "role/:userId(\\d+)", - component: () => import("@/views/system/user/authRole"), - name: "AuthRole", - meta: { title: "分配角色", activeMenu: "/system/user" }, - }, - ], - }, - { - path: "/system/role-auth", - component: Layout, - hidden: true, - permissions: ["system:role:edit"], - children: [ - { - path: "user/:roleId(\\d+)", - component: () => import("@/views/system/role/authUser"), - name: "AuthUser", - meta: { title: "分配用户", activeMenu: "/system/role" }, - }, - ], - }, - { - path: "/system/dict-data", - component: Layout, - hidden: true, - permissions: ["system:dict:list"], - children: [ - { - path: "index/:dictId(\\d+)", - component: () => import("@/views/system/dict/data"), - name: "Data", - meta: { title: "字典数据", activeMenu: "/system/dict" }, - }, - ], - }, - { - path: "/monitor/job-log", - component: Layout, - hidden: true, - permissions: ["monitor:job:list"], - children: [ - { - path: "index/:jobId(\\d+)", - component: () => import("@/views/monitor/job/log"), - name: "JobLog", - meta: { title: "调度日志", activeMenu: "/monitor/job" }, - }, - ], - }, - { - path: "/tool/gen-edit", - component: Layout, - hidden: true, - permissions: ["tool:gen:edit"], - children: [ - { - path: "index/:tableId(\\d+)", - component: () => import("@/views/tool/gen/editTable"), - name: "GenEdit", - meta: { title: "修改生成配置", activeMenu: "/tool/gen" }, - }, - ], - }, -]; -console.log("constantRoutes", constantRoutes); -const router = createRouter({ - history: createWebHistory(), - routes: constantRoutes, - scrollBehavior(to, from, savedPosition) { - if (savedPosition) { - return savedPosition; - } - return { top: 0 }; - }, -}); - -export default router; +import { createWebHistory, createRouter } from "vue-router"; +/* Layout */ +import Layout from "@/layout"; + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: "/redirect", + component: Layout, + hidden: true, + children: [ + { + path: "/redirect/:path(.*)", + component: () => import("@/views/redirect/index.vue"), + }, + ], + }, + { + path: "/login", + component: () => import("@/views/login"), + hidden: true, + }, + { + path: "/register", + component: () => import("@/views/register"), + hidden: true, + }, + { + path: "/:pathMatch(.*)*", + component: () => import("@/views/error/404"), + hidden: true, + }, + { + path: "/401", + component: () => import("@/views/error/401"), + hidden: true, + }, + { + //机械臂路由 + path: "/mechanical_arm/:id", + component: () => + import("@/views/device/register/components/MechanicalArm/index.vue"), + hidden: true, + }, + // router/index.js + { + path: "/device", + component: Layout, + children: [ + { + path: "camera/:id", + component: () => + import("@/views/device/register/components/Camera/index.vue"), + name: "摄像头示教", + meta: { title: "摄像头详情" }, + hidden: true, + }, + ], + }, + { + path: "/device", + component: Layout, + children: [ + { + path: "hand/:id", + component: () => + import("@/views/device/register/components/DexHand/index.vue"), + name: "灵巧手示教", + meta: { title: "灵巧手详情" }, + hidden: true, + }, + ], + }, + { + path: "speaker/:id", + component: () => + import("@/views/device/register/components/Speaker/index.vue"), + name: "扬声器示教", + meta: { title: "扬声器详情" }, + hidden: true, + }, + { + //语音合成 + path: "/speech_synthesis", + component: () => + import("@/views/device/register/components/SpeechSynthesis/index.vue"), + meta: { title: "语音合成", noLogin: true }, + hidden: true, + }, + { + //触摸交互 + path: "/touch_interaction", + component: () => + import("@/views/device/register/components/TouchInteraction/index.vue"), + meta: { title: "触摸交互", noLogin: true }, + hidden: true, + }, + { + //logicFlow + path: "/logic_flow", + component: () => + import("@/views/device/register/components/LogicFlow/index.vue"), + meta: { title: "logicFlow", noLogin: true }, + hidden: true, + }, + { + //logicFlow + path: "/flow", + component: () => import("@/views/flow/index.vue"), + meta: { title: "flow", noLogin: true }, + hidden: true, + }, + { + //电机路由 + path: "/Motor/:id", + component: () => + import("@/views/device/register/components/Motor/index.vue"), + hidden: true, + }, + { + path: "", + component: Layout, + redirect: "/index", + children: [ + { + path: "/index", + component: () => import("@/views/index"), + name: "Index", + meta: { title: "首页", icon: "dashboard", affix: true }, + }, + ], + }, +]; + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: "/system/user-auth", + component: Layout, + hidden: true, + permissions: ["system:user:edit"], + children: [ + { + path: "role/:userId(\\d+)", + component: () => import("@/views/system/user/authRole"), + name: "AuthRole", + meta: { title: "分配角色", activeMenu: "/system/user" }, + }, + ], + }, + { + path: "/system/role-auth", + component: Layout, + hidden: true, + permissions: ["system:role:edit"], + children: [ + { + path: "user/:roleId(\\d+)", + component: () => import("@/views/system/role/authUser"), + name: "AuthUser", + meta: { title: "分配用户", activeMenu: "/system/role" }, + }, + ], + }, + { + path: "/system/dict-data", + component: Layout, + hidden: true, + permissions: ["system:dict:list"], + children: [ + { + path: "index/:dictId(\\d+)", + component: () => import("@/views/system/dict/data"), + name: "Data", + meta: { title: "字典数据", activeMenu: "/system/dict" }, + }, + ], + }, + { + path: "/monitor/job-log", + component: Layout, + hidden: true, + permissions: ["monitor:job:list"], + children: [ + { + path: "index/:jobId(\\d+)", + component: () => import("@/views/monitor/job/log"), + name: "JobLog", + meta: { title: "调度日志", activeMenu: "/monitor/job" }, + }, + ], + }, + { + path: "/tool/gen-edit", + component: Layout, + hidden: true, + permissions: ["tool:gen:edit"], + children: [ + { + path: "index/:tableId(\\d+)", + component: () => import("@/views/tool/gen/editTable"), + name: "GenEdit", + meta: { title: "修改生成配置", activeMenu: "/tool/gen" }, + }, + ], + }, +]; +console.log("constantRoutes", constantRoutes); +const router = createRouter({ + history: createWebHistory(), + routes: constantRoutes, + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition; + } + return { top: 0 }; + }, +}); + +export default router; diff --git a/src/views/device/register/components/DexHand/LeftBottomHand.vue b/src/views/device/register/components/DexHand/LeftBottomHand.vue index 77b36ea..62cf533 100644 --- a/src/views/device/register/components/DexHand/LeftBottomHand.vue +++ b/src/views/device/register/components/DexHand/LeftBottomHand.vue @@ -21,6 +21,7 @@ const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2'); // 待您修改 const deviceId = ref('hand1'); // 待您修改 const frameData = ref({ count: 0, lastTime: 0 }); const isHandSeries = ref(false); +const containerRef = ref(null) // 矩形定义 const rectangle = [ @@ -362,15 +363,39 @@ onMounted(async () => { await nextTick(); initializeRectangleData(); // 初始化数据 initCanvas(); // 初始化 Canvas + +// 使用 ResizeObserver 监听父容器尺寸变化 + const container = containerRef.value || handCanvas.value?.parentElement; + if (container) { + const resizeObserver = new ResizeObserver(debounce(updateCanvas, 10)); + resizeObserver.observe(container); + // 保存 ResizeObserver 实例以便清理 + data.resizeObserver = resizeObserver; + } else { + console.error('Failed to find container for ResizeObserver'); + } + const debouncedResize = debounce(updateCanvas, 10); window.addEventListener('resize', debouncedResize); + data.debouncedResize = debouncedResize; // 保存以便清理 + + + }); onUnmounted(() => { - window.removeEventListener('resize', debounce(updateCanvas, 10)); - // 如果流正在启动 + // 清理 ResizeObserver + if (data.resizeObserver) { + data.resizeObserver.disconnect(); + delete data.resizeObserver; + } + // 清理 window 的 resize 事件 + if (data.debouncedResize) { + window.removeEventListener('resize', data.debouncedResize); + delete data.debouncedResize; + } + // 清理 WebSocket 订阅 if (isHandSeries.value) { - // 关闭 WebSocket 通道 subscribeSensorData(false); } }); diff --git a/src/views/device/register/components/DexHand/LeftTopHand.vue b/src/views/device/register/components/DexHand/LeftTopHand.vue index f8d6c62..afcb7e7 100644 --- a/src/views/device/register/components/DexHand/LeftTopHand.vue +++ b/src/views/device/register/components/DexHand/LeftTopHand.vue @@ -32,6 +32,7 @@ const emit = defineEmits(['update:topSeriesData']); const loading = ref(false); const handImage = ref(null); +const containerRef = ref(null); // 新增:引用父容器 const defaultHandImageInfo = { width: 891, height: 981 }; const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height; @@ -114,16 +115,39 @@ const updateToSeriesData = () => { emit('update:topSeriesData', newTopSeriesData); }); }; - +const data = ref({ + debouncedResize: null, + resizeObserver: null, +}); onMounted(() => { updateSliderPositions(); + // 使用 ResizeObserver 监听父容器尺寸变化 + const container = containerRef.value || handImage.value?.parentElement; + if (container) { + const resizeObserver = new ResizeObserver(debounce(updateSliderPositions, 10)); + resizeObserver.observe(container); + data.value.resizeObserver = resizeObserver; + } else { + console.error('Failed to find container for ResizeObserver'); + } + const debouncedResize = debounce(updateSliderPositions, 0); window.addEventListener('resize', debouncedResize); + data.value.debouncedResize = debouncedResize; updateToSeriesData(); }); onUnmounted(() => { - window.removeEventListener('resize', debounce(updateSliderPositions, 0)); + // 清理 ResizeObserver + if (data.value.resizeObserver) { + data.value.resizeObserver.disconnect(); + data.value.resizeObserver = null; + } + // 清理 window 的 resize 事件 + if (data.value.debouncedResize) { + window.removeEventListener('resize', data.value.debouncedResize); + data.value.debouncedResize = null; + } }); diff --git a/src/views/device/register/components/DexHand/RightBottomCharts.vue b/src/views/device/register/components/DexHand/RightBottomCharts.vue index 72d54bb..bc42122 100644 --- a/src/views/device/register/components/DexHand/RightBottomCharts.vue +++ b/src/views/device/register/components/DexHand/RightBottomCharts.vue @@ -1,5 +1,5 @@