feat: draw point

This commit is contained in:
chndfang 2025-04-28 20:04:46 +08:00
parent 7ebf69cc2f
commit 3edd781bde
19 changed files with 105 additions and 28 deletions

View File

@ -24,7 +24,7 @@ export default [
files: ['**/*.vue'],
languageOptions: { parserOptions: { parser: tseslint.parser } },
rules: {
'vue/multi-word-component-names': ['warn', { ignores: ['index'] }],
'vue/multi-word-component-names': ['warn', { ignores: ['index', 'exception'] }],
},
},
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/point/11-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/point/12-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
public/point/13-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/point/14-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/point/15-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -44,8 +44,6 @@ export const EDITOR_CONFIG: Options = {
keydown: KeydownType.None,
strictScope: true,
moveConnectedLine: false,
textRotate: false,
textFlip: false,
disableInput: true,
disableRotate: true,
disableSize: true,
@ -53,12 +51,11 @@ export const EDITOR_CONFIG: Options = {
disableEmptyLine: true,
disableRepeatLine: true,
minScale: 0.19,
maxScale: 2.01,
maxScale: 4.01,
scaleOff: 0.01,
defaultAnchors: [],
activeGlobalAlpha: 0,
fontSize: 14,
lineHeight: 1.5,
textAlign: 'center',
textBaseline: 'top',
fontFamily: 'system-ui',
};

View File

@ -0,0 +1,9 @@
{
"token": {
"colorInfo": "#0ea278",
"colorPrimary": "#0dbb8a",
"colorTextBase": "#fafafa",
"colorBgBase": "#2a2c2c",
"colorBorderSecondary": "#595e5e"
}
}

View File

@ -6,12 +6,12 @@
"strokeActive": "#FCC947",
"fill-1": "#14D1A5",
"fill-2": "#69C6F5",
"fill-3": "#E48B1D"
"fill-3": "#E48B1D",
"fill-4": "#E48B1D"
},
"point-l": {
"stroke": "#595959",
"strokeActive": "#FCC947",
"fill": "#1F1F1F"
"strokeActive": "#FCC947"
},
"line": "#8C8C8C"
}

View File

