فهرست منبع

feat: 初始化工程结构

bill 9 ماه پیش
والد
کامیت
3e608f3816
46فایلهای تغییر یافته به همراه1077 افزوده شده و 4793 حذف شده
  1. 3 0
      .env
  2. 6 4
      components.d.ts
  3. 11 0
      example/fuse/App.vue
  4. 0 0
      example/fuse/assets/WX20241031-111850.png
  5. 0 0
      example/fuse/assets/icons/BedsideCupboard.svg
  6. 0 0
      example/fuse/assets/icons/vue.svg
  7. 2 2
      src/example/fuse/main.ts
  8. 0 0
      example/fuse/store/index.ts
  9. 1 1
      src/example/fuse/styles/global.scss
  10. 2 2
      src/example/fuse/views/header/funds.ts
  11. 64 0
      example/fuse/views/header/header.vue
  12. 2 0
      example/fuse/views/header/index.ts
  13. 4 4
      src/example/fuse/views/home.vue
  14. 0 0
      example/fuse/views/slide/index.ts
  15. 3 3
      src/example/fuse/views/slide/menu.ts
  16. 23 0
      example/fuse/views/slide/slide-item.vue
  17. 57 0
      example/fuse/views/slide/slide.vue
  18. 1 1
      src/example/fuse/views/use-draw.ts
  19. 1 2
      index.html
  20. 0 4126
      package-lock.json
  21. 2 3
      package.json
  22. 357 315
      pnpm-lock.yaml
  23. 3 0
      src/constant/init.ts
  24. 8 0
      src/core/components/arrow/temp-arrow.vue
  25. 9 0
      src/core/components/circle/temp-circle.vue
  26. 81 0
      src/core/components/icon/temp-icon.vue
  27. 68 0
      src/core/components/image/temp-image.vue
  28. 8 0
      src/core/components/line/temp-line.vue
  29. 8 0
      src/core/components/polygon/temp-polygon.vue
  30. 30 0
      src/core/components/text/temp-text.vue
  31. 33 0
      src/core/propertys/color.vue
  32. 20 0
      src/core/propertys/controller.ts
  33. 200 0
      src/core/propertys/propertys.vue
  34. 4 4
      src/core/store/index.ts
  35. 0 134
      src/draw/core/Transformer.ts
  36. 0 11
      src/example/fuse/App.vue
  37. 0 8
      src/example/fuse/styles/element.scss
  38. 0 62
      src/example/fuse/views/header/header.vue
  39. 0 1
      src/example/fuse/views/header/index.ts
  40. 0 22
      src/example/fuse/views/slide/slide-item.vue
  41. 0 53
      src/example/fuse/views/slide/slide.vue
  42. 7 0
      src/global.d.ts
  43. 1 2
      src/index.ts
  44. 8 0
      src/vite-env.d.ts
  45. 0 1
      tsconfig.node.json
  46. 50 32
      vite.config.ts

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VITE_PRIMARY='#D8000A'
+VITE_TITLE='绘图'
+VITE_ENTRY='example/fuse/main.ts'

+ 6 - 4
components.d.ts

@@ -8,17 +8,19 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     AlideItem: typeof import('./src/components/alide-item.vue')['default']
-    Content: typeof import('./src/views/content/content.vue')['default']
+    Content: typeof import('./src/example/fuse/views/content/content.vue')['default']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
+    ElSpace: typeof import('element-plus/es')['ElSpace']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
-    Header: typeof import('./src/views/header/header.vue')['default']
+    Header: typeof import('./src/example/fuse/views/header/header.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
-    Slide: typeof import('./src/views/slide/slide.vue')['default']
-    SlideItem: typeof import('./src/views/slide/slide-item.vue')['default']
+    Slide: typeof import('./src/example/fuse/views/slide/slide.vue')['default']
+    SlideItem: typeof import('./src/example/fuse/views/slide/slide-item.vue')['default']
   }
 }

+ 11 - 0
example/fuse/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <ElConfigProvider :locale="zhCn">
+    <Home />
+  </ElConfigProvider>
+</template>
+
+<script setup lang="ts">
+import { ElConfigProvider } from "element-plus";
+import zhCn from "element-plus/es/locale/lang/zh-cn.mjs";
+import Home from "./views/home.vue";
+</script>

src/assets/WX20241031-111850.png → example/fuse/assets/WX20241031-111850.png


src/assets/icons/BedsideCupboard.svg → example/fuse/assets/icons/BedsideCupboard.svg


src/assets/icons/vue.svg → example/fuse/assets/icons/vue.svg


+ 2 - 2
src/example/fuse/main.ts

@@ -1,13 +1,13 @@
 import { createApp } from 'vue'
 import './styles/global.scss'
+import 'element-plus/theme-chalk/src/index.scss'
 import VueKonva from "vue-konva";
 import App from './App.vue'
 import { pinia } from './store'
-import { version } from '@/../package.json'
 
 const app = createApp(App)
 app.use(pinia)
 app.use(VueKonva)
 app.mount('#app')
 
-console.log('当前版本', version)
+console.log('当前版本', window.__VERSION__)

src/example/fuse/store/index.ts → example/fuse/store/index.ts


+ 1 - 1
src/example/fuse/styles/global.scss

@@ -1,4 +1,4 @@
-@use 'element-plus/theme-chalk/src/common/var.scss';
+@use 'element-plus/theme-chalk/src/common/var';
 
 $headerSize: 70px;
 $slideSize: 100px;

+ 2 - 2
src/example/fuse/views/header/funds.ts

@@ -1,4 +1,4 @@
-import { useDraw } from "@/views/use-draw.ts";
+import { useDraw } from "../use-draw.ts";
 
 export const revoke = () => {
 
@@ -41,7 +41,7 @@ export const exportData = () => {
 export const gotoDrawing = () => {
 }
 
-import bgImage from '@/assets/WX20241031-111850.png'
+import bgImage from '../../assets/WX20241031-111850.png'
 export const useHeaderFunds = () => {
 	const draw = useDraw()
 	const setBGImage = () => {

+ 64 - 0
example/fuse/views/header/header.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="header">
+    <div class="nav">
+      <el-button type="primary" plain>返回</el-button>
+    </div>
+    <div class="draw-operate">
+      <div v-for="group in groups">
+        <el-icon class="operate" v-for="fn in group" @click="fn()">
+          {{ funds.describes.get(fn)?.name }}
+        </el-icon>
+      </div>
+    </div>
+    <div class="saves">
+      <el-button type="primary" plain>保存</el-button>
+      <el-button type="primary" plain>导出</el-button>
+      <el-button>图纸</el-button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ElButton, ElIcon } from "element-plus";
+import { useHeaderFunds } from "./funds.ts";
+const funds = useHeaderFunds();
+const groups = [
+  [funds.revoke, funds.recover],
+  [funds.clear, funds.rotate, funds.full],
+  [funds.aiImport, funds.setBGImage, funds.gotoVR],
+];
+if (import.meta.env.DEV) {
+  groups.push([funds.toggleHit]);
+}
+</script>
+
+<style lang="scss" scoped>
+@use 'element-plus/theme-chalk/src/common/var';
+
+.header {
+  background-color: var.$color-primary;
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  justify-content: space-between;
+}
+
+.draw-operate {
+  text-align: center;
+  color: #fff;
+  display: flex;
+  align-items: center;
+
+  > div:not(:last-child) {
+    padding-right: 10px;
+    margin-right: 10px;
+    border-right: 1px solid #fff;
+  }
+
+  i {
+    width: auto;
+    margin: 0 5px;
+  }
+}
+</style>
+/

+ 2 - 0
example/fuse/views/header/index.ts

@@ -0,0 +1,2 @@
+export * from './funds.ts'
+export { default as Header } from './header.vue'

+ 4 - 4
src/example/fuse/views/home.vue

@@ -11,11 +11,11 @@
 </template>
 
 <script lang="ts" setup>
-import Header from "@/views/header/header.vue";
+import { Header } from "./header/";
 import { ref } from "vue";
 import { Slide } from "./slide";
-import { DrawExpose, Renderer } from "@/draw";
-import { installDraw } from "@/views/use-draw.ts";
+import { DrawExpose, Renderer } from "@/index.ts";
+import { installDraw } from "./use-draw.ts";
 
 const drawEle = ref<HTMLDivElement | null>(null);
 const draw = ref<DrawExpose>();
@@ -23,7 +23,7 @@ installDraw(draw);
 </script>
 
 <style lang="scss" scoped>
-@use '@/styles/global';
+@use '../styles/global';
 
 .layout {
   display: flex;

src/example/fuse/views/slide/index.ts → example/fuse/views/slide/index.ts


+ 3 - 3
src/example/fuse/views/slide/menu.ts

@@ -1,4 +1,4 @@
-import { ShapeType, shapeNames, PresetAdd } from '@/draw'
+import { ShapeType, shapeNames, PresetAdd } from '@/index.ts'
 import { v4 as uuid } from 'uuid'
 
 
@@ -40,8 +40,8 @@ export const getValue = (payload: PresetAdd, queryMenus = menus): string | undef
 }
 
 
-import svg1 from '@/assets/icons/vue.svg'
-import svg2 from '@/assets/icons/BedsideCupboard.svg'
+import svg1 from '../../assets/icons/vue.svg'
+import svg2 from '../../assets/icons/BedsideCupboard.svg'
 import { toRaw } from "vue";
 
 export const menus: MenuItem[] = [

+ 23 - 0
example/fuse/views/slide/slide-item.vue

@@ -0,0 +1,23 @@
+<template>
+  <el-sub-menu :index="data.value || data.name" v-if="data.children?.length">
+    <template #title>
+      <p class="center">{{ data.name }}</p>
+    </template>
+    <SlideItem v-for="item in data.children" :data="item" />
+  </el-sub-menu>
+  <el-menu-item v-else :index="data.value">
+    <p class="center">{{ data.name }}</p>
+  </el-menu-item>
+</template>
+
+<script lang="ts" setup>
+import { ElSubMenu, ElMenuItem } from "element-plus";
+import { MenuItem } from "@/example/fuse/views/slide/menu.ts";
+defineProps<{ data: MenuItem }>();
+</script>
+
+<style scoped lang="scss">
+.center {
+  width: 100%;
+}
+</style>

+ 57 - 0
example/fuse/views/slide/slide.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="slide">
+    <el-menu
+      :default-active="draw?.presetAdd && getValue(draw?.presetAdd)"
+      class="slide-menu"
+      @select="selectHandler"
+      collapse
+      :popper-offset="0"
+      popper-class="slide-popper"
+    >
+      <SlideItem v-for="menu in menus" :data="menu" />
+    </el-menu>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ElMenu } from "element-plus";
+import { getItem, getValue, menus } from "./menu.ts";
+import SlideItem from "./slide-item.vue";
+import { useDraw } from "../use-draw.ts";
+
+const draw = useDraw();
+const selectHandler = (value: string) => {
+  const item = getItem(value);
+  if (!item || !item.payload) throw "无效菜单";
+  draw.value!.enterMouseAddShape(item.payload.type, item.payload.preset);
+};
+</script>
+/
+
+<style lang="scss" scoped>
+@use '../../styles/global';
+
+.slide {
+  transition: transform 0.3s ease;
+  margin-left: 0;
+
+  &.hide {
+    transform: translateX(-100%);
+  }
+}
+
+.slide-menu {
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+}
+</style>
+
+<style lang="scss">
+@use '../../styles/global';
+
+.slide-popper .el-menu--popup {
+  min-width: global.$slideSize;
+}
+</style>
+/

+ 1 - 1
src/example/fuse/views/use-draw.ts

@@ -1,5 +1,5 @@
 import { inject, provide, Ref } from 'vue'
-import { DrawExpose } from "@/draw";
+import { DrawExpose } from "@/index.ts";
 
 const actionKey = Symbol('drawAction');
 export const installDraw = (drawRef: Ref<DrawExpose | undefined>) => {

+ 1 - 2
index.html

@@ -4,10 +4,9 @@
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite + Vue + TS</title>
+    <title><%- title %></title>
   </head>
   <body>
     <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
   </body>
 </html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 4126
package-lock.json


+ 2 - 3
package.json

@@ -11,6 +11,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
     "@tweenjs/tween.js": "^25.0.0",
+    "@types/node": "^22.9.0",
     "@types/three": "^0.169.0",
     "element-plus": "^2.8.6",
     "konva": "^9.3.16",
@@ -20,10 +21,8 @@
     "sass": "^1.80.4",
     "stateshot": "^1.3.5",
     "three": "^0.169.0",
-    "unplugin-auto-import": "^0.18.3",
-    "unplugin-element-plus": "^0.8.0",
-    "unplugin-vue-components": "^0.27.4",
     "uuid": "^11.0.2",
+    "vite-plugin-html": "^3.2.2",
     "vue": "^3.5.12",
     "vue-konva": "^3.1.2"
   },

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 357 - 315
pnpm-lock.yaml


+ 3 - 0
src/constant/init.ts

@@ -0,0 +1,3 @@
+import { Transform } from "konva/lib/Util";
+
+export const initTransform = new Transform()

+ 8 - 0
src/core/components/arrow/temp-arrow.vue

@@ -0,0 +1,8 @@
+<template>
+	<v-arrow :config="{...dataToConfig(data), opacity: 0.3}" />
+</template>
+
+<script lang="ts" setup>
+import { ArrowData, dataToConfig } from "./index.ts";
+defineProps<{ data: ArrowData }>()
+</script>

+ 9 - 0
src/core/components/circle/temp-circle.vue

@@ -0,0 +1,9 @@
+<template>
+	<v-circle :config="dataToConfig(data)">
+	</v-circle>
+</template>
+
+<script lang="ts" setup>
+import { CircleData, dataToConfig } from "./index.ts";
+defineProps<{ data: CircleData }>()
+</script>

+ 81 - 0
src/core/components/icon/temp-icon.vue

@@ -0,0 +1,81 @@
+<template>
+	<v-group :config="groupConfig" v-if="groupConfig" ref="shape">
+		<v-group :config="initDecMat" >
+			<v-rect :config="rectConfig" />
+			<v-path v-for="config in pathConfigs" :config="config"/>
+		</v-group>
+	</v-group>
+</template>
+
+<script lang="ts" setup>
+import { IconData, dataToConfig } from "./index.ts";
+import { computed, ref, watch } from "vue";
+import { getSvgContent, parseSvgContent, SVGParseResult } from "@/utils/resource.ts";
+import { Group } from "konva/lib/Group";
+import { DC } from "@/helper/deconstruction";
+import { Transform } from "konva/lib/Util";
+
+const props = defineProps<{ data: IconData, addMode?: boolean }>()
+const svg = ref<SVGParseResult | null>(null)
+const data = computed(() => dataToConfig(props.data))
+const shape = ref<DC<Group>>()
+
+defineExpose({ 
+  get shape() {
+    return shape.value
+  } 
+})
+
+watch(() => data.value.url, async url => {
+	svg.value = null;
+	const svgContent = await getSvgContent(url)
+	svg.value = parseSvgContent(svgContent)
+}, {immediate: true})
+
+const pathConfigs = computed(() => {
+	if (!svg.value) return [];
+	return svg.value.paths.map(path => ({
+		...path,
+		...data.value,
+		offset: {x: svg.value!.x, y: svg.value!.y}
+	}))
+})
+
+const initDecMat = computed(() => {
+	if (!svg.value) return null;
+	let w = data.value.width
+	let h = data.value.height
+	w = w || svg.value.width || 0
+	h = h || svg.value.height || 0
+
+	const scale = {
+		x: w / svg.value.width,
+		y: h / svg.value.height,
+	}
+
+	return new Transform()
+		.scale(scale.x, scale.y)
+		.multiply(new Transform().translate(-svg.value.width / 2, -svg.value.height / 2))
+		.decompose()
+})
+
+const groupConfig = computed(() => {
+	return {
+		...new Transform(data.value.mat).decompose(),
+		opacity: props.addMode ? 0.3 : 1,
+	}
+})
+
+const rectConfig = computed(() => {
+	if (!svg.value) return null;
+	return {
+		fill: data.value.coverFill,
+		id: 'rep',
+		stroke: data.value.coverStroke,
+		opacity: data.value.coverOpcatiy,
+		strokeWidth: data.value.coverStrokeWidth,
+		width: svg.value.width,
+		height: svg.value.height,
+	}
+})
+</script>

+ 68 - 0
src/core/components/image/temp-image.vue

@@ -0,0 +1,68 @@
+<template>
+	<v-group :config="groupConfig" v-if="groupConfig" ref="shape">
+		<v-image :config="{...style, ...config}" v-if="image"/>
+	</v-group>
+</template>
+
+<script lang="ts" setup>
+import { ImageData, dataToConfig } from "./index.ts";
+import { computed, ref, watch } from "vue";
+import { getImage } from "@/utils/resource.ts";
+import { useResize } from "@/core/hook/use-event.ts";
+import { Transform } from "konva/lib/Util";
+import { Group } from "konva/lib/Group";
+import { DC } from "@/helper/deconstruction";
+
+const props = defineProps<{ data: ImageData, addMode?: boolean }>()
+const image = ref<HTMLImageElement | null>(null)
+const shape = ref<DC<Group>>()
+
+defineExpose({ 
+  get shape() {
+    return shape.value
+  } 
+})
+
+const style = computed(() => dataToConfig(props.data))
+
+watch(() => props.data.url, async url => {
+	image.value = null;
+	image.value = await getImage(url)
+
+}, {immediate: true})
+
+const size = useResize()
+const config = computed(() => {
+	let w = props.data.width
+	let h = props.data.height
+
+	// 认为是百分比
+	if (image.value && size.value && (w <= 1 || h <= 1)) {
+		w = w <= 1 ? size.value.width * w : w
+		h = h <= 1 ? size.value.height * h : h
+		w = w || (image.value.width / image.value.height) * h
+		h = h || (image.value.height / image.value.width) * w
+	}
+	w = w || image.value?.width || 0
+	h = h || image.value?.height || 0
+
+
+	return {
+		image: image.value,
+		opacity: props.addMode ? 0.3 : 1,
+		stroke: 'red',
+		width: w,
+		height: h,
+		offset: {
+			x: w / 2,
+			y: h / 2,
+		},
+	}
+})
+
+const groupConfig = computed(() => {
+	return {
+		...new Transform(props.data.mat).decompose()
+	}
+})
+</script>

+ 8 - 0
src/core/components/line/temp-line.vue

@@ -0,0 +1,8 @@
+<template>
+	<v-line :config="{...dataToConfig(data), opacity: 0.3}" />
+</template>
+
+<script lang="ts" setup>
+import { LineData, dataToConfig } from "./index.ts";
+defineProps<{ data: LineData }>()
+</script>

+ 8 - 0
src/core/components/polygon/temp-polygon.vue

@@ -0,0 +1,8 @@
+<template>
+	<v-line :config="{...dataToConfig(data), opacity: 0.3}" />
+</template>
+
+<script lang="ts" setup>
+import { PolygonData, dataToConfig } from "./index.ts";
+defineProps<{ data: PolygonData }>()
+</script>

+ 30 - 0
src/core/components/text/temp-text.vue

@@ -0,0 +1,30 @@
+<template>
+	<v-text :config="config" ref="shape" name="text">
+	</v-text>
+</template>
+
+<script lang="ts" setup>
+import { TextData, dataToConfig } from "./index.ts";
+import { computed, ref } from "vue";
+import { DC } from "@/helper/deconstruction";
+import { Transform } from "konva/lib/Util";
+import { Group } from "konva/lib/Group";
+
+const props = defineProps<{ data: TextData, addMode?: boolean }>()
+const shape = ref<DC<Group>>()
+defineExpose({ 
+  get shape() {
+    return shape.value
+  } 
+})
+
+const config = computed(() => {
+	const dec = new Transform(props.data.mat).decompose()
+	const conf = {
+		...dataToConfig(props.data),
+		...dec,
+		opacity: props.addMode ? 0.3 : 1
+	}
+	return conf
+})
+</script>

+ 33 - 0
src/core/propertys/color.vue

@@ -0,0 +1,33 @@
+<template>
+  <el-color-picker
+    :modelValue="value"
+    @active-change="(color) => $emit('update:value', color)"
+    size="small"
+    :predefine="predefineColors"
+  />
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import { ElColorPicker } from "element-plus";
+
+defineProps<{ value: string }>();
+defineEmits<{ (e: "update:value", val: string | null): string }>();
+
+const predefineColors = ref([
+  "#ff4500",
+  "#ff8c00",
+  "#ffd700",
+  "#90ee90",
+  "#00ced1",
+  "#1e90ff",
+  "#c71585",
+  "rgba(255, 69, 0, 0.68)",
+  "rgb(255, 120, 0)",
+  "hsv(51, 100, 98)",
+  "hsva(120, 40, 94, 0.5)",
+  "hsl(181, 100%, 37%)",
+  "hsla(209, 100%, 56%, 0.73)",
+  "#c7158577",
+]);
+</script>

+ 20 - 0
src/core/propertys/controller.ts

@@ -0,0 +1,20 @@
+import Color from "./color.vue";
+
+export const colorType = "color";
+
+export const propertyComponents = {
+  [colorType]: Color,
+};
+
+export type PropertyType = keyof typeof propertyComponents;
+export type PropertyComponents<T extends PropertyType> =
+  (typeof propertyComponents)[T];
+
+export type PropertyValue<T extends PropertyType> = InstanceType<PropertyComponents<T>>['$props']['value']
+
+export type PropertyDescribes = Record<string, { type: PropertyType; label: string }>
+export type PropertysData<T extends PropertyDescribes = PropertyDescribes> = {
+  [K in keyof T]: PropertyValue<T[K]['type']>
+}
+
+export { default as Propertys } from './propertys.vue'

+ 200 - 0
src/core/propertys/propertys.vue

@@ -0,0 +1,200 @@
+<template>
+  <Teleport :to="mount" v-if="pointer">
+    <div
+      class="mount-property"
+      @pointerdown.stop
+      @pointermove.stop
+      @pointerup.stop
+      @mousedown.stop
+      @mousemove.stop
+      @mouseup.stop
+    >
+      <div class="mask" @click.stop="prevDefault" :class="{ show: showMask }"></div>
+      <div
+        :style="{ transform: pointer }"
+        :size="8"
+        class="propertys-controller"
+        ref="layout"
+      >
+        <div class="item" v-for="(val, key) in describes">
+          <span>{{ val.label }}</span>
+          <div>
+            <component
+              @click.stop="showMask = true"
+              v-model:value="data[key]"
+              :is="propertyComponents[val.type]"
+              :key="key"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </Teleport>
+</template>
+
+<script lang="ts" setup>
+import { computed, nextTick, ref, watch } from "vue";
+import { useStage, useTransformIngShapes } from "../hook/use-global-vars.ts";
+import { PropertyDescribes, propertyComponents } from "./controller.ts";
+import { DC, EntityShape } from "@/helper/deconstruction";
+import { useMouseShapeStatus } from "../hook/use-mouse-status.ts";
+import { useViewerTransformConfig } from "../hook/use-viewer.ts";
+import { Transform } from "konva/lib/Util";
+
+const props = defineProps<{
+  target: DC<EntityShape> | undefined;
+  data: Record<string, any>;
+  describes: PropertyDescribes;
+}>();
+
+const layout = ref<HTMLDivElement>();
+const showMask = ref(false);
+const prevDefault = (ev: MouseEvent) => {
+  showMask.value = false;
+  const mouseEvent = new MouseEvent("pointermove", ev);
+  mount.value!.querySelector(".konvajs-content")!.dispatchEvent(mouseEvent);
+};
+
+const stage = useStage();
+const mount = computed(() => {
+  if (!stage.value) return;
+  const $stage = stage.value.getStage();
+  return $stage.container();
+});
+
+const status = useMouseShapeStatus(computed(() => props.target));
+const transformIngShapes = useTransformIngShapes();
+const hidden = computed(
+  () =>
+    !stage.value?.getStage() ||
+    !props.target?.getNode() ||
+    !status.value.hover ||
+    transformIngShapes.value.length !== 0
+);
+
+const move = new Transform();
+const pointer = ref<string | null>(null);
+const calcPointer = async () => {
+  const $stage = stage.value!.getStage();
+  const mousePosition = $stage.pointerPos;
+  if (!mousePosition) {
+    pointer.value = null;
+    return;
+  }
+  const $shape = props.target!.getNode();
+  const shapeRect = $shape.getClientRect();
+
+  const shapeR = shapeRect.x + shapeRect.width;
+  const shapeB = shapeRect.y + shapeRect.height;
+  let x = Math.min(Math.max(mousePosition.x, shapeRect.x), shapeR);
+  let y = Math.min(Math.max(mousePosition.y, shapeRect.y), shapeB);
+
+  move.reset();
+  move.translate(x, y);
+  pointer.value = `matrix(${move.m.join(",")})`;
+  await nextTick();
+
+  const domRect = layout.value!.getBoundingClientRect();
+  x = x - domRect.width / 2;
+  x = Math.max(x, shapeRect.x);
+
+  if (x + domRect.width > shapeR) {
+    x = shapeR - domRect.width;
+  }
+  if (y + domRect.height > shapeB) {
+    y = y - domRect.height;
+  }
+
+  x = Math.max(x, 10);
+  y = Math.max(y, 10);
+  if (x + domRect.width > $stage.width() - 10) {
+    x = $stage.width() - domRect.width - 10;
+  }
+  if (y + domRect.height > $stage.height() - 10) {
+    y = $stage.height() - domRect.height - 10;
+  }
+
+  move.reset();
+  move.translate(x, y);
+  pointer.value = `matrix(${move.m.join(",")})`;
+};
+let timeout: any;
+const resetPointer = () => {
+  if (hidden.value) {
+    pointer.value = null;
+    return;
+  } else if (pointer.value) {
+    return;
+  }
+  clearTimeout(timeout);
+  timeout = setTimeout(calcPointer, 300);
+};
+
+watch(hidden, resetPointer);
+watch(useViewerTransformConfig(), () => {
+  pointer.value = null;
+  resetPointer();
+});
+watch(() => props.data, resetPointer);
+</script>
+
+<style lang="scss" scoped>
+.mount-property {
+  position: absolute;
+  inset: 0;
+  overflow: hidden;
+  pointer-events: none;
+}
+.mask {
+  position: absolute;
+  inset: 0;
+  background-color: rgba(0, 0, 0, 0);
+  pointer-events: none;
+  transition: background-color 0.3s ease;
+  &.show {
+    background-color: rgba(0, 0, 0, 0.1);
+    pointer-events: all;
+  }
+}
+
+.propertys-controller {
+  position: absolute;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+  background-color: #ffffff;
+  left: 0;
+  top: 0;
+  overflow: hidden;
+  padding: 6px 12px;
+  color: #303133;
+  width: max-content;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+  min-width: 10px;
+  // pointer-events: none;
+
+  .item {
+    display: flex;
+    align-items: center;
+    pointer-events: all;
+    span {
+      margin-right: 4px;
+    }
+
+    &:not(:first-child) {
+      span {
+        margin-left: 4px;
+      }
+      &::before {
+        display: block;
+        flex: 0 0 auto;
+        content: "|";
+        opacity: 0.7;
+        margin-left: 4px;
+      }
+    }
+  }
+}
+</style>

+ 4 - 4
src/core/store/index.ts

@@ -135,7 +135,7 @@ export const data = reactive({
 	],
 	"icon": [
 		{
-			"url": "/src/assets/icons/vue.svg",
+			"url": "/example/fuse//assets/icons/vue.svg",
 			"mat": [
 				1,
 				0,
@@ -146,7 +146,7 @@ export const data = reactive({
 			]
 		},
 		{
-			"url": "/src/assets/icons/vue.svg",
+			"url": "/example/fuse//assets/icons/vue.svg",
 			"mat": [
 				1,
 				0,
@@ -157,7 +157,7 @@ export const data = reactive({
 			]
 		},
 		{
-			"url": "/src/assets/icons/vue.svg",
+			"url": "/example/fuse/assets/icons/vue.svg",
 			"mat": [
 				1,
 				0,
@@ -168,7 +168,7 @@ export const data = reactive({
 			]
 		},
 		{
-			"url": "/src/assets/icons/BedsideCupboard.svg",
+			"url": "/example/fuse/assets/icons/BedsideCupboard.svg",
 			"width": 300,
 			"height": 300,
 			"stroke": "red",

+ 0 - 134
src/draw/core/Transformer.ts

@@ -1,134 +0,0 @@
-import { Transformer as BaseTransformer, TransformerConfig } from "konva/lib/shapes/Transformer";
-import * as Util_1 from "konva/lib/Util";
-import * as Global_1 from "konva/lib/Global";
-import { KonvaEventObject } from "konva/lib/Node";
-
-export class Transformer extends BaseTransformer {
-  constructor(config?: TransformerConfig) {
-    super(config)
-  }
-
-  __getNodeRect(): { x: number; y: number; width: number; height: number; rotation: number; } {
-    return super.__getNodeRect()
-  }
-
-
-  _fitNodesInto(newAttrs: any, evt: KonvaEventObject<any>) {
-    const oldAttrs = this._getNodeRect();
-    const minSize = 1;
-    if (Util_1.Util._inRange(newAttrs.width, -this.padding() * 2 - minSize, minSize)) {
-        this.update();
-        return;
-    }
-    if (Util_1.Util._inRange(newAttrs.height, -this.padding() * 2 - minSize, minSize)) {
-        this.update();
-        return;
-    }
-    const t = new Util_1.Transform();
-    t.rotate(Global_1.Konva.getAngle(this.rotation()));
-    if (this._movingAnchorName &&
-        newAttrs.width < 0 &&
-        this._movingAnchorName.indexOf('left') >= 0) {
-        const offset = t.point({
-            x: -this.padding() * 2,
-            y: 0,
-        });
-        newAttrs.x += offset.x;
-        newAttrs.y += offset.y;
-        newAttrs.width += this.padding() * 2;
-        this._movingAnchorName = this._movingAnchorName.replace('left', 'right');
-        this._anchorDragOffset.x -= offset.x;
-        this._anchorDragOffset.y -= offset.y;
-    }
-    else if (this._movingAnchorName &&
-        newAttrs.width < 0 &&
-        this._movingAnchorName.indexOf('right') >= 0) {
-        const offset = t.point({
-            x: this.padding() * 2,
-            y: 0,
-        });
-        this._movingAnchorName = this._movingAnchorName.replace('right', 'left');
-        this._anchorDragOffset.x -= offset.x;
-        this._anchorDragOffset.y -= offset.y;
-        newAttrs.width += this.padding() * 2;
-    }
-    if (this._movingAnchorName &&
-        newAttrs.height < 0 &&
-        this._movingAnchorName.indexOf('top') >= 0) {
-        const offset = t.point({
-            x: 0,
-            y: -this.padding() * 2,
-        });
-        newAttrs.x += offset.x;
-        newAttrs.y += offset.y;
-        this._movingAnchorName = this._movingAnchorName.replace('top', 'bottom');
-        this._anchorDragOffset.x -= offset.x;
-        this._anchorDragOffset.y -= offset.y;
-        newAttrs.height += this.padding() * 2;
-    }
-    else if (this._movingAnchorName &&
-        newAttrs.height < 0 &&
-        this._movingAnchorName.indexOf('bottom') >= 0) {
-        const offset = t.point({
-            x: 0,
-            y: this.padding() * 2,
-        });
-        this._movingAnchorName = this._movingAnchorName.replace('bottom', 'top');
-        this._anchorDragOffset.x -= offset.x;
-        this._anchorDragOffset.y -= offset.y;
-        newAttrs.height += this.padding() * 2;
-    }
-    if (this.boundBoxFunc()) {
-        const bounded = this.boundBoxFunc()(oldAttrs, newAttrs);
-        if (bounded) {
-            newAttrs = bounded;
-        }
-        else {
-            Util_1.Util.warn('boundBoxFunc returned falsy. You should return new bound rect from it!');
-        }
-    }
-    const baseSize = 10000000;
-    const oldTr = new Util_1.Transform();
-    oldTr.translate(oldAttrs.x, oldAttrs.y);
-    oldTr.rotate(oldAttrs.rotation);
-    oldTr.scale(oldAttrs.width / baseSize, oldAttrs.height / baseSize);
-    const newTr = new Util_1.Transform();
-    const newScaleX = newAttrs.width / baseSize;
-    const newScaleY = newAttrs.height / baseSize;
-    if (this.flipEnabled() === false) {
-        newTr.translate(newAttrs.x, newAttrs.y);
-        newTr.rotate(newAttrs.rotation);
-        newTr.translate(newAttrs.width < 0 ? newAttrs.width : 0, newAttrs.height < 0 ? newAttrs.height : 0);
-        newTr.scale(Math.abs(newScaleX), Math.abs(newScaleY));
-    }
-    else {
-        newTr.translate(newAttrs.x, newAttrs.y);
-        newTr.rotate(newAttrs.rotation);
-        newTr.scale(newScaleX, newScaleY);
-    }
-    const delta = newTr.multiply(oldTr.invert());
-    this._nodes.forEach((node) => {
-        var _a;
-        const parentTransform = node.getParent()!.getAbsoluteTransform();
-        const localTransform = node.getTransform().copy();
-        localTransform.translate(node.offsetX(), node.offsetY());
-        const newLocalTransform = new Util_1.Transform();
-        newLocalTransform
-            .multiply(parentTransform.copy().invert())
-            .multiply(delta)
-            .multiply(parentTransform)
-            .multiply(localTransform);
-        const attrs = newLocalTransform.decompose();
-        node.setAttrs(attrs);
-        (_a = node.getLayer()) === null || _a === void 0 ? void 0 : _a.batchDraw();
-    });
-    this.rotation(Util_1.Util._getRotation(newAttrs.rotation));
-    this._nodes.forEach((node) => {
-        this._fire('transform', { evt: evt, target: node });
-        node._fire('transform', { evt: evt, target: node });
-    });
-    this._resetTransformCache();
-    this.update();
-    this.getLayer()!.batchDraw();
-  }
-}

+ 0 - 11
src/example/fuse/App.vue

@@ -1,11 +0,0 @@
-
-<template>
-	<ElConfigProvider :locale="zhCn">
-		<Home />
-	</ElConfigProvider>
-</template>
-
-<script setup lang="ts">
-import zhCn from 'element-plus/es/locale/lang/zh-cn.mjs'
-import Home from "./views/home.vue";
-</script>

+ 0 - 8
src/example/fuse/styles/element.scss

@@ -1,8 +0,0 @@
-@forward 'element-plus/theme-chalk/src/common/var.scss' with (
-  $colors: (
-    'primary': (
-      'base': #D8000A,
-    ),
-  ),
- $common-component-size: ('default': 40px)
-);

+ 0 - 62
src/example/fuse/views/header/header.vue

@@ -1,62 +0,0 @@
-<template>
-	<div class="header">
-		<div class="nav">
-			<el-button type="primary" plain>返回</el-button>
-		</div>
-		<div class="draw-operate">
-			<div v-for="group in groups">
-				<el-icon class="operate" v-for="fn in group" @click="fn()">
-					{{ funds.describes.get(fn)?.name }}
-				</el-icon>
-			</div>
-		</div>
-		<div class="saves">
-			<el-button type="primary" plain>保存</el-button>
-			<el-button type="primary" plain>导出</el-button>
-			<el-button>图纸</el-button>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { useHeaderFunds } from './funds.ts'
-const funds = useHeaderFunds()
-const groups = [
-	[funds.revoke, funds.recover],
-	[funds.clear, funds.rotate, funds.full],
-	[funds.aiImport, funds.setBGImage, funds.gotoVR],
-]
-if (import.meta.env.DEV) {
-	groups.push([funds.toggleHit])
-}
-</script>
-
-<style lang="scss" scoped>
-@use 'element-plus/theme-chalk/src/common/var';
-
-.header {
-	background-color: var.$color-primary;
-	display: flex;
-	align-items: center;
-	padding: 10px;
-	justify-content: space-between;
-}
-
-.draw-operate {
-	text-align: center;
-	color: #fff;
-	display: flex;
-	align-items: center;
-
-	> div:not(:last-child) {
-		padding-right: 10px;
-		margin-right: 10px;
-		border-right: 1px solid #fff;
-	}
-
-	i {
-		width: auto;
-		margin: 0 5px;
-	}
-}
-</style>/

+ 0 - 1
src/example/fuse/views/header/index.ts

@@ -1 +0,0 @@
-export * from './funds.ts'

+ 0 - 22
src/example/fuse/views/slide/slide-item.vue

@@ -1,22 +0,0 @@
-<template>
-	<el-sub-menu :index="data.value" v-if="data.children?.length">
-		<template #title>
-			<p class="center">{{ data.name }}</p>
-		</template>
-		<SlideItem v-for="item in data.children" :data="item" />
-	</el-sub-menu>
-	<el-menu-item v-else :index="data.value">
-		<p class="center">{{data.name}}</p>
-	</el-menu-item>
-</template>
-
-<script lang="ts" setup>
-import { MenuItem } from "@/views/slide/menu.ts";
-defineProps<{ data: MenuItem }>()
-</script>
-
-<style scoped lang="scss">
-.center {
-	width: 100%;
-}
-</style>

+ 0 - 53
src/example/fuse/views/slide/slide.vue

@@ -1,53 +0,0 @@
-<template>
-	<div class="slide">
-		<el-menu
-				:default-active="draw?.presetAdd && getValue(draw?.presetAdd)"
-				class="slide-menu"
-				@select="selectHandler"
-				collapse
-				:popper-offset="0"
-				popper-class="slide-popper">
-			<SlideItem v-for="menu in menus" :data="menu"/>
-		</el-menu>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import { getItem, getValue, menus } from './menu.ts'
-import SlideItem from "@/views/slide/slide-item.vue";
-import { useDraw } from "@/views/use-draw.ts";
-
-const draw  = useDraw()
-const selectHandler = (value: string) => {
-	const item = getItem(value)
-	if (!item || !item.payload) throw '无效菜单'
-	draw.value!.enterMouseAddShape(item.payload.type, item.payload.preset)
-}
-</script>/
-
-<style lang="scss" scoped>
-@use '@/styles/global';
-
-.slide {
-	transition: transform .3s ease;
-	margin-left: 0;
-
-	&.hide {
-		transform: translateX(-100%);
-	}
-}
-
-.slide-menu {
-	width: 100%;
-	height: 100%;
-	overflow-y: auto;
-}
-</style>
-
-<style lang="scss">
-@use '@/styles/global';
-
-.slide-popper .el-menu--popup {
-	min-width: global.$slideSize;
-}
-</style>/

+ 7 - 0
src/global.d.ts

@@ -0,0 +1,7 @@
+declare global {  
+  interface Window {  
+    __VERSION__: string  
+  }  
+}  
+
+export {};  

+ 1 - 2
src/index.ts

@@ -10,5 +10,4 @@ for (const key in components) {
 	shapeNames[key as ShapeType] = components[key as ShapeType].shapeName
 }
 
-export type { DrawExpose } from './core/hook/use-expose.ts'
-export type { PresetAdd } from './core/hook/use-global-vars.ts'
+export type { DrawExpose } from './core/hook/use-expose.ts'

+ 8 - 0
src/vite-env.d.ts

@@ -1 +1,9 @@
 /// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly VITE_PRIMARY: string
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}

+ 0 - 1
tsconfig.node.json

@@ -4,7 +4,6 @@
     "lib": ["ES2023"],
     "module": "ESNext",
     "skipLibCheck": true,
-
     /* Bundler mode */
     "moduleResolution": "Bundler",
     "allowImportingTsExtensions": true,

+ 50 - 32
vite.config.ts

@@ -1,36 +1,54 @@
-import { defineConfig } from 'vite'
-import AutoImport from 'unplugin-auto-import/vite'
-import Components from 'unplugin-vue-components/vite'
-import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
-import vue from '@vitejs/plugin-vue'
-import path from 'node:path'
+import { defineConfig, loadEnv } from "vite";
+import vue from "@vitejs/plugin-vue";
+import path from "node:path";
+import { createHtmlPlugin } from 'vite-plugin-html';  
+import { version } from './package.json'
 
 // https://vite.dev/config/
-export default defineConfig({
-	resolve: {
-		alias: {
-			'@/': `${path.resolve(__dirname, 'src')}/`,
-		},
-	},
-  css: {
-    preprocessorOptions: {
-      scss: {
-				additionalData: `@use "@/styles/element.scss" as *;`,
+export default ({ mode }: any) => {
+	const env = loadEnv(mode, process.cwd())
+
+  return defineConfig({
+    resolve: {
+      alias: {
+        "@/": `${path.resolve(__dirname, "src")}/`,
+      },
+    },
+    css: {
+      preprocessorOptions: {
+        scss: {
+          quietDeps: true,
+          additionalData: `
+						@forward 'element-plus/theme-chalk/src/common/var' with (
+							$colors: (
+								'primary': (
+									'base': ${env.VITE_PRIMARY},
+								),
+							),
+						);
+					`,
+        },
       },
     },
-  },
-	server: {
-		port: 9000,
-		open: true,
-		host: '0.0.0.0',
-	},
-	plugins: [
-		vue(),
-		// AutoImport({
-		// 	resolvers: [ElementPlusResolver({importStyle: 'sass'})],
-		// }),
-		Components({
-			resolvers: [ElementPlusResolver({importStyle: 'sass'})],
-		})
-	],
-})
+    server: {
+      port: 9000,
+      open: true,
+      host: "0.0.0.0",
+    },
+    plugins: [
+      createHtmlPlugin({  
+        template: 'index.html',
+        entry: env.VITE_ENTRY,
+        inject: {
+          data: {
+            title: env.VITE_TITLE,
+          },
+        }
+      }),  
+      vue()
+    ],
+    define: {
+      __VERSION__: JSON.stringify(version)
+    }
+  });
+};