diff --git a/package-lock.json b/package-lock.json
index b07e727..acad1a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,9 +9,11 @@
"version": "0.0.0",
"dependencies": {
"@meta2d/core": "^1.0.78",
+ "@vueuse/rxjs": "^13.1.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.8.4",
"lodash-es": "^4.17.21",
+ "rxjs": "^7.8.2",
"vue": "^3.5.13",
"vue-i18n": "^11.1.3",
"vue-router": "^4.5.0"
@@ -1769,6 +1771,34 @@
}
}
},
+ "node_modules/@vueuse/rxjs": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/rxjs/-/rxjs-13.1.0.tgz",
+ "integrity": "sha512-/ItzpO8yky6K+frthSlUbBvmy4WSrKSHze/SyYPMgGGLh+xMYFUvmXQnO/daVC4fdWJN8Phb2dfa7d9WZTDQhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vueuse/shared": "13.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "rxjs": ">=6.0.0",
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-13.1.0.tgz",
+ "integrity": "sha512-IVS/qRRjhPTZ6C2/AM3jieqXACGwFZwWTdw5sNTSKk2m/ZpkuuN+ri+WCVUP8TqaKwJYt/KuMwmXspMAw8E6ew==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
@@ -4706,7 +4736,6 @@
"version": "7.8.2",
"resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
- "dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
@@ -5942,7 +5971,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD"
},
"node_modules/type-check": {
diff --git a/package.json b/package.json
index 67e56a4..1a2b2ca 100644
--- a/package.json
+++ b/package.json
@@ -10,9 +10,11 @@
},
"dependencies": {
"@meta2d/core": "^1.0.78",
+ "@vueuse/rxjs": "^13.1.0",
"ant-design-vue": "^4.2.6",
"axios": "^1.8.4",
"lodash-es": "^4.17.21",
+ "rxjs": "^7.8.2",
"vue": "^3.5.13",
"vue-i18n": "^11.1.3",
"vue-router": "^4.5.0"
diff --git a/src/App.vue b/src/App.vue
index dce10d2..6302c4a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,8 +5,6 @@ import sTheme from '@core/theme.service';
-
-
-
+
diff --git a/src/ant.scss b/src/ant.scss
index 60deae1..e69de29 100644
--- a/src/ant.scss
+++ b/src/ant.scss
@@ -1,4 +0,0 @@
-.ant-app {
- width: 100%;
- height: 100%;
-}
diff --git a/src/components/robot-list.vue b/src/components/robot-list.vue
new file mode 100644
index 0000000..fed9789
--- /dev/null
+++ b/src/components/robot-list.vue
@@ -0,0 +1,47 @@
+
+
+
+
+ 常规
+ 点位
+
+
+ 库区
+ 互斥区
+ 非互斥区
+
+
+
+
diff --git a/src/pages/group-editor.vue b/src/pages/group-editor.vue
new file mode 100644
index 0000000..34f22af
--- /dev/null
+++ b/src/pages/group-editor.vue
@@ -0,0 +1,10 @@
+
+
+组编辑器
+
+
diff --git a/src/pages/home.vue b/src/pages/home.vue
deleted file mode 100644
index 16e09c7..0000000
--- a/src/pages/home.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue
new file mode 100644
index 0000000..fb9036d
--- /dev/null
+++ b/src/pages/scene-editor.vue
@@ -0,0 +1,36 @@
+
+
+
+
+ header
+
+
+
+
+
+
+
+
+ sider
+
+
+
+
+
diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts
index 106c587..5da0dbb 100644
--- a/src/services/editor.service.ts
+++ b/src/services/editor.service.ts
@@ -1,47 +1,62 @@
-import { EDITOR_CONFIG, type MapPen, MapPointType } from '@api/map';
+import { EDITOR_CONFIG, MapAreaType, type MapPen, MapPointType } from '@api/map';
import sTheme from '@core/theme.service';
-import { LockState, Meta2d } from '@meta2d/core';
+import { EditType, LockState, Meta2d } from '@meta2d/core';
+import { useObservable } from '@vueuse/rxjs';
import THEME from 'asset/themes/editor.json';
+import { cloneDeep, pick } from 'lodash-es';
+import { debounceTime, filter, map, Subject, switchMap } from 'rxjs';
import { watch } from 'vue';
-export class EditorService {
- readonly #editor: Meta2d;
+export type Point = Record<'x' | 'y', number>;
- public destroy() {
- this.#editor.destroy();
- }
-
- public resize(): void {
- this.#editor.resize();
- this.#editor.render();
- }
-
- public open(map?: string, readonly = false): void {
+export class EditorService extends Meta2d {
+ public load(map?: string, readonly = false): void {
const data = map ? JSON.parse(map) : undefined;
- this.#editor.open(data);
- this.#editor.lock(readonly ? LockState.Disable : LockState.None);
+ this.open(data);
+ this.lock(readonly ? LockState.Disable : LockState.None);
}
public save(): string {
- const data = this.#editor.data();
+ const data = this.data();
const map = JSON.stringify(data);
return map;
}
public export(): string {
- const png = this.#editor.toPng(10);
+ const png = this.toPng(10);
return png;
}
+ readonly #mouse$$ = new Subject<{ type: 'click' | 'mousedown' | 'mouseup'; value: Point }>();
+ public readonly mouseClick = useObservable(
+ this.#mouse$$.pipe(
+ filter(({ type }) => type === 'click'),
+ debounceTime(100),
+ map(({ value }) => value),
+ ),
+ );
+ public readonly mouseBrush = useObservable<[Point, Point]>(
+ this.#mouse$$.pipe(
+ filter(({ type }) => type === 'mousedown'),
+ switchMap(({ value: s }) =>
+ this.#mouse$$.pipe(
+ filter(({ type }) => type === 'mouseup'),
+ map(({ value: e }) => <[Point, Point]>[s, e]),
+ ),
+ ),
+ ),
+ );
+
//#region 点位
- public async addPoint(x: number = 0, y: number = 0, type = MapPointType.普通点): Promise {
+ public async addPoint(p: Point, type = MapPointType.普通点): Promise {
const pen: MapPen = {
name: 'point',
- x,
- y,
+ x: p.x,
+ y: p.y,
width: 24,
height: 24,
point: { type },
};
- await this.#editor.addPen(pen);
+ await this.addPen(pen, false, true, true);
+ this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
}
//#endregion
@@ -49,18 +64,36 @@ export class EditorService {
//#endregion
//#region 区域
+ public async addArea(p1: Point, p2: Point, type = MapAreaType.库区) {
+ const scale = this.data().scale ?? 1;
+ const w = Math.abs(p1.x - p2.x);
+ const h = Math.abs(p1.y - p2.y);
+ if (w * scale < 50 || h * scale < 60) return;
+ const pen: MapPen = {
+ name: 'area',
+ x: Math.min(p1.x, p2.x),
+ y: Math.min(p1.y, p2.y),
+ width: w,
+ height: h,
+ area: { type },
+ locked: LockState.DisableMoveScale,
+ };
+ const area = await this.addPen(pen, false, true, true);
+ this.bottom(area);
+ this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
+ }
//#endregion
constructor(container: HTMLDivElement) {
- this.#editor = new Meta2d(container, EDITOR_CONFIG);
+ super(container, EDITOR_CONFIG);
(container.children.item(5)).ondrop = null;
- this.#editor.on('*', (e, v) => this.#listen(e, v));
+ this.on('*', (e, v) => this.#listen(e, v));
this.#register();
watch(
() => sTheme.theme,
- (v) => this.#editor.setTheme(v),
+ (v) => this.setTheme(v),
{ immediate: true },
);
}
@@ -68,6 +101,12 @@ export class EditorService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
#listen(e: unknown, v: any) {
switch (e) {
+ case 'click':
+ case 'mousedown':
+ case 'mouseup':
+ this.#mouse$$.next({ type: e, value: pick(v, 'x', 'y') });
+ break;
+
default:
// console.log(e, v);
break;
@@ -75,8 +114,9 @@ export class EditorService {
}
#register() {
- this.#editor.store.theme = THEME;
- this.#editor.registerCanvasDraw({ point: drawPoint });
+ this.store.theme = THEME;
+ this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea });
+ this.registerAnchors({ point: anchorPoint });
}
}
@@ -84,7 +124,34 @@ export class EditorService {
function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {};
const { type } = pen.point ?? {};
- ctx.rect(x, y, width, height);
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.roundRect(x, y, width, height, 4);
ctx.stroke();
+ ctx.fillStyle = '#fff';
+ ctx.fillText(String(type), x + width / 2, y + height / 2);
+ ctx.restore();
+}
+function anchorPoint(pen: MapPen): void {
+ pen.anchors = [{ x: 0.5, y: 0.5 }];
+}
+
+function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
+ const [p1, p2] = pen.calculative?.worldAnchors ?? [];
+ const { direction } = pen.route ?? {};
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.restore();
+}
+
+function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
+ const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {};
+ const { type } = pen.area ?? {};
+ ctx.save();
+ ctx.lineWidth = 1;
+ ctx.strokeRect(x, y, width, height);
+ ctx.fillStyle = '#fff';
+ ctx.fillText(String(type), x + width / 2, y);
+ ctx.restore();
}
//#endregion
diff --git a/src/services/router.ts b/src/services/router.ts
index c8df74a..b776985 100644
--- a/src/services/router.ts
+++ b/src/services/router.ts
@@ -1,51 +1,17 @@
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
export const ROUTES = Object.freeze([
- { path: '/:pathMatch(.*)*', redirect: '/home' },
- { path: '/home', component: () => import('@/home.vue') },
- // {
- // path: '/',
- // component: () => import('@layout/main.vue'),
- // children: [
- // { path: '', redirect: 'home' },
- // { name: '首页', path: 'home', component: () => import('@/home.vue') },
- // { name: '任务管理', path: 'task-management', component: () => import('@/home.vue') },
- // { name: '车辆管理', path: 'vehicle-management', component: () => import('@/home.vue') },
- // {
- // name: '地图管理',
- // path: 'map-management',
- // meta: { standalone: true },
- // component: () => import('@/map-management/scene-edit.vue'),
- // },
- // { name: '参数设置', path: 'parameter-setting', component: import('@/parameter-setting/index.vue') },
- // {
- // name: '调度仿真',
- // path: 'scheduling-emulation',
- // children: [
- // {
- // name: '调度模拟',
- // path: 'scheduling-simulation',
- // components: {
- // default: () => import('@/scheduling-emulation/scheduling-simulation.vue'),
- // toolbar: () => import('@common/map-switcher.vue'),
- // },
- // },
- // {
- // name: '车辆调试',
- // path: 'vehicle-commissioning',
- // component: () => import('@/scheduling-emulation/vehicle-commissioning.vue'),
- // },
- // {
- // name: '场景测试',
- // path: 'scenario-testing',
- // component: () => import('@/scheduling-emulation/scenario-testing.vue'),
- // },
- // ],
- // },
- // { name: '告警管理', path: 'alarm-management', component: () => import('@/home.vue') },
- // { name: '日志管理', path: 'log-management', component: () => import('@/home.vue') },
- // ],
- // },
+ { path: '/:pathMatch(.*)*', redirect: '/' },
+ {
+ path: '/scene-editor/:id',
+ props: true,
+ component: () => import('@/scene-editor.vue'),
+ },
+ {
+ path: '/group-editor/:id',
+ props: true,
+ component: () => import('@/group-editor.vue'),
+ },
]);
export const router = createRouter({
diff --git a/src/services/theme.service.ts b/src/services/theme.service.ts
index e29e5b3..2327dbc 100644
--- a/src/services/theme.service.ts
+++ b/src/services/theme.service.ts
@@ -7,7 +7,7 @@ enum Theme {
}
export const THEMES = Object.freeze<[string, Theme][]>(Object.entries(Theme));
-const THEME_STORAGE_KEY = 'locale';
+const THEME_STORAGE_KEY = 'theme';
class ThemeService {
#theme = ref(localStorage.getItem(THEME_STORAGE_KEY) || Theme.Dark);
diff --git a/src/style.scss b/src/style.scss
index 2c83ad4..828d966 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -5,6 +5,8 @@ body {
width: 100vw;
height: 100vh;
margin: 0;
+ user-select: none;
+ -webkit-user-drag: none;
}
#app {
@@ -17,3 +19,57 @@ body {
height: 100%;
overflow: hidden;
}
+
+@for $i from 0 through 100 {
+ .size-#{$i * 2} {
+ font-size: $i * 2px !important;
+ }
+
+ .w-#{$i * 4} {
+ width: $i * 4px !important;
+ }
+
+ .p-#{$i * 2} {
+ padding: $i * 2px !important;
+ }
+ .ph-#{$i * 2} {
+ padding-inline: $i * 2px !important;
+ }
+ .pv-#{$i * 2} {
+ padding-block: $i * 2px !important;
+ }
+ .pl-#{$i * 2} {
+ padding-left: $i * 2px !important;
+ }
+ .pr-#{$i * 2} {
+ padding-right: $i * 2px !important;
+ }
+ .pt-#{$i * 2} {
+ padding-top: $i * 2px !important;
+ }
+ .pb-#{$i * 2} {
+ padding-bottom: $i * 2px !important;
+ }
+
+ .m-#{$i * 2} {
+ margin: $i * 2px !important;
+ }
+ .mh-#{$i * 2} {
+ margin-inline: $i * 2px !important;
+ }
+ .mv-#{$i * 2} {
+ margin-block: $i * 2px !important;
+ }
+ .ml-#{$i * 2} {
+ margin-left: $i * 2px !important;
+ }
+ .mr-#{$i * 2} {
+ margin-right: $i * 2px !important;
+ }
+ .mt-#{$i * 2} {
+ margin-top: $i * 2px !important;
+ }
+ .mb-#{$i * 2} {
+ margin-bottom: $i * 2px !important;
+ }
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 7a8e688..11f02fe 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -1,7 +1 @@
///
-
-declare module '*.vue' {
- import type { DefineComponent } from 'vue';
- const vueComponent: DefineComponent