@ -21,6 +21,7 @@ const mode = ref<Mode>(Mode.常规);
watch(editor.value.mouseClick, (v) => {
if (mode.value !== Mode.添加点位) return;
if (isEmpty(v)) return;
editor.value.addPoint(v, 1);
editor.value.addPoint(v, 11);
});
watch(editor.value.mouseBrush, (v) => {
@ -44,4 +45,4 @@ watch(editor.value.mouseBrush, (v) => {
</a-space>
</template>
<style scoped lang="scss"></style>
<!-- <style scoped lang="scss"></style> -->

10
src/pages/exception.vue Normal file
View File

@ -0,0 +1,10 @@
<script setup lang="ts">
type Props = {
code: string;
};
defineProps<Props>();
</script>
<template>{{ code }}</template>
<!-- <style scoped lang="scss"></style> -->

View File

@ -1,6 +1,6 @@
import { EDITOR_CONFIG, MapAreaType, type MapPen, MapPointType } from '@api/map';
import sTheme from '@core/theme.service';
import { EditType, LockState, Meta2d } from '@meta2d/core';
import { CanvasLayer, EditType, LockState, Meta2d } from '@meta2d/core';
import { useObservable } from '@vueuse/rxjs';
import { cloneDeep, get, pick } from 'lodash-es';
import { debounceTime, filter, map, Subject, switchMap } from 'rxjs';
@ -44,13 +44,19 @@ export class EditorService extends Meta2d {
),
);
public override find(target: string): MapPen[] {
return super.find(target);
}
//#region 点位
public async addPoint(p: Point, type = MapPointType.): Promise<void> {
const pen: MapPen = {
...p,
...this.#mapPoint(type),
...this.#mapPointImage(type),
name: 'point',
text: 'POINT',
tags: ['point', `point-${type}`],
label: 'POINT',
point: { type },
};
const { x, y, width, height } = this.getPenRect(pen);
@ -60,13 +66,17 @@ export class EditorService extends Meta2d {
this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] });
}
#mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'image'>> {
const theme = this.data().theme;
#mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'iconSize'>> {
const width = type < 10 ? 24 : 48;
const height = type < 10 ? 24 : 60;
const lineWidth = type < 10 ? 2 : 3;
const iconSize = type < 10 ? 4 : 10;
return { width, height, lineWidth, iconSize };
}
#mapPointImage(type: MapPointType): Required<Pick<MapPen, 'image' | 'canvasLayer'>> {
const theme = this.data().theme;
const image = type < 10 ? '' : `/point/${type}-${theme}.png`;
return { width, height, lineWidth, image };
return { image, canvasLayer: CanvasLayer.CanvasMain };
}
//#endregion
@ -81,6 +91,7 @@ export class EditorService extends Meta2d {
if (w * scale < 50 || h * scale < 60) return;
const pen: MapPen = {
name: 'area',
tags: ['area', `area-${type}`],
x: Math.min(p1.x, p2.x),
y: Math.min(p1.y, p2.y),
width: w,
@ -103,14 +114,31 @@ export class EditorService extends Meta2d {
watch(
() => sTheme.theme,
(v) => this.setTheme(v),
(v) => this.#load(v),
{ immediate: true },
);
}
#load(theme?: string): void {
if (theme) {
this.setTheme(theme);
}
this.find('point').forEach((pen) => {
if (!pen.point?.type) return;
if (pen.point.type < 10) return;
this.canvas.updateValue(pen, this.#mapPointImage(pen.point.type));
});
this.render();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
#listen(e: unknown, v: any) {
switch (e) {
case 'opened':
this.#load();
break;
case 'click':
case 'mousedown':
case 'mouseup':
@ -132,8 +160,8 @@ export class EditorService extends Meta2d {
//#region 绘制函数
function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
const theme = sTheme.editor;
const active = pen.calculative?.active;
const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {};
const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
const { type } = pen.point ?? {};
const { label = '' } = pen ?? {};
@ -143,28 +171,55 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
case MapPointType.:
case MapPointType.:
case MapPointType.:
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x + w / 2 - r, y + r);
ctx.arcTo(x + w / 2, y, x + w - r, y + h / 2 - r, r);
ctx.arcTo(x + w, y + h / 2, x + w / 2 + r, y + h - r, r);
ctx.arcTo(x + w / 2, y + h, x + r, y + h / 2 + r, r);
ctx.arcTo(x, y + h / 2, x + r, y + h / 2 - r, r);
ctx.closePath();
ctx.fillStyle = get(theme, `point-s.fill-${type}`) ?? '';
ctx.fill();
ctx.strokeStyle = get(theme, active ? 'point-s.strokeActive' : 'point-s.stroke') ?? '';
if (type === MapPointType.) {
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x + 0.66 * r, y + h / 2 - 0.66 * r);
ctx.lineTo(x + r, y + h / 2 - r);
ctx.moveTo(x + w / 2 - 0.66 * r, y + 0.66 * r);
ctx.lineTo(x + w / 2 - r, y + r);
ctx.moveTo(x + w / 2 + 0.66 * r, y + 0.66 * r);
ctx.lineTo(x + w / 2 + r, y + r);
ctx.moveTo(x + w - 0.66 * r, y + h / 2 - 0.66 * r);
ctx.lineTo(x + w - r, y + h / 2 - r);
ctx.moveTo(x + w - 0.66 * r, y + h / 2 + 0.66 * r);
ctx.lineTo(x + w - r, y + h / 2 + r);
ctx.moveTo(x + w / 2 + 0.66 * r, y + h - 0.66 * r);
ctx.lineTo(x + w / 2 + r, y + h - r);
ctx.moveTo(x + w / 2 - 0.66 * r, y + h - 0.66 * r);
ctx.lineTo(x + w / 2 - r, y + h - r);
ctx.moveTo(x + 0.66 * r, y + h / 2 + 0.66 * r);
ctx.lineTo(x + r, y + h / 2 + r);
}
ctx.stroke();
break;
case MapPointType.:
case MapPointType.:
case MapPointType.:
case MapPointType.:
case MapPointType.:
ctx.rect(x, y, width, height);
// ctx.fillStyle = get(theme, 'point-l.fill') ?? '';
// ctx.fill();
ctx.roundRect(x, y, w, h, r);
ctx.strokeStyle = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke') ?? '';
ctx.stroke();
break;
default:
break;
}
ctx.fillStyle = get(theme, 'color') ?? '';
ctx.font = '14px/20px system-ui';
ctx.font = `${fontSize}px/${lineHeight} ${fontFamily}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(label, x + width / 2, y - 10);
ctx.textBaseline = 'top';
ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
ctx.restore();
}
function anchorPoint(pen: MapPen): void {

View File

@ -1,7 +1,12 @@
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
export const ROUTES = Object.freeze<RouteRecordRaw[]>([
{ path: '/:pathMatch(.*)*', redirect: '/' },
{ path: '/:pathMatch(.*)*', redirect: '/exception/404' },
{
path: '/exception/:code',
props: true,
component: () => import('@/exception.vue'),
},
{
path: '/scene-editor/:id',
props: true,

View File

@ -29,7 +29,7 @@ class ThemeService {
public get ant(): AntdTheme {
switch (this.#theme.value) {
case Theme.Dark:
return { algorithm: theme.darkAlgorithm };
return THEME_MAP[`antd-${this.#theme.value}`] ?? {};
case Theme.Light:
default:
return { algorithm: theme.defaultAlgorithm };