feat: 连续对话
This commit is contained in:
commit
e221f8d978
Binary file not shown.
|
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 270 KiB |
@ -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 {
|
||||
|
||||
192
src/main.js
192
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')
|
||||
|
||||
@ -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' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
||||
* meta : {
|
||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 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' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
||||
* meta : {
|
||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div style="width: 100%;height: 100%; margin: 0 auto;">
|
||||
<div ref="containerRef" style="width: 100%;height: 100%; margin: 0 auto;">
|
||||
<div class="tabs">
|
||||
<div
|
||||
v-for="(tab, index) in tabs"
|
||||
@ -48,6 +48,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const chart = ref(null);
|
||||
const containerRef = ref(null); // 新增:引用父容器
|
||||
const activeTab = ref('avg'); // 默认选中平均值
|
||||
|
||||
// Tab 配置
|
||||
@ -58,6 +59,15 @@ const tabs = [
|
||||
|
||||
let myChart;
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (func, delay) => {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func.apply(null, args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
const initChart = () => {
|
||||
if (!chart.value) {
|
||||
console.error('Chart element is null');
|
||||
@ -127,22 +137,55 @@ watch([() => props.maxData, () => props.avgData, activeTab], () => {
|
||||
updateChart(activeTab.value === 'max' ? props.maxData : props.avgData);
|
||||
});
|
||||
|
||||
const data = ref({
|
||||
resizeObserver: null,
|
||||
debouncedResize: null,
|
||||
});
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
updateChart(activeTab.value === 'max' ? props.maxData : props.avgData);
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
myChart?.resize();
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// 使用 ResizeObserver 监听父容器尺寸变化
|
||||
const container = containerRef.value;
|
||||
if (container) {
|
||||
const resizeObserver = new ResizeObserver(debounce(() => {
|
||||
if (myChart) {
|
||||
console.log('Container resized:', container.offsetWidth, container.offsetHeight);
|
||||
myChart.resize();
|
||||
}
|
||||
}, 10));
|
||||
resizeObserver.observe(container);
|
||||
data.value.resizeObserver = resizeObserver;
|
||||
} else {
|
||||
console.error('Failed to find container for ResizeObserver');
|
||||
}
|
||||
|
||||
// 保留 window 的 resize 事件监听
|
||||
const debouncedResize = debounce(() => {
|
||||
if (myChart) {
|
||||
myChart.resize();
|
||||
}
|
||||
}, 10);
|
||||
window.addEventListener('resize', debouncedResize);
|
||||
data.value.debouncedResize = debouncedResize;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理图表实例和事件监听器
|
||||
myChart?.dispose();
|
||||
window.removeEventListener('resize', () => {
|
||||
myChart?.resize();
|
||||
});
|
||||
// 清理图表实例
|
||||
if (myChart) {
|
||||
myChart.dispose();
|
||||
myChart = null;
|
||||
}
|
||||
// 清理 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;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="right-chart-panel">
|
||||
<div class="right-chart-panel" ref="containerRef">
|
||||
<div ref="topChart" class="chart"></div>
|
||||
</div>
|
||||
</template>
|
||||
@ -42,8 +42,18 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const topChart = ref(null);
|
||||
const containerRef = ref(null); // 新增:引用父容器
|
||||
let chart1Instance = null;
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (func, delay) => {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func.apply(null, args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
const topChartData = ref({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
@ -62,27 +72,64 @@ const topChartData = ref({
|
||||
series: [{ data: props.topSeriesData, type: 'bar' }],
|
||||
});
|
||||
|
||||
const data = ref({
|
||||
resizeObserver: null,
|
||||
debouncedResize: null,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
if (!topChart.value) {
|
||||
console.error('Chart element is null');
|
||||
return;
|
||||
}
|
||||
chart1Instance = echarts.init(topChart.value);
|
||||
chart1Instance.setOption(topChartData.value);
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
chart1Instance?.resize();
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
// 使用 ResizeObserver 监听父容器尺寸变化
|
||||
const container = containerRef.value;
|
||||
if (container) {
|
||||
const resizeObserver = new ResizeObserver(debounce(() => {
|
||||
if (chart1Instance) {
|
||||
console.log('Container resized:', container.offsetWidth, container.offsetHeight);
|
||||
chart1Instance.resize();
|
||||
}
|
||||
}, 10));
|
||||
resizeObserver.observe(container);
|
||||
data.value.resizeObserver = resizeObserver;
|
||||
} else {
|
||||
console.error('Failed to find container for ResizeObserver');
|
||||
}
|
||||
|
||||
// 保留 window 的 resize 事件监听
|
||||
const debouncedResize = debounce(() => {
|
||||
if (chart1Instance) {
|
||||
chart1Instance.resize();
|
||||
}
|
||||
}, 10);
|
||||
window.addEventListener('resize', debouncedResize);
|
||||
data.value.debouncedResize = debouncedResize;
|
||||
} catch (error) {
|
||||
console.error('图表初始化失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理图表实例和事件监听器
|
||||
chart1Instance?.dispose();
|
||||
window.removeEventListener('resize', () => {
|
||||
chart1Instance?.resize();
|
||||
});
|
||||
// 清理图表实例
|
||||
if (chart1Instance) {
|
||||
chart1Instance.dispose();
|
||||
chart1Instance = null;
|
||||
}
|
||||
// 清理 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;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听 props.topSeriesData
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
<!-- index.vue -->
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="left-panel">
|
||||
@ -9,23 +10,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<RightCharts :bottom-series-data="bottomSeriesData" />
|
||||
<RightBottomCharts :max-data="maxData" :avg-data="avgData" style="height: 50%" />
|
||||
<RightTopCharts :top-series-data="topSeriesData" style="height: 50%;" />
|
||||
<RightBottomCharts :max-data="maxData" :avg-data="avgData" style="height: 50%;" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import RightCharts from './RightTopCharts.vue';
|
||||
import RightTopCharts from './RightTopCharts.vue';
|
||||
import LeftTopHand from './LeftTopHand.vue';
|
||||
import LeftBottomHand from './LeftBottomHand.vue'
|
||||
import LeftBottomHand from './LeftBottomHand.vue';
|
||||
import RightBottomCharts from './RightBottomCharts.vue';
|
||||
|
||||
|
||||
const topSeriesData = ref([0, 0, 0, 0, 0, 0]);
|
||||
const bottomSeriesData = ref([0, 0, 0, 0, 0, 0]);
|
||||
|
||||
const maxData = ref({
|
||||
thumb: { end: 0, tip: 0, middle: 0, palm: 0 },
|
||||
indexFinger: { end: 0, tip: 0, middle: 0 },
|
||||
@ -43,27 +42,28 @@ const avgData = ref({
|
||||
palm: { touch: 0 }
|
||||
});
|
||||
|
||||
|
||||
// 处理更新事件
|
||||
const handleUpdateTopSeriesData = (newData) => {
|
||||
topSeriesData.value = newData;
|
||||
};
|
||||
|
||||
// 处理更新事件
|
||||
const handleUpdateBottomSeriesData = ({ maxData, avgData }) => {
|
||||
maxData.value = newData;
|
||||
avgData.value = avgData;
|
||||
const handleUpdateBottomSeriesData = ({ maxData: newMaxData, avgData: newAvgData }) => {
|
||||
maxData.value = newMaxData;
|
||||
avgData.value = newAvgData;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1; /* 占据父容器所有可用空间 */
|
||||
width: 100%;
|
||||
height: 100%; /* 占满父容器高度 */
|
||||
min-height: 0; /* 防止 flex 子项最小高度问题 */
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
overflow: hidden; /* 防止内容溢出 */
|
||||
box-sizing: border-box; /* 包含 padding 和 border */
|
||||
padding: 20px; /* 与父容器一致,避免双重 padding */
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
@ -72,12 +72,16 @@ const handleUpdateBottomSeriesData = ({ maxData, avgData }) => {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
height: 100%; /* 占满 container 高度 */
|
||||
padding: 0; /* 移除子面板内边距,交给 container 处理 */
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hand-container {
|
||||
position: relative;
|
||||
height: 50%;
|
||||
height: 50%; /* 每个 hand-container 占左面板的 50% 高度 */
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,565 @@
|
||||
<script setup>
|
||||
import {useRoute} from "vue-router"
|
||||
import dayjs from "dayjs";
|
||||
import {deviceTeach, deviceUpdateProfile} from "@/api/device/register.js";
|
||||
import {formatNumber} from "@/views/device/register/components/index.js";
|
||||
import {convertNumbersToStringAndRemoveNulls} from "@/utils/fn.js";
|
||||
|
||||
const data = reactive({
|
||||
form: {
|
||||
workModule: 1,//工作模式
|
||||
posStep: 1,//位置步进
|
||||
postureStep: 1,//姿态步进
|
||||
jointStep: 1,//关节步进
|
||||
joint1: 0.000012,//关节1
|
||||
joint2: 0.000012,//
|
||||
joint3: 0.000012,//
|
||||
joint4: 0.000012,//
|
||||
joint5: 0.000012,//
|
||||
joint6: 0.000012,//关节6
|
||||
x: 11,//
|
||||
y: 11,//
|
||||
z: 11,//
|
||||
rx: 11,//
|
||||
ry: 11,//
|
||||
rz: 11,//
|
||||
arm: 1,//目标
|
||||
coordinate: 1,//坐标系
|
||||
},
|
||||
speed: 20,//速度
|
||||
zeroPos: {//零位姿态
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
rz: 0,
|
||||
},
|
||||
initPos: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1,
|
||||
rx: 1,
|
||||
ry: 1,
|
||||
rz: 1,
|
||||
},//初始姿态
|
||||
armList: [
|
||||
{
|
||||
label: '机械臂1',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '机械臂2',
|
||||
value: 2
|
||||
}
|
||||
],
|
||||
coordinateList: [
|
||||
{
|
||||
label: 'x轴',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'y轴',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: 'z轴',
|
||||
value: 3
|
||||
}
|
||||
],
|
||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||||
})
|
||||
|
||||
const {
|
||||
form, time,
|
||||
zeroPos,
|
||||
initPos,
|
||||
armList,
|
||||
coordinateList
|
||||
} = toRefs(data)
|
||||
|
||||
const route = useRoute()
|
||||
let timer = null
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
//获取机械臂参数
|
||||
const getDeviceInfo = () => {
|
||||
deviceTeach(devId.value).then((res) => {
|
||||
let data = formatNumber(res.data)//字符类型转数字类型
|
||||
Object.assign(form.value, data)
|
||||
//设置初始位置
|
||||
initPos.value.x = data.x
|
||||
initPos.value.y = data.y
|
||||
initPos.value.z = data.z
|
||||
initPos.value.rx = data.rx
|
||||
initPos.value.ry = data.ry
|
||||
initPos.value.rz = data.rz
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDeviceInfo()
|
||||
const posControls = document.querySelectorAll(".btn")
|
||||
//监听按钮点击事件
|
||||
posControls.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const target = e.currentTarget.classList[1]
|
||||
const vals = target.split("_")//x_0 rx_0
|
||||
const pos = vals[0]
|
||||
const calc = !!Number(vals[1])
|
||||
if (calc) {
|
||||
form.value[pos] += 0.01 * data.speed
|
||||
} else {
|
||||
form.value[pos] -= 0.01 * data.speed
|
||||
}
|
||||
})
|
||||
})
|
||||
//监听失焦事件
|
||||
// const warp = document.querySelector(".mechanical-arm")
|
||||
// warp.addEventListener('focusout', handleFocus, false)
|
||||
|
||||
timer = setInterval(() => {
|
||||
time.value = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||
getDeviceInfo()
|
||||
}, 1000)
|
||||
})
|
||||
watch(() => JSON.parse(JSON.stringify(form.value)), (newData, oldData) => {
|
||||
// const change = findDifferences(oldData, newData)
|
||||
const data = convertNumbersToStringAndRemoveNulls(newData)
|
||||
deviceUpdateProfile({idDeDeviceRegistration: devId.value, ...data}).then((res)=>{
|
||||
console.log(res)
|
||||
})
|
||||
})
|
||||
|
||||
const handleFocus = (e) => {
|
||||
const type = e.target.type
|
||||
if (type === "number") {
|
||||
// console.log("数字框失焦")
|
||||
}
|
||||
}
|
||||
|
||||
const devId = computed(() => {
|
||||
return route.path.split("/")[2]
|
||||
})
|
||||
const deviceModel = computed(() => {
|
||||
return route.path.split("/")[1]
|
||||
})
|
||||
|
||||
const handleResets = (type) => {
|
||||
if (type === "zero") {
|
||||
Object.assign(form.value, zeroPos.value)
|
||||
return
|
||||
}
|
||||
if (type === "init") {
|
||||
Object.assign(form.value, initPos.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mechanical-arm">
|
||||
<div class="top flex between">
|
||||
<div class="left " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">机械臂</div>
|
||||
<div class="body">机械臂图片</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center" style="width: 22%">
|
||||
<div class="card">
|
||||
<div class="title">工作模式</div>
|
||||
<div class="body">
|
||||
<el-radio-group v-model="form.workModule">
|
||||
<el-radio :value="1">真实机械臂</el-radio>
|
||||
<el-radio :value="2">仿真机械臂</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="margin-top: 10px">
|
||||
<div class="title">步进模式</div>
|
||||
<div class="mb10">
|
||||
<div class="mb5 action-label">位置步进( mm )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.posStep" :step="2"/>
|
||||
</div>
|
||||
|
||||
<div class="mb10">
|
||||
<div class="mb5 action-label">姿态步进( deg )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.postureStep" :step="2"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb5 action-label">关节步进( deg )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.jointStep" :step="2"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">位置控制</div>
|
||||
<div class="body">
|
||||
<div class="btn-wrap pos-control" style="transform: rotateX(40deg);height: calc(100% - 70px)">
|
||||
<div class="btn z_0">
|
||||
<span>Z-</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn x_0">
|
||||
<span> X-</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn z_1">
|
||||
<span>Z+</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn y_0">
|
||||
<span>Y-</span>
|
||||
<svg t="1747126605366" class="turn-left" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn y_1">
|
||||
<span>Y+</span>
|
||||
<svg t="1747126605366" class="turn-right" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn x_1">
|
||||
<span>X+</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb10 action-label">参考坐标系</div>
|
||||
<el-select
|
||||
|
||||
v-model="form.coordinate"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in coordinateList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom flex between">
|
||||
<div class="left " style="width: 30%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">机械臂位置姿态</div>
|
||||
<div class="body">
|
||||
<div class="flex mb10">
|
||||
<div class="left flex1">
|
||||
<div class="pos-title mb10">位置 ( m )</div>
|
||||
<div class="mb10"> X: {{ form.x.toFixed(6) }}</div>
|
||||
<div class="mb10"> Y: {{ form.y.toFixed(6) }}</div>
|
||||
<div class="mb10"> Z: {{ form.z.toFixed(6) }}</div>
|
||||
</div>
|
||||
<div class="right flex1">
|
||||
<div class="pos-title mb10">姿态 ( deg )</div>
|
||||
<div class="mb10"> RX: {{ form.rx.toFixed(6) }}</div>
|
||||
<div class="mb10"> RY: {{ form.ry.toFixed(6) }}</div>
|
||||
<div class="mb10"> RZ: {{ form.rz.toFixed(6) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb10 action-label">目标</div>
|
||||
<el-select
|
||||
v-model="form.arm"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in armList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center " style="width: 30%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">关节控制单位( deg )</div>
|
||||
<div class="body">
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节1</div>
|
||||
<el-input-number v-model="form.joint1" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节2</div>
|
||||
<el-input-number v-model="form.joint2" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节3</div>
|
||||
<el-input-number v-model="form.joint3" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节4</div>
|
||||
<el-input-number v-model="form.joint4" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节5</div>
|
||||
<el-input-number v-model="form.joint5" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节6</div>
|
||||
<el-input-number v-model="form.joint6" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">姿态控制</div>
|
||||
<div class="body">
|
||||
<div class="btn-wrap">
|
||||
<div class="btn rz_0 c">
|
||||
<span>Z-</span>
|
||||
<svg t="1747126605366" class="turn-lb" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn rx_0 c">
|
||||
<span>X-</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn rz_1 c">
|
||||
<span>Z+</span>
|
||||
<svg t="1747126605366" class="turn-rb" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn ry_0 c">
|
||||
<span> Y-</span>
|
||||
<svg t="1747126605366" class="turn-left" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn ry_1 c">
|
||||
<span>Y+</span>
|
||||
<svg t="1747126605366" class="turn-right" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn rx_1 c">
|
||||
<span> X+</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions flex align-center between">
|
||||
<div class="left">
|
||||
<el-button @click="handleResets('zero')">零位姿态</el-button>
|
||||
<el-button @click="handleResets('init')">初始位姿</el-button>
|
||||
</div>
|
||||
<div class="center">
|
||||
{{ time }}
|
||||
</div>
|
||||
<div class="right flex align-center">
|
||||
<div style="width: 100px;user-select: none">速度</div>
|
||||
<el-slider size="small" :show-tooltip="false" v-model="data.speed"/>
|
||||
<div style="margin-left: 20px"> {{ data.speed }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mechanical-arm {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.top {
|
||||
padding: 10px 10px 0;
|
||||
height: 48%;
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.card:last-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
padding: 0 10px;
|
||||
height: 44%;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.actions {
|
||||
height: 6%;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
|
||||
padding: 0 10px;
|
||||
|
||||
.right {
|
||||
width: 300px;
|
||||
|
||||
}
|
||||
|
||||
.center {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.action-label-pos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.el-input-number {
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
padding: 10px;
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 900;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: calc(100% - 29px);
|
||||
overflow-y: auto;
|
||||
|
||||
.pos-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
transform-origin: center center;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.turn-lb {
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.turn-rb {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.turn-left {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.turn-right {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.turn-bottom {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@ -1,33 +1,34 @@
|
||||
<script setup>
|
||||
import {useRoute} from "vue-router"
|
||||
import { reactive, toRefs, onMounted, onUnmounted, watch, computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import dayjs from "dayjs";
|
||||
import {deviceTeach, deviceUpdateProfile} from "@/api/device/register.js";
|
||||
import {formatNumber} from "@/views/device/register/components/index.js";
|
||||
import {convertNumbersToStringAndRemoveNulls} from "@/utils/fn.js";
|
||||
import { deviceTeach, deviceUpdateProfile } from "@/api/device/register.js";
|
||||
import { formatNumber } from "@/views/device/register/components/index.js";
|
||||
import { convertNumbersToStringAndRemoveNulls } from "@/utils/fn.js";
|
||||
|
||||
const data = reactive({
|
||||
form: {
|
||||
workModule: 1,//工作模式
|
||||
posStep: 1,//位置步进
|
||||
postureStep: 1,//姿态步进
|
||||
jointStep: 1,//关节步进
|
||||
joint1: 0.000012,//关节1
|
||||
joint2: 0.000012,//
|
||||
joint3: 0.000012,//
|
||||
joint4: 0.000012,//
|
||||
joint5: 0.000012,//
|
||||
joint6: 0.000012,//关节6
|
||||
x: 11,//
|
||||
y: 11,//
|
||||
z: 11,//
|
||||
rx: 11,//
|
||||
ry: 11,//
|
||||
rz: 11,//
|
||||
arm: 1,//目标
|
||||
coordinate: 1,//坐标系
|
||||
workModule: 1,
|
||||
posStep: 1,
|
||||
postureStep: 1,
|
||||
jointStep: 1,
|
||||
joint1: 0.000012,
|
||||
joint2: 0.000012,
|
||||
joint3: 0.000012,
|
||||
joint4: 0.000012,
|
||||
joint5: 0.000012,
|
||||
joint6: 0.000012,
|
||||
x: 11,
|
||||
y: 11,
|
||||
z: 11,
|
||||
rx: 11,
|
||||
ry: 11,
|
||||
rz: 11,
|
||||
arm: 1,
|
||||
coordinate: 1,
|
||||
},
|
||||
speed: 20,//速度
|
||||
zeroPos: {//零位姿态
|
||||
speed: 20,
|
||||
zeroPos: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
@ -42,388 +43,217 @@ const data = reactive({
|
||||
rx: 1,
|
||||
ry: 1,
|
||||
rz: 1,
|
||||
},//初始姿态
|
||||
},
|
||||
armList: [
|
||||
{
|
||||
label: '机械臂1',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: '机械臂2',
|
||||
value: 2
|
||||
}
|
||||
],
|
||||
coordinateList: [
|
||||
{
|
||||
label: 'x轴',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'y轴',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: 'z轴',
|
||||
value: 3
|
||||
}
|
||||
{ label: '目标1', value: 1 },
|
||||
{ label: '目标2', value: 2 },
|
||||
{ label: '目标3', value: 3 },
|
||||
],
|
||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||||
})
|
||||
});
|
||||
|
||||
const {
|
||||
form, time,
|
||||
zeroPos,
|
||||
initPos,
|
||||
armList,
|
||||
coordinateList
|
||||
} = toRefs(data)
|
||||
const { form, time, zeroPos, initPos, armList } = toRefs(data);
|
||||
|
||||
const route = useRoute()
|
||||
let timer = null
|
||||
const route = useRoute();
|
||||
let timer = null;
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
clearInterval(timer);
|
||||
});
|
||||
|
||||
//获取机械臂参数
|
||||
const getDeviceInfo = () => {
|
||||
deviceTeach(devId.value).then((res) => {
|
||||
let data = formatNumber(res.data)//字符类型转数字类型
|
||||
Object.assign(form.value, data)
|
||||
//设置初始位置
|
||||
initPos.value.x = data.x
|
||||
initPos.value.y = data.y
|
||||
initPos.value.z = data.z
|
||||
initPos.value.rx = data.rx
|
||||
initPos.value.ry = data.ry
|
||||
initPos.value.rz = data.rz
|
||||
})
|
||||
}
|
||||
let dataRes = formatNumber(res.data);
|
||||
Object.assign(form.value, dataRes);
|
||||
initPos.value.x = dataRes.x;
|
||||
initPos.value.y = dataRes.y;
|
||||
initPos.value.z = dataRes.z;
|
||||
initPos.value.rx = dataRes.rx;
|
||||
initPos.value.ry = dataRes.ry;
|
||||
initPos.value.rz = dataRes.rz;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDeviceInfo()
|
||||
const posControls = document.querySelectorAll(".btn")
|
||||
//监听按钮点击事件
|
||||
getDeviceInfo();
|
||||
const posControls = document.querySelectorAll(".pos-btn");
|
||||
posControls.forEach((item) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const target = e.currentTarget.classList[1]
|
||||
const vals = target.split("_")//x_0 rx_0
|
||||
const pos = vals[0]
|
||||
const calc = !!Number(vals[1])
|
||||
if (calc) {
|
||||
form.value[pos] += 0.01 * data.speed
|
||||
} else {
|
||||
form.value[pos] -= 0.01 * data.speed
|
||||
}
|
||||
})
|
||||
})
|
||||
//监听失焦事件
|
||||
// const warp = document.querySelector(".mechanical-arm")
|
||||
// warp.addEventListener('focusout', handleFocus, false)
|
||||
const target = e.currentTarget.dataset.target;
|
||||
const [pos, direction] = target.split("_");
|
||||
const step = (pos === 'x' || pos === 'y' || pos === 'z') ? form.value.posStep : form.value.postureStep;
|
||||
const delta = direction === 'plus' ? step : -step;
|
||||
form.value[pos] += delta * (data.speed / 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
timer = setInterval(() => {
|
||||
time.value = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||
getDeviceInfo()
|
||||
}, 1000)
|
||||
})
|
||||
watch(() => JSON.parse(JSON.stringify(form.value)), (newData, oldData) => {
|
||||
// const change = findDifferences(oldData, newData)
|
||||
const data = convertNumbersToStringAndRemoveNulls(newData)
|
||||
deviceUpdateProfile({idDeDeviceRegistration: devId.value, ...data}).then((res)=>{
|
||||
console.log(res)
|
||||
})
|
||||
})
|
||||
|
||||
const handleFocus = (e) => {
|
||||
const type = e.target.type
|
||||
if (type === "number") {
|
||||
// console.log("数字框失焦")
|
||||
}
|
||||
}
|
||||
watch(() => JSON.parse(JSON.stringify(form.value)), (newData) => {
|
||||
const dataToSend = convertNumbersToStringAndRemoveNulls(newData);
|
||||
deviceUpdateProfile({ idDeDeviceRegistration: devId.value, ...dataToSend }).then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
});
|
||||
|
||||
const devId = computed(() => {
|
||||
return route.path.split("/")[2]
|
||||
})
|
||||
const deviceModel = computed(() => {
|
||||
return route.path.split("/")[1]
|
||||
})
|
||||
return route.path.split("/")[2];
|
||||
});
|
||||
|
||||
const handleResets = (type) => {
|
||||
if (type === "zero") {
|
||||
Object.assign(form.value, zeroPos.value)
|
||||
return
|
||||
Object.assign(form.value, zeroPos.value);
|
||||
return;
|
||||
}
|
||||
if (type === "init") {
|
||||
Object.assign(form.value, initPos.value)
|
||||
Object.assign(form.value, initPos.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mechanical-arm">
|
||||
<div class="top flex between">
|
||||
<div class="left " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">机械臂</div>
|
||||
<div class="body">机械臂图片</div>
|
||||
<div class="top card">
|
||||
<div class="controls flex align-center" style="justify-content: flex-start;">
|
||||
<div class="control-label">目标</div>
|
||||
<el-select v-model="form.arm" placeholder="选择目标" style="width: 100px; margin-right: 20px;">
|
||||
<el-option v-for="item in armList" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-button @click="handleResets('zero')" style="margin-right: 10px;">零位姿态</el-button>
|
||||
<el-button @click="handleResets('init')">初始位置</el-button>
|
||||
</div>
|
||||
<div class="pose-info flex align-center">
|
||||
<div class="pose-item">X: {{ form.x.toFixed(6) }} m</div>
|
||||
<div class="pose-item">Y: {{ form.y.toFixed(6) }} m</div>
|
||||
<div class="pose-item">Z: {{ form.z.toFixed(6) }} m</div>
|
||||
<div class="pose-item">RX: {{ form.rx.toFixed(6) }} deg</div>
|
||||
<div class="pose-item">RY: {{ form.ry.toFixed(6) }} deg</div>
|
||||
<div class="pose-item">RZ: {{ form.rz.toFixed(6) }} deg</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom flex">
|
||||
<div class="left card" style="flex: 1;">
|
||||
<div class="title">姿态与位置控制</div>
|
||||
<div class="control-buttons">
|
||||
<div class="axis-controls flex">
|
||||
<div class="axis-group">
|
||||
<span>X: {{ form.x.toFixed(6) }} m</span>
|
||||
<button class="pos-btn" data-target="x_minus">-</button>
|
||||
<button class="pos-btn" data-target="x_plus">+</button>
|
||||
</div>
|
||||
<div class="axis-group">
|
||||
<span>Y: {{ form.y.toFixed(6) }} m</span>
|
||||
<button class="pos-btn" data-target="y_minus">-</button>
|
||||
<button class="pos-btn" data-target="y_plus">+</button>
|
||||
</div>
|
||||
<div class="axis-group">
|
||||
<span>Z: {{ form.z.toFixed(6) }} m</span>
|
||||
<button class="pos-btn" data-target="z_minus">-</button>
|
||||
<button class="pos-btn" data-target="z_plus">+</button>
|
||||
</div>
|
||||
<div class="axis-group">
|
||||
<span>RX: {{ form.rx.toFixed(6) }} deg</span>
|
||||
<button class="pos-btn rotation" data-target="rx_minus">
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="pos-btn rotation" data-target="rx_plus">
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="axis-group">
|
||||
<span>RY: {{ form.ry.toFixed(6) }} deg</span>
|
||||
<button class="pos-btn rotation" data-target="ry_minus">
|
||||
<svg t="1747126605366" class="turn-left" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="pos-btn rotation" data-target="ry_plus">
|
||||
<svg t="1747126605366" class="turn-right" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="axis-group">
|
||||
<span>RZ: {{ form.rz.toFixed(6) }} deg</span>
|
||||
<button class="pos-btn rotation" data-target="rz_minus">
|
||||
<svg t="1747126605366" class="turn-lb" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="pos-btn rotation" data-target="rz_plus">
|
||||
<svg t="1747126605366" class="turn-rb" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1461" width="20" height="20">
|
||||
<path d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z" fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="coordinate-system">
|
||||
<svg width="400" height="400" viewBox="0 0 400 400" class="coordinate-svg">
|
||||
<!-- X axis (positive) -->
|
||||
<line x1="200" y1="200" x2="350" y2="200" stroke="#f00" stroke-width="4" />
|
||||
<text x="360" y="205" fill="#f00">X</text>
|
||||
<!-- X axis (negative) -->
|
||||
<line x1="200" y1="200" x2="50" y2="200" stroke="#f00" stroke-width="4" />
|
||||
<text x="40" y="205" fill="#f00">-X</text>
|
||||
<!-- Y axis (positive) -->
|
||||
<line x1="200" y1="200" x2="200" y2="50" stroke="#0f0" stroke-width="4" />
|
||||
<text x="195" y="40" fill="#0f0">Y</text>
|
||||
<!-- Y axis (negative) -->
|
||||
<line x1="200" y1="200" x2="200" y2="350" stroke="#0f0" stroke-width="4" />
|
||||
<text x="195" y="360" fill="#0f0">-Y</text>
|
||||
<!-- Z axis (positive) -->
|
||||
<line x1="200" y1="200" x2="300" y2="100" stroke="#00f" stroke-width="4" />
|
||||
<text x="305" y="95" fill="#00f">Z</text>
|
||||
<!-- Z axis (negative) -->
|
||||
<line x1="200" y1="200" x2="100" y2="300" stroke="#00f" stroke-width="4" />
|
||||
<text x="95" y="305" fill="#00f">-Z</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center" style="width: 22%">
|
||||
<div class="card">
|
||||
<div class="title">工作模式</div>
|
||||
<div class="body">
|
||||
<el-radio-group v-model="form.workModule">
|
||||
<el-radio :value="1">真实机械臂</el-radio>
|
||||
<el-radio :value="2">仿真机械臂</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="right card" style="flex: 1;">
|
||||
<div class="title">关节控制 (deg)</div>
|
||||
<div class="input-list">
|
||||
<div class="input-item">
|
||||
<span>关节1</span>
|
||||
<el-input-number v-model="form.joint1" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="margin-top: 10px">
|
||||
<div class="title">步进模式</div>
|
||||
<div class="mb10">
|
||||
<div class="mb5 action-label">位置步进( mm )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.posStep" :step="2"/>
|
||||
<div class="input-item">
|
||||
<span>关节2</span>
|
||||
<el-input-number v-model="form.joint2" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
|
||||
<div class="mb10">
|
||||
<div class="mb5 action-label">姿态步进( deg )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.postureStep" :step="2"/>
|
||||
<div class="input-item">
|
||||
<span>关节3</span>
|
||||
<el-input-number v-model="form.joint3" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb5 action-label">关节步进( deg )</div>
|
||||
<el-input-number style="width: 100%;" v-model="form.jointStep" :step="2"/>
|
||||
<div class="input-item">
|
||||
<span>关节4</span>
|
||||
<el-input-number v-model="form.joint4" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">位置控制</div>
|
||||
<div class="body">
|
||||
<div class="btn-wrap pos-control" style="transform: rotateX(40deg);height: calc(100% - 70px)">
|
||||
<div class="btn z_0">
|
||||
<span>Z-</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn x_0">
|
||||
<span> X-</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn z_1">
|
||||
<span>Z+</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn y_0">
|
||||
<span>Y-</span>
|
||||
<svg t="1747126605366" class="turn-left" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn y_1">
|
||||
<span>Y+</span>
|
||||
<svg t="1747126605366" class="turn-right" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn x_1">
|
||||
<span>X+</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb10 action-label">参考坐标系</div>
|
||||
<el-select
|
||||
|
||||
v-model="form.coordinate"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in coordinateList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="input-item">
|
||||
<span>关节5</span>
|
||||
<el-input-number v-model="form.joint5" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
<div class="input-item">
|
||||
<span>关节6</span>
|
||||
<el-input-number v-model="form.joint6" :step="form.jointStep" :precision="6" />
|
||||
</div>
|
||||
<div class="input-item">
|
||||
<span>关节步进</span>
|
||||
<el-input-number v-model="form.jointStep" :step="0.1" :precision="1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom flex between">
|
||||
<div class="left " style="width: 30%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">机械臂位置姿态</div>
|
||||
<div class="body">
|
||||
<div class="flex mb10">
|
||||
<div class="left flex1">
|
||||
<div class="pos-title mb10">位置 ( m )</div>
|
||||
<div class="mb10"> X: {{ form.x.toFixed(6) }}</div>
|
||||
<div class="mb10"> Y: {{ form.y.toFixed(6) }}</div>
|
||||
<div class="mb10"> Z: {{ form.z.toFixed(6) }}</div>
|
||||
</div>
|
||||
<div class="right flex1">
|
||||
<div class="pos-title mb10">姿态 ( deg )</div>
|
||||
<div class="mb10"> RX: {{ form.rx.toFixed(6) }}</div>
|
||||
<div class="mb10"> RY: {{ form.ry.toFixed(6) }}</div>
|
||||
<div class="mb10"> RZ: {{ form.rz.toFixed(6) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb10 action-label">目标</div>
|
||||
<el-select
|
||||
v-model="form.arm"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in armList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center " style="width: 30%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">关节控制单位( deg )</div>
|
||||
<div class="body">
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节1</div>
|
||||
<el-input-number v-model="form.joint1" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节2</div>
|
||||
<el-input-number v-model="form.joint2" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节3</div>
|
||||
<el-input-number v-model="form.joint3" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节4</div>
|
||||
<el-input-number v-model="form.joint4" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节5</div>
|
||||
<el-input-number v-model="form.joint5" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
<div class="mb10 action-label-pos">
|
||||
<div class="action-label">关节6</div>
|
||||
<el-input-number v-model="form.joint6" :step="0.000001" :precision="6"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right " style="width: 38%">
|
||||
<div class="card" style="height: 100%">
|
||||
<div class="title">姿态控制</div>
|
||||
<div class="body">
|
||||
<div class="btn-wrap">
|
||||
<div class="btn rz_0 c">
|
||||
<span>Z-</span>
|
||||
<svg t="1747126605366" class="turn-lb" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn rx_0 c">
|
||||
<span>X-</span>
|
||||
<svg t="1747126605366" class="turn-up" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn rz_1 c">
|
||||
<span>Z+</span>
|
||||
<svg t="1747126605366" class="turn-rb" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn ry_0 c">
|
||||
<span> Y-</span>
|
||||
<svg t="1747126605366" class="turn-left" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn ry_1 c">
|
||||
<span>Y+</span>
|
||||
<svg t="1747126605366" class="turn-right" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
<div class="btn rx_1 c">
|
||||
<span> X+</span>
|
||||
<svg t="1747126605366" class="turn-bottom" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1461" width="32" height="32">
|
||||
<path
|
||||
d="M311.432533 448.170667H163.84L510.293333 3.413333 856.746667 448.170667h-147.592534L760.832 1006.933333H259.754667z"
|
||||
fill="#4076ff" p-id="1462"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="btn"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions flex align-center between">
|
||||
<div class="left">
|
||||
<el-button @click="handleResets('zero')">零位姿态</el-button>
|
||||
<el-button @click="handleResets('init')">初始位姿</el-button>
|
||||
</div>
|
||||
<div class="center">
|
||||
{{ time }}
|
||||
</div>
|
||||
<div class="right flex align-center">
|
||||
<div style="width: 100px;user-select: none">速度</div>
|
||||
<el-slider size="small" :show-tooltip="false" v-model="data.speed"/>
|
||||
<div style="margin-left: 20px"> {{ data.speed }}%</div>
|
||||
<div class="footer card flex align-center between">
|
||||
<div class="time">{{ time }}</div>
|
||||
<div class="speed flex align-center">
|
||||
<span>速度</span>
|
||||
<el-slider v-model="data.speed" :min="0" :max="100" style="width: 200px; margin: 0 10px;" />
|
||||
<span>{{ data.speed }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -431,135 +261,167 @@ const handleResets = (type) => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mechanical-arm {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
|
||||
.card {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 10px 10px 0;
|
||||
height: 48%;
|
||||
.pose-info {
|
||||
justify-content: space-around;
|
||||
padding: 10px 0;
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.card:last-child {
|
||||
flex: 1;
|
||||
.pose-item {
|
||||
font-size: 16px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
padding: 0 10px;
|
||||
height: 44%;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.actions {
|
||||
height: 6%;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
|
||||
padding: 0 10px;
|
||||
|
||||
.right {
|
||||
width: 300px;
|
||||
|
||||
}
|
||||
|
||||
.center {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.action-label {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.action-label-pos {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.el-input-number {
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
gap: 20px;
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 900;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: calc(100% - 29px);
|
||||
overflow-y: auto;
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.pos-title {
|
||||
user-select: none;
|
||||
.control-buttons {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.axis-controls {
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
|
||||
.axis-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
min-width: 150px;
|
||||
|
||||
span {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: #4076ff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rotation svg {
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.turn-up {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.turn-bottom {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.turn-left {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.turn-right {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.turn-lb {
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.turn-rb {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrap {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
justify-items: center;
|
||||
.coordinate-system {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
.coordinate-svg {
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
.right {
|
||||
.input-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
width: 80px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
transform-origin: center center;
|
||||
|
||||
.el-input-number {
|
||||
width: calc(100% - 90px);
|
||||
}
|
||||
}
|
||||
|
||||
.turn-lb {
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.turn-rb {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.turn-left {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.turn-right {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.turn-bottom {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 50px;
|
||||
|
||||
.time {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.speed {
|
||||
gap: 10px;
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@ -194,7 +194,11 @@ const debouncedSetVolume = debounce(setVolume, 500);
|
||||
|
||||
<style scoped>
|
||||
.audio-player {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
height: calc(100vh - 125px);
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<el-tree v-if="!isTreeCollapsed" :data="treeData" :props="treeProps" :default-expanded-keys="['robot-node-config']" @node-click="handleNodeClick" />
|
||||
</div>
|
||||
<div class="robot-panel">
|
||||
<div id="robot-bg" ref="robotBg" :style="{ backgroundImage: `url(${robotImage})` }">
|
||||
<div id="robot-bg" ref="robotBg" :style="{ backgroundImage: `url(${robotImage})`, width: bgSize.width + 'px', height: bgSize.height + 'px' }">
|
||||
<div
|
||||
v-for="area in adjustedAreas"
|
||||
:key="area.id"
|
||||
@ -48,12 +48,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<component :is="popup.component" v-bind="popup.props" />
|
||||
<!-- 边缘拖动区域 -->
|
||||
<div class="resize-edge top" @mousedown="startResizing($event, index, 'top')"></div>
|
||||
<div class="resize-edge bottom" @mousedown="startResizing($event, index, 'bottom')"></div>
|
||||
<div class="resize-edge left" @mousedown="startResizing($event, index, 'left')"></div>
|
||||
<div class="resize-edge right" @mousedown="startResizing($event, index, 'right')"></div>
|
||||
<!-- 角落拖动区域 -->
|
||||
<div class="resize-corner top-left" @mousedown="startResizing($event, index, 'top-left')"></div>
|
||||
<div class="resize-corner top-right" @mousedown="startResizing($event, index, 'top-right')"></div>
|
||||
<div class="resize-corner bottom-left" @mousedown="startResizing($event, index, 'bottom-left')"></div>
|
||||
@ -67,147 +65,151 @@ import { ref, onMounted, computed, onUnmounted, watch } from 'vue';
|
||||
import HandControl from '../register/components/DexHand/index.vue';
|
||||
import CameraView from '../register/components/Camera/index.vue';
|
||||
import MusicPlayer from '../register/components/Speaker/index.vue';
|
||||
import MechanicalArm from '../register/components/MechanicalArm/index.vue';
|
||||
import image from '@/assets/images/robot.png';
|
||||
|
||||
// 组件映射
|
||||
const componentsMap = {
|
||||
HandControl,
|
||||
CameraView,
|
||||
MusicPlayer
|
||||
MusicPlayer,
|
||||
MechanicalArm
|
||||
};
|
||||
|
||||
const robotImage = image;
|
||||
const robotBg = ref(null);
|
||||
const isTreeCollapsed = ref(false); // 控制树形控件收起/展开
|
||||
|
||||
const isTreeCollapsed = ref(false);
|
||||
const popups = ref([]);
|
||||
const isDragging = ref(false);
|
||||
const isResizing = ref([]);
|
||||
const currentZIndex = ref(2000); // 提高初始 z-index
|
||||
const dragIndex = ref(null); // 跟踪当前拖动的索引
|
||||
|
||||
const currentZIndex = ref(2000);
|
||||
const dragIndex = ref(null);
|
||||
const resizeStart = ref({ index: null, startX: 0, startY: 0, originalWidth: 0, originalHeight: 0, originalTop: 0, originalLeft: 0, edge: '' });
|
||||
const imageDimensions = ref({ width: 0, height: 0 });
|
||||
const containerSize = ref({ width: 0, height: 0 });
|
||||
const bgSize = ref({ width: 0, height: 0 });
|
||||
|
||||
// 原始坐标和尺寸(基于图片原始大小)
|
||||
const originalAreas = ref([
|
||||
{ id: 'hand-left', x: 15, y: 505, width: 110, height: 50, component: HandControl, label: '左手' },
|
||||
{ id: 'hand-right', x: 300, y: 505, width: 110, height: 50, component: HandControl, label: '右手' },
|
||||
{ id: 'arm-left', x: 0, y: 150, width: 220, height: 50, component: MechanicalArm, label: '左机械臂' },
|
||||
{ id: 'arm-right', x: 200, y: 150, width: 220, height: 50, component: MechanicalArm, label: '右机械臂' },
|
||||
{ id: 'camera', x: 140, y: 50, width: 160, height: 50, component: CameraView, label: '摄像头' },
|
||||
{ id: 'mouth', x: 140, y: 100, width: 160, height: 50, component: MusicPlayer, label: '扬声器' }
|
||||
]);
|
||||
|
||||
// 树数据
|
||||
const treeData = ref([
|
||||
{
|
||||
id: 'robot-node-config', // 添加唯一 ID 用于默认展开
|
||||
id: 'robot-node-config',
|
||||
label: '机器人节点配置',
|
||||
children: [
|
||||
{
|
||||
id: 'node-1',
|
||||
label: '节点1',
|
||||
children: [{ id: 'node-1-1', label: '子节点1-1' }, { id: 'node-1-2', label: '子节点1-2' }]
|
||||
},
|
||||
{
|
||||
id: 'node-2',
|
||||
label: '节点2',
|
||||
children: [{ id: 'node-2-1', label: '子节点2-1' }, { id: 'node-2-2', label: '子节点2-2' }]
|
||||
},
|
||||
{ id: 'node-1', label: '节点1', children: [{ id: 'node-1-1', label: '子节点1-1' }, { id: 'node-1-2', label: '子节点1-2' }] },
|
||||
{ id: 'node-2', label: '节点2', children: [{ id: 'node-2-1', label: '子节点2-1' }, { id: 'node-2-2', label: '子节点2-2' }] },
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
};
|
||||
const treeProps = { children: 'children', label: 'label' };
|
||||
|
||||
const handleNodeClick = (data) => {
|
||||
console.log('点击节点', data.label);
|
||||
};
|
||||
|
||||
// 原始坐标和尺寸(基于图片真实尺寸),添加 label 用于显示文字
|
||||
const originalAreas = ref([
|
||||
{ id: 'hand-left', x: 50, y: 200, width: 50, height: 100, component: HandControl, label: '左手' },
|
||||
{ id: 'hand-right', x: 400, y: 200, width: 50, height: 100, component: HandControl, label: '右手' },
|
||||
{ id: 'camera', x: 225, y: 100, width: 50, height: 50, component: CameraView, label: '摄像头' },
|
||||
{ id: 'mouth', x: 225, y: 300, width: 50, height: 50, component: MusicPlayer, label: '扬声器' }
|
||||
]);
|
||||
|
||||
const imageDimensions = ref({ width: 0, height: 0 });
|
||||
|
||||
// 计算调整后的区域
|
||||
const adjustedAreas = computed(() => {
|
||||
if (!imageDimensions.value.width || !imageDimensions.value.height || !robotBg.value) return [];
|
||||
const containerWidth = robotBg.value.parentElement.offsetWidth - 40 - (isTreeCollapsed.value ? 0 : 240); // 减去外层 padding 和 tree-panel 宽度
|
||||
console.log(containerWidth)
|
||||
const containerHeight = robotBg.value.parentElement.offsetHeight - 40; // 减去外层 padding
|
||||
const containerWidth = containerSize.value.width - (isTreeCollapsed.value ? 0 : 240); // 仅减去树宽度
|
||||
const containerHeight = containerSize.value.height; // 无垂直padding
|
||||
const scaleX = containerWidth / imageDimensions.value.width;
|
||||
const scaleY = containerHeight / imageDimensions.value.height;
|
||||
const scale = Math.min(scaleX, scaleY); // 取最小缩放比例以适配
|
||||
const offsetX = (containerWidth - (imageDimensions.value.width * scale)) / 2;
|
||||
const offsetY = (containerHeight - (imageDimensions.value.height * scale)) / 2;
|
||||
const scale = Math.min(scaleX, scaleY, 1); // 防止放大
|
||||
const scaledWidth = imageDimensions.value.width * scale; // 图片宽
|
||||
const scaledHeight = imageDimensions.value.height * scale;
|
||||
|
||||
// const offsetX = (containerWidth - scaledWidth) / 2;
|
||||
// const offsetY = (containerHeight - scaledHeight) / 2;
|
||||
|
||||
// 更新背景图片尺寸
|
||||
bgSize.value = { width: scaledWidth, height: scaledHeight };
|
||||
|
||||
return originalAreas.value.map(area => ({
|
||||
...area,
|
||||
x: (area.x * scale) + offsetX,
|
||||
y: (area.y * scale) + offsetY,
|
||||
x: area.x * scale,
|
||||
y: area.y * scale,
|
||||
width: area.width * scale,
|
||||
height: area.height * scale
|
||||
}));
|
||||
});
|
||||
|
||||
// 打开弹窗,考虑树宽度和padding偏移
|
||||
const openPopup = (area) => {
|
||||
// 检查是否已存在相同 id 的窗口
|
||||
const existingPopup = popups.value.find(p => p.id === area.id);
|
||||
if (existingPopup) {
|
||||
console.log('Popup with id', area.id, 'is already open');
|
||||
return; // 如果已打开,阻止重复打开
|
||||
console.log('已打开ID为', area.id, '的弹窗');
|
||||
return;
|
||||
}
|
||||
const popup = {
|
||||
id: area.id,
|
||||
component: area.component, // 使用组件引用
|
||||
top: area.y + 'px',
|
||||
left: area.x + 'px',
|
||||
const treeWidth = isTreeCollapsed.value ? 0 : 240;
|
||||
const leftOffset = treeWidth + 20; // tree宽度 + robot-panel padding-left
|
||||
const popup = {
|
||||
id: area.id,
|
||||
component: area.component,
|
||||
top: `${area.y}px`,
|
||||
left: `${area.x + leftOffset}px`,
|
||||
zIndex: currentZIndex.value++,
|
||||
width: 640, // 默认宽度
|
||||
height: 480, // 默认高度
|
||||
originalWidth: 640, // 初始化原始宽度
|
||||
originalHeight: 480, // 初始化原始高度
|
||||
originalTop: area.y + 'px', // 初始化原始顶部位置
|
||||
originalLeft: area.x + 'px', // 初始化原始左侧位置
|
||||
width: 640,
|
||||
height: 480,
|
||||
originalWidth: 640,
|
||||
originalHeight: 480,
|
||||
originalTop: `${area.y}px`,
|
||||
originalLeft: `${area.x + leftOffset}px`,
|
||||
isMaximized: false,
|
||||
props: { terminalId: 'your-terminal-id', cameraId: 'your-camera-id' } // 替换为实际值
|
||||
props: { terminalId: 'your-terminal-id', cameraId: 'your-camera-id' }
|
||||
};
|
||||
popups.value.push(popup);
|
||||
isResizing.value.push(false);
|
||||
console.log('Opened popup with id', area.id, 'initial size:', popup.width, 'x', popup.height, 'position:', popup.top, popup.left);
|
||||
console.log('打开弹窗ID', area.id, '初始尺寸:', popup.width, 'x', popup.height, '位置:', popup.top, popup.left);
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const closePopup = (index) => {
|
||||
popups.value.splice(index, 1);
|
||||
isResizing.value.splice(index, 1);
|
||||
console.log('Closed popup at index', index);
|
||||
console.log('关闭弹窗索引', index);
|
||||
};
|
||||
|
||||
// 最小化弹窗
|
||||
const minimizePopup = (index) => {
|
||||
const popup = popups.value[index];
|
||||
if (popup.isMaximized) {
|
||||
popup.width = popup.originalWidth; // 恢复到原始宽度
|
||||
popup.height = popup.originalHeight; // 恢复到原始高度
|
||||
popup.top = popup.originalTop; // 恢复到原始顶部位置
|
||||
popup.left = popup.originalLeft; // 恢复到原始左侧位置
|
||||
popup.width = popup.originalWidth;
|
||||
popup.height = popup.originalHeight;
|
||||
popup.top = popup.originalTop;
|
||||
popup.left = popup.originalLeft;
|
||||
popup.isMaximized = false;
|
||||
console.log('Minimized popup', popup.id, 'to size:', popup.width, 'x', popup.height, 'position:', popup.top, popup.left);
|
||||
console.log('最小化弹窗', popup.id, '到尺寸:', popup.width, 'x', popup.height, '位置:', popup.top, popup.left);
|
||||
}
|
||||
};
|
||||
|
||||
// 最大化弹窗
|
||||
const maximizePopup = (index) => {
|
||||
const popup = popups.value[index];
|
||||
if (!popup.isMaximized) {
|
||||
popup.originalWidth = popup.width; // 保存当前宽度作为原始宽度
|
||||
popup.originalHeight = popup.height; // 保存当前高度作为原始高度
|
||||
popup.originalTop = popup.top; // 保存当前顶部位置
|
||||
popup.originalLeft = popup.left; // 保存当前左侧位置
|
||||
popup.width = window.innerWidth; // 全屏宽度
|
||||
popup.height = window.innerHeight; // 全屏高度
|
||||
popup.top = '0px'; // 从顶部开始
|
||||
popup.left = '0px'; // 从左侧开始
|
||||
popup.originalWidth = popup.width;
|
||||
popup.originalHeight = popup.height;
|
||||
popup.originalTop = popup.top;
|
||||
popup.originalLeft = popup.left;
|
||||
popup.width = window.innerWidth;
|
||||
popup.height = window.innerHeight;
|
||||
popup.top = '0px';
|
||||
popup.left = '0px';
|
||||
popup.isMaximized = true;
|
||||
console.log('Maximized popup', popup.id, 'to size:', popup.width, 'x', popup.height, 'position:', popup.top, popup.left);
|
||||
console.log('最大化弹窗', popup.id, '到尺寸:', popup.width, 'x', popup.height, '位置:', popup.top, popup.left);
|
||||
}
|
||||
};
|
||||
|
||||
// 拖动相关逻辑
|
||||
const startDragging = (e, index) => {
|
||||
isDragging.value = true;
|
||||
dragIndex.value = index;
|
||||
@ -217,18 +219,16 @@ const startDragging = (e, index) => {
|
||||
popup.zIndex = currentZIndex.value++;
|
||||
document.addEventListener('mousemove', dragHandler);
|
||||
document.addEventListener('mouseup', stopDragging);
|
||||
console.log('Start dragging', index, 'ClientY:', e.clientY, 'Handle height:', e.currentTarget.offsetHeight);
|
||||
};
|
||||
|
||||
const dragHandler = (e) => {
|
||||
if (isDragging.value && dragIndex.value !== null) {
|
||||
requestAnimationFrame(() => {
|
||||
const popup = popups.value[dragIndex.value];
|
||||
const newTop = Math.max(0, e.pageY - popup.startY); // 限制顶部不小于 0
|
||||
const newLeft = e.pageX - popup.startX; // 左侧无限制
|
||||
const newTop = Math.max(0, e.pageY - popup.startY);
|
||||
const newLeft = e.pageX - popup.startX;
|
||||
popup.top = `${newTop}px`;
|
||||
popup.left = `${newLeft}px`;
|
||||
console.log('Dragging', dragIndex.value, 'New position:', popup.left, popup.top);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -240,6 +240,7 @@ const stopDragging = () => {
|
||||
document.removeEventListener('mouseup', stopDragging);
|
||||
};
|
||||
|
||||
// 调整大小相关逻辑
|
||||
const startResizing = (e, index, edge) => {
|
||||
const popup = popups.value[index];
|
||||
resizeStart.value = {
|
||||
@ -255,7 +256,6 @@ const startResizing = (e, index, edge) => {
|
||||
isResizing.value[index] = true;
|
||||
document.addEventListener('mousemove', handleResize);
|
||||
document.addEventListener('mouseup', stopResizingGlobal);
|
||||
console.log('Start resizing', edge);
|
||||
};
|
||||
|
||||
const handleResize = (e) => {
|
||||
@ -271,22 +271,18 @@ const handleResize = (e) => {
|
||||
case 'top':
|
||||
const newHeight = Math.max(minHeight, resizeStart.value.originalHeight - diffY);
|
||||
popup.height = newHeight;
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeight))}px`; // 限制顶部
|
||||
console.log('Resizing top', 'newHeight:', newHeight, 'newTop:', popup.top);
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeight))}px`;
|
||||
break;
|
||||
case 'bottom':
|
||||
popup.height = Math.max(minHeight, resizeStart.value.originalHeight + diffY);
|
||||
console.log('Resizing bottom', 'newHeight:', popup.height);
|
||||
break;
|
||||
case 'left':
|
||||
const newWidth = Math.max(minWidth, resizeStart.value.originalWidth - diffX);
|
||||
popup.width = newWidth;
|
||||
popup.left = `${resizeStart.value.originalLeft + (resizeStart.value.originalWidth - newWidth)}px`;
|
||||
console.log('Resizing left', 'newWidth:', newWidth, 'newLeft:', popup.left);
|
||||
break;
|
||||
case 'right':
|
||||
popup.width = Math.max(minWidth, resizeStart.value.originalWidth + diffX);
|
||||
console.log('Resizing right', 'newWidth:', popup.width);
|
||||
break;
|
||||
case 'top-left':
|
||||
const newWidthTL = Math.max(minWidth, resizeStart.value.originalWidth - diffX);
|
||||
@ -294,16 +290,14 @@ const handleResize = (e) => {
|
||||
popup.width = newWidthTL;
|
||||
popup.height = newHeightTL;
|
||||
popup.left = `${resizeStart.value.originalLeft + (resizeStart.value.originalWidth - newWidthTL)}px`;
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeightTL))}px`; // 限制顶部
|
||||
console.log('Resizing top-left', 'newWidth:', newWidthTL, 'newHeight:', newHeightTL, 'newTop:', popup.top);
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeightTL))}px`;
|
||||
break;
|
||||
case 'top-right':
|
||||
const newWidthTR = Math.max(minWidth, resizeStart.value.originalWidth + diffX);
|
||||
const newHeightTR = Math.max(minHeight, resizeStart.value.originalHeight - diffY);
|
||||
popup.width = newWidthTR;
|
||||
popup.height = newHeightTR;
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeightTR))}px`; // 限制顶部
|
||||
console.log('Resizing top-right', 'newWidth:', newWidthTR, 'newHeight:', newHeightTR, 'newTop:', popup.top);
|
||||
popup.top = `${Math.max(0, resizeStart.value.originalTop + (resizeStart.value.originalHeight - newHeightTR))}px`;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
const newWidthBL = Math.max(minWidth, resizeStart.value.originalWidth - diffX);
|
||||
@ -311,12 +305,10 @@ const handleResize = (e) => {
|
||||
popup.width = newWidthBL;
|
||||
popup.height = newHeightBL;
|
||||
popup.left = `${resizeStart.value.originalLeft + (resizeStart.value.originalWidth - newWidthBL)}px`;
|
||||
console.log('Resizing bottom-left', 'newWidth:', newWidthBL, 'newHeight:', newHeightBL, 'newLeft:', popup.left);
|
||||
break;
|
||||
case 'bottom-right':
|
||||
popup.width = Math.max(minWidth, resizeStart.value.originalWidth + diffX);
|
||||
popup.height = Math.max(minHeight, resizeStart.value.originalHeight + diffY);
|
||||
console.log('Resizing bottom-right', 'newWidth:', popup.width, 'newHeight:', popup.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -331,19 +323,39 @@ const stopResizingGlobal = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (fn, delay) => {
|
||||
let timeout;
|
||||
return (...args) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => fn(...args), delay);
|
||||
};
|
||||
};
|
||||
|
||||
// 更新容器和图片尺寸
|
||||
const updateDimensions = debounce(() => {
|
||||
console.log('图片',robotBg.value.width,robotBg.value.offsetWidth, robotBg.value.offsetHeight)
|
||||
if (robotBg.value && robotBg.value.parentElement) {
|
||||
containerSize.value = {
|
||||
width: robotBg.value.parentElement.offsetWidth - 40, // 左右padding 20*2
|
||||
height: robotBg.value.parentElement.offsetHeight // 无垂直padding
|
||||
};
|
||||
console.log('更新容器尺寸:', containerSize.value.width, 'x', containerSize.value.height);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
onMounted(() => {
|
||||
const img = new Image();
|
||||
img.src = robotImage;
|
||||
img.onload = () => {
|
||||
imageDimensions.value = { width: img.width, height: img.height };
|
||||
console.log('图片尺寸:', imageDimensions.value.width, 'x', imageDimensions.value.height);
|
||||
if (robotBg.value) {
|
||||
watch(() => [robotBg.value.parentElement.offsetWidth, robotBg.value.parentElement.offsetHeight, isTreeCollapsed.value], ([newWidth, newHeight]) => {
|
||||
const containerWidth = newWidth - 40 - (isTreeCollapsed.value ? 0 : 240); // 减去外层 padding 和 tree-panel 宽度
|
||||
const containerHeight = newHeight - 40; // 减去外层 padding
|
||||
const scale = Math.min(containerWidth / img.width, containerHeight / img.height);
|
||||
robotBg.value.style.minWidth = `${img.width * scale}px`;
|
||||
robotBg.value.style.minHeight = `${img.height * scale}px`;
|
||||
}, { immediate: true });
|
||||
const observer = new ResizeObserver(updateDimensions);
|
||||
observer.observe(robotBg.value.parentElement);
|
||||
watch(() => isTreeCollapsed.value, updateDimensions, { immediate: true });
|
||||
updateDimensions(); // 初始更新
|
||||
onUnmounted(() => observer.disconnect());
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -358,47 +370,48 @@ onUnmounted(() => {
|
||||
.robot-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex; /* 保持与外层一致 */
|
||||
overflow: hidden; /* 防止遮挡弹窗 */
|
||||
padding: 0; /* 外层已处理 padding */
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.tree-panel {
|
||||
flex-shrink: 0; /* 防止收缩 */
|
||||
/* padding: 10px; */
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid #ddd;
|
||||
overflow-y: auto; /* 垂直滚动 */
|
||||
height: auto; /* 动态高度 */
|
||||
transition: width 0.3s ease; /* 平滑过渡 */
|
||||
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.tree-panel:deep(.el-tree) {
|
||||
padding: 10px !important;
|
||||
}
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.robot-panel {
|
||||
flex-grow: 1; /* 填满剩余空间 */
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center; /* 水平居中 */
|
||||
align-items: center; /* 垂直居中 */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0 20px; /* 补偿外层 padding */
|
||||
position: relative; /* 确保按钮定位 */
|
||||
padding: 0 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#robot-bg {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
min-width: 0; /* 防止收缩 */
|
||||
min-height: 0; /* 防止收缩 */
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
position: relative; /* 添加 relative 定位 */
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
position: absolute;
|
||||
left: 20px; /* 贴近左边边 */
|
||||
top: 10px; /* 顶部留点间距 */
|
||||
left: 20px;
|
||||
top: 10px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
@ -408,7 +421,7 @@ onUnmounted(() => {
|
||||
font-size: 24px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
z-index: 2001; /* 确保高于其他元素 */
|
||||
z-index: 2001;
|
||||
}
|
||||
|
||||
.toggle-button:hover {
|
||||
@ -417,7 +430,7 @@ onUnmounted(() => {
|
||||
|
||||
.clickable-area {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.clickable-text {
|
||||
@ -443,23 +456,24 @@ onUnmounted(() => {
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
transition: width 0.2s, height 0.2s, top 0.2s, left 0.2s; /* 平滑过渡 */
|
||||
transition: width 0.2s, height 0.2s, top 0.2s, left 0.2s;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.popup.dragging {
|
||||
transition: none; /* 拖动时禁用过渡 */
|
||||
transition: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
height: 30px; /* 增加高度以容纳按钮 */
|
||||
background: #e0e0e0; /* 背景色 */
|
||||
cursor: move; /* 拖动光标 */
|
||||
height: 30px;
|
||||
background: #e0e0e0;
|
||||
cursor: move;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* 标题居中 */
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
@ -471,8 +485,8 @@ onUnmounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 60px; /* 留出按钮空间 */
|
||||
text-align: center; /* 居中文本 */
|
||||
right: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.popup-controls {
|
||||
@ -484,8 +498,8 @@ onUnmounted(() => {
|
||||
|
||||
.control-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px; /* 放大按钮 */
|
||||
padding: 4px 8px; /* 增加内边距 */
|
||||
font-size: 18px;
|
||||
padding: 4px 8px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@ -502,19 +516,17 @@ onUnmounted(() => {
|
||||
background: #ff6666;
|
||||
}
|
||||
|
||||
/* 边缘拖动区域 */
|
||||
.resize-edge {
|
||||
position: absolute;
|
||||
background: transparent;
|
||||
z-index: 2; /* 提高拖动区域优先级 */
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.top { top: 0; left: 0; right: 0; height: 10px; cursor: ns-resize; } /* 增加高度以覆盖 drag-handle */
|
||||
.top { top: 0; left: 0; right: 0; height: 10px; cursor: ns-resize; }
|
||||
.bottom { bottom: 0; left: 0; right: 0; height: 5px; cursor: ns-resize; }
|
||||
.left { top: 0; bottom: 0; left: 0; width: 5px; cursor: ew-resize; }
|
||||
.right { top: 0; bottom: 0; right: 0; width: 5px; cursor: ew-resize; }
|
||||
|
||||
/* 角落拖动区域 */
|
||||
.resize-corner {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
@ -528,6 +540,6 @@ onUnmounted(() => {
|
||||
.bottom-right { bottom: 0; right: 0; cursor: nwse-resize; }
|
||||
|
||||
.popup.resizing {
|
||||
transition: none; /* 缩放时禁用过渡 */
|
||||
transition: none;
|
||||
}
|
||||
</style>
|
||||
136
vite.config.js
136
vite.config.js
@ -1,68 +1,68 @@
|
||||
import {defineConfig, loadEnv, normalizePath} from 'vite'
|
||||
import path from 'path'
|
||||
import createVitePlugins from './vite/plugins'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({mode, command}) => {
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
const {VITE_APP_ENV, VITE_API_URL} = env
|
||||
const variablePath = normalizePath(path.resolve('./src/style/variable.scss'));
|
||||
return {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
|
||||
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||
base: VITE_APP_ENV === 'production' ? '/' : '/',
|
||||
plugins: createVitePlugins(env, command === 'build'),
|
||||
resolve: {
|
||||
// https://cn.vitejs.dev/config/#resolve-alias
|
||||
alias: {
|
||||
// 设置路径
|
||||
'~': path.resolve(__dirname, './'),
|
||||
// 设置别名
|
||||
'@': path.resolve(__dirname, './src')
|
||||
},
|
||||
// https://cn.vitejs.dev/config/#resolve-extensions
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
// vite 相关配置
|
||||
server: {
|
||||
port: 13080,
|
||||
host: true,
|
||||
open: true,
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
'/dev-api': {
|
||||
//杨 http://192.168.0.10:13080
|
||||
//赵 http://10.148.108.58:13080
|
||||
// dev http://10.148.20.34:13080
|
||||
// target: command === 'build' ? VITE_API_URL : 'http://10.148.20.34:13080',
|
||||
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.10:13080',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
//fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
{
|
||||
postcssPlugin: 'internal:charset-removal',
|
||||
AtRule: {
|
||||
charset: (atRule) => {
|
||||
if (atRule.name === 'charset') {
|
||||
atRule.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "${variablePath}";`
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
import {defineConfig, loadEnv, normalizePath} from 'vite'
|
||||
import path from 'path'
|
||||
import createVitePlugins from './vite/plugins'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({mode, command}) => {
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
const {VITE_APP_ENV, VITE_API_URL} = env
|
||||
const variablePath = normalizePath(path.resolve('./src/style/variable.scss'));
|
||||
return {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
|
||||
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||
base: VITE_APP_ENV === 'production' ? '/' : '/',
|
||||
plugins: createVitePlugins(env, command === 'build'),
|
||||
resolve: {
|
||||
// https://cn.vitejs.dev/config/#resolve-alias
|
||||
alias: {
|
||||
// 设置路径
|
||||
'~': path.resolve(__dirname, './'),
|
||||
// 设置别名
|
||||
'@': path.resolve(__dirname, './src')
|
||||
},
|
||||
// https://cn.vitejs.dev/config/#resolve-extensions
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
// vite 相关配置
|
||||
server: {
|
||||
port: 13080,
|
||||
host: true,
|
||||
open: true,
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
'/dev-api': {
|
||||
//杨 http://192.168.0.10:13080
|
||||
//赵 http://10.148.108.58:13080
|
||||
// dev http://10.148.20.34:13080
|
||||
// target: command === 'build' ? VITE_API_URL : 'http://10.148.20.34:13080',
|
||||
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.10:13080',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
//fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
{
|
||||
postcssPlugin: 'internal:charset-removal',
|
||||
AtRule: {
|
||||
charset: (atRule) => {
|
||||
if (atRule.name === 'charset') {
|
||||
atRule.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "${variablePath}";`
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user