Browse Source

Merge branch 'master' of http://192.168.0.115:3000/bill/fuse-code

xzw 3 years ago
parent
commit
775587577d

+ 11 - 2
src/api/model.ts

@@ -6,8 +6,8 @@ import {
 export enum ModelType {
   SWKK,
   SWKJ,
-  SWSS,
   SWMX,
+  SWSS = 'laser',
 }
 
 export const ModelTypeDesc: Record<ModelType, string>  = {
@@ -17,8 +17,17 @@ export const ModelTypeDesc: Record<ModelType, string>  = {
   [ModelType.SWMX]: '三维模型',
 }
 
-export interface Model {
+export interface ModelAttrs {
+  show: boolean,
+  scale: number,
+  opacity: number,
+  bottom: number,
+  position: { x: number, y: number, z: number },
+  rotation: { x: number, y: number, z: number }
+}
+export interface Model extends ModelAttrs {
   id: string
+  url: string
   title: string
   type: ModelType
   size: number,

+ 68 - 0
src/components/actions/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="actions">
+    <span 
+      v-for="(action, i) in items" 
+      :class="{active: equal(selected, action)}"
+      :key="action.key || i" 
+      @click="clickHandler(action)"
+    >
+      <ui-icon :type="action.icon" />
+      {{ action.text }}
+    </span>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, toRaw, watchEffect } from 'vue'
+
+export type ActionsItem = { 
+  icon: string, 
+  key?: string, 
+  text: string,
+  action: () => (() => void) | void
+}
+export type ActionsProps = { items: ActionsItem[] }
+
+defineProps<ActionsProps>()
+
+const equal = (a: ActionsItem | null, b: ActionsItem | null) => toRaw(a) === toRaw(b)
+const selected = ref<ActionsItem | null>(null)
+const clickHandler = (select: ActionsItem) => {
+  selected.value = equal(selected.value, select) ? null : select
+}
+
+watchEffect(() => {
+  if (selected.value) {
+    return selected.value.action()
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.actions {
+  display: flex;
+  gap: 10px;
+  
+  span {
+    flex: 1;
+    height: 34px;
+    background: rgba(255,255,255,0.1);
+    border-radius: 4px 4px 4px 4px;
+    opacity: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: rgba(255,255,255,0.6);
+    font-size: 14px;
+    cursor: pointer;
+    transition: all .3s ease;
+
+
+    &:hover,
+    &.active {
+      background: rgba(0,200,175,0.16);
+      color: #00C8AF;
+    }
+  }
+}
+</style>

+ 0 - 1
src/components/bill-ui/components/input/range.vue

@@ -74,7 +74,6 @@ const rangeClickHandler = ev => {
 
 const parent = document.documentElement
 const slideDownHandler = ev => {
-    console.log(ev)
     ev.preventDefault()
     const moveStartX = ev.clientX || ev.touches[0].clientX
     const startPercen = percen.value

+ 24 - 12
src/components/list/index.vue

@@ -7,22 +7,27 @@
       </div>
     </li>
     <ul class="content">
-      <li v-for="(item, i) in data" :key="key ? item[key] : i">
-        <slot name="atom" :item="item"></slot>
+      <li 
+        v-for="(item, i) in data" 
+        :key="key ? item[key] : i" 
+        :class="{select: item.select}"
+        @click="$emit('changeSelect', item)"
+      >
+        <div class="atom-content">
+          <slot name="atom" :item="item"></slot>
+        </div>
       </li>
     </ul>
   </ul>
 </template>
 
 <script lang="ts" setup>
-
-type ListProps = {
-  title: string
-  key?: string
-  data: Array<any>
-}
+type Item = Record<string, any> & {select?: boolean}
+type ListProps = { title: string, key?: string, data: Array<Item>}
 
 defineProps<ListProps>()
+
+defineEmits<{ (e: 'changeSelect', item: Item): void }>()
 </script>
 
 <style lang="scss" scoped>
@@ -40,11 +45,18 @@ defineProps<ListProps>()
 }
 
 .content {
-  padding: 0 20px;
-
   li {
-    padding: 20px 0;
-    border-bottom: 1px solid rgba(255,255,255,0.16);
+    padding: 0 20px;
+    cursor: pointer;
+
+    &.select {
+      background: rgba(0,200,175,0.1600);
+    }
+
+    .atom-content {
+      padding: 20px 0;
+      border-bottom: 1px solid rgba(255,255,255,0.16);
+    }
   }
 }
 

+ 2 - 2
src/layout/main.vue

@@ -34,10 +34,10 @@ const layoutClassNames = computed(() => {
   }
 })
 
-const sceneRef = ref<HTMLCanvasElement>()
+const sceneRef = ref<HTMLDivElement>()
 const stopSdkInstalWatch = watchEffect(() => {
   if (loaded.value && sceneRef.value) {
-    initialSDK({ canvas: sceneRef.value })
+    initialSDK({ layout: sceneRef.value })
     stopSdkInstalWatch()
   }
 })

+ 50 - 0
src/sdk/association.ts

@@ -0,0 +1,50 @@
+import { models } from '@/store'
+import { toRaw, createVNode, watchEffect } from 'vue'
+import { 
+  mount, 
+  diffArrayChange, 
+  shallowWatchArray, 
+  arrayChildEffectScope 
+} from '@/utils'
+
+import type { SDK, SceneModel } from '.'
+import type { Model } from '@/api'
+
+const sceneModelMap = new WeakMap<Model, SceneModel>()
+export const getSceneModel = (model: Model) => sceneModelMap.get(toRaw(model))
+
+const associationModels = (sdk: SDK) => {
+  const getModels = () => models.value
+  shallowWatchArray(getModels, (models, oldModels) => {
+    const { added, deleted } = diffArrayChange(models, oldModels)
+    for (const item of added) {
+      if (getSceneModel(item)) {
+        continue;
+      }
+
+      const itemRaw = toRaw(item)
+      // const sceneModel = sdk.addModel(itemRaw)
+      // sceneModelMap.set(itemRaw, sceneModel)
+
+      // sceneModel.on('position', pos => item.position = pos)
+      // sceneModel.on('rotation', rot => item.rotation = rot)
+    }
+    for (const item of deleted) {
+      getSceneModel(item)?.destroy()
+    }
+  })
+  
+  arrayChildEffectScope(getModels, item => {
+    watchEffect(() => getSceneModel(item)?.changeBottom(item.bottom))
+    watchEffect(() => getSceneModel(item)?.changeOpacity(item.opacity))
+    watchEffect(() => getSceneModel(item)?.changeScale(item.scale))
+    watchEffect(() => getSceneModel(item)?.changeShow(item.show))
+  })
+}
+
+
+
+export const setup = (sdk: SDK, mountEl: HTMLDivElement) => {
+  associationModels(sdk)
+  mount(mountEl, () => createVNode('p', null, '123123'))
+}

+ 47 - 21
src/sdk/index.ts

@@ -1,33 +1,58 @@
 import cover from './cover'
-import { setup } from './view'
+import { setup } from './association'
 import { loadLib } from '@/utils'
 
-import type { ModelType } from '@/api'
+import type { ModelAttrs, Model } from '@/api'
 import type { Emitter } from 'mitt'
 
-type ToChangeAPI<T extends Record<string, any>> = {
-  [key in keyof T as `change${Capitalize<key & string>}`]: T[key]
-}
 
+type SceneModelAttrs = ModelAttrs & { select: boolean }
+export type SceneModel = Emitter<Pick<SceneModelAttrs, 'position' | 'rotation' | 'select'>> 
+  & ToChangeAPI<Omit<SceneModelAttrs, 'position' | 'rotation'>>
+  & { 
+    destroy: () => void 
+    enterRotateMode: () => void
+    leaveRotateMode: () => void
+    enterMoveMode: () => void
+    leaveMoveMode: () => void
+  }
+
+
+export type AddModelProps = Pick<Model, 'type' | 'url'> & ModelAttrs
 export interface SDK {
-  addModel: (props: AddModelProps) => Model
+  layout: HTMLDivElement,
+  addModel: (props: AddModelProps) => SceneModel
 }
+  
 
-export type ModelEventMap = { 
-  select: boolean,
-  scale: number,
-  opacity: number,
-  bottom: number
-}
-export type AddModelProps = {
-  url: string,
-  type: ModelType
+const presetViewElement = (layout: HTMLDivElement) => {
+  const style = getComputedStyle(layout)
+  const allows = ['relative', 'absolute', 'fixed']
+
+  if (!allows.includes(style.position)) {
+    layout.style.position = 'relative'
+  }
+  const el = document.createElement('div')
+  el.style.cssText = `
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    pointer-events: none;
+    z-index:101
+  `
+  layout.appendChild(el)
+  return el
 }
-export type Model = Emitter<ModelEventMap> & ToChangeAPI<ModelEventMap>
+
 
 export let sdk: SDK
-export type InialSDKProps = { canvas: HTMLCanvasElement }
-export const initialSDK = async (el: InialSDKProps) => {
+export type InialSDKProps = { layout: HTMLDivElement }
+let initialed = false
+export const initialSDK = async (props: InialSDKProps) => {
+  if (initialed) return;
+  initialed = true
   const libs = [
     `/lib/proj4/proj4.js`,
     `/lib/jquery/jquery-3.1.1.min.js`,
@@ -36,10 +61,11 @@ export const initialSDK = async (el: InialSDKProps) => {
   ]
   await Promise.all(libs.map(loadLib))
   await loadLib(`/lib/potree/potree.js`)
-  sdk = cover(el) as SDK
-  setup(sdk)
+
+  sdk = cover(props.layout) as SDK
+  setup(sdk, presetViewElement(props.layout))
 }
 
 
-export * from './view'
+export * from './association'
 export default sdk

+ 0 - 13
src/sdk/view.ts

@@ -1,13 +0,0 @@
-import { models } from '@/store'
-import { toRaw } from 'vue'
-
-import type { SDK } from './'
-
-
-const associationModels = (sdk: SDK) => {
-  console.log(sdk, toRaw(models.value))
-}
-
-export const setup = (sdk: SDK) => {
-  associationModels(sdk)
-}

+ 8 - 3
src/store/model.ts

@@ -5,7 +5,6 @@ import sdk from '@/sdk'
 import type { Models } from '@/api'
 
 export const models = ref<Models>([])
-export const showModels = ref<Models>([])
 
 
 export const initialModels = async () => {
@@ -13,13 +12,19 @@ export const initialModels = async () => {
   models.value = [
     {
       id: '123',
+      url: '',
       type: ModelType.SWKJ,
       title: '某安外',
       size: 1000,
-      time: '2012-02-05'
+      time: '2012-02-05',
+      scale: 1,
+      rotation: { x: 1, y: 1, z: 1},
+      position: { x: 1, y: 1, z: 1},
+      opacity: 0.1,
+      bottom: 1,
+      show: true
     }
   ]
-  console.log('????', sdk)
 }
 
 

+ 22 - 0
src/utils/diff.ts

@@ -0,0 +1,22 @@
+
+export const diffArrayChange = <T extends Array<any>>(newItems: T, oldItems: T) => {
+  const addedItems = [] as unknown as T
+  const deletedItems = [] as unknown as T
+
+  for (const item of newItems) {
+    if (!oldItems.includes(item)) {
+      addedItems.push(item)
+    }
+  }
+
+  for (const item of oldItems) {
+    if (!newItems.includes(item)) {
+      deletedItems.push(item)
+    }
+  }
+
+  return {
+    added: addedItems,
+    deleted: deletedItems
+  }
+}

+ 4 - 10
src/utils/index.ts

@@ -1,13 +1,3 @@
-import { watchEffect, nextTick } from "vue";
-
-export const onlyWatchEffect: typeof watchEffect = (cb, options) => {
-  const stopWatch = watchEffect((...args) => {
-    cb(...args);
-    nextTick(stopWatch);
-  }, options);
-  return stopWatch;
-};
-
 // 加载第三方库
 export const loadLib = (() => {
   const cache: Record<string, Promise<void>> = {};
@@ -41,7 +31,11 @@ export const loadLib = (() => {
   };
 })();
 
+
 export * from "./stack";
 export * from "./loading";
 export * from "./route";
 export * from "./asyncBus";
+export * from './mount'
+export * from './watch'
+export * from './diff'

+ 13 - 0
src/utils/mount.ts

@@ -0,0 +1,13 @@
+import { createVNode, render, Teleport, createBlock, openBlock } from 'vue'
+
+import type { Component } from 'vue'
+
+export const mount = (to: HTMLDivElement, Component: Component, props?: Record<string, any>) => {
+  const appEl = document.createElement('div')
+  const vnode = createVNode(Component, props)
+  openBlock()
+  const portBlock =createBlock(Teleport as any, { to }, [ vnode ])
+  render(portBlock, appEl)
+
+  return () => render(null, appEl)
+}

+ 46 - 0
src/utils/watch.ts

@@ -0,0 +1,46 @@
+import { watchEffect, nextTick, effectScope } from "vue";
+import { diffArrayChange } from './diff'
+
+import type { EffectScope } from 'vue'
+
+// 一次监听
+export const onlyWatchEffect: typeof watchEffect = (cb, options) => {
+  const stopWatch = watchEffect((...args) => {
+    cb(...args);
+    nextTick(stopWatch);
+  }, options);
+  return stopWatch;
+};
+
+export const shallowWatchArray = <T extends any[]>(
+  getItems: () => T, 
+  cb: (newItems: T, oldItems: T) => void
+) => {
+  let oldItems = [] as unknown as T
+  watchEffect(() => {
+    const newItems = getItems()
+    cb(newItems, oldItems)
+    oldItems = [...newItems] as T
+  })
+}
+
+export const arrayChildEffectScope = <T extends any[]>(
+  getItems: () => T, 
+  cb: (item: T[number]) => void
+) => {
+  const scopes = new WeakMap<T, EffectScope>()
+  shallowWatchArray(getItems, (newItems, oldItems) => {
+    console.log('shallowWatchArray', newItems, oldItems)
+    const { added, deleted } = diffArrayChange(newItems, oldItems)
+    for (const addItem of added) {
+      const scope = effectScope()
+      scope.run(() => {
+        cb(addItem)
+      })
+      scopes.set(addItem, scope)
+    }
+    for (const delItem of deleted) {
+      scopes.get(delItem)?.stop()
+    }
+  })
+}

+ 30 - 17
src/views/merge/edit.vue

@@ -1,28 +1,19 @@
 <template>
   <ui-group>
     <template #header>
-      <div class="edit-header">
-        <ui-button width="48%">
-          <ui-icon type="move" style="margin-right: 6px" />
-          <span>移动</span>
-        </ui-button>
-        <ui-button width="48%">
-          <ui-icon type="flip" style="margin-right: 6px" />
-          <span>旋转</span>
-        </ui-button>
-      </div>
+        <Actions class="edit-header" :items="actionItems" />
     </template>
     <ui-group-option label="等比缩放">
       <template #icon>
         <a href="">设置比例</a>
       </template>
-      <ui-input type="range" v-model="scale" v-bind="scaleOption" width="100%" />
+      <ui-input type="range" v-model="model.scale" v-bind="scaleOption" width="100%" />
     </ui-group-option>
     <ui-group-option label="离地高度">
-      <ui-input type="range" v-model="bottom" v-bind="bottomOption" width="100%"/>
+      <ui-input type="range" v-model="model.bottom" v-bind="bottomOption" width="100%"/>
     </ui-group-option>
     <ui-group-option label="模型不透明度">
-      <ui-input type="range" v-model="opacity" v-bind="opacityOption" width="100%" />
+      <ui-input type="range" v-model="model.opacity" v-bind="opacityOption" width="100%" />
     </ui-group-option>
     <ui-group-option>
       <ui-button>配准</ui-button>
@@ -34,15 +25,37 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { getSceneModel } from '@/sdk'
+import Actions from '@/components/actions/index.vue'
+
+import type { Model } from '@/store'
+import type { ActionsProps } from '@/components/actions/index.vue'
+
+const props = defineProps<{model: Model}>()
+const sceneModel = getSceneModel(props.model)
 
 const opacityOption = { min: 0.01, max: 1, step: 0.01, }
 const bottomOption = { min: 1, max: 100, step: 1, }
 const scaleOption = { min: 0.01, max: 1, step: 0.01, }
+const actionItems: ActionsProps['items'] = [
+  { 
+    icon: 'move', 
+    text: '移动', 
+    action: () => {
+      sceneModel?.enterMoveMode()
+      return () => sceneModel?.leaveMoveMode()
+    } 
+  },
+  { 
+    icon: 'flip', 
+    text: '旋转', 
+    action: () => {
+      sceneModel?.enterRotateMode()
+      return () => sceneModel?.leaveRotateMode()
+    } 
+  },
+]
 
-const scale = ref(0.01)
-const bottom = ref(0.01)
-const opacity = ref(0.01)
 
 </script>
 

+ 23 - 18
src/views/merge/index.vue

@@ -1,47 +1,52 @@
 <template>
   <LeftPano>
-    <List title="数据列表" key="id" :data="modelList">
+    <List 
+      title="数据列表" 
+      key="id" 
+      :data="modelList" 
+      @change-select="item => modelChangeSelect(item.raw)"
+    >
       <template #action>
         <ui-icon type="add" ctrl/>
       </template>
       <template #atom="{ item }">
-        <ModelSign
-          :model="item.raw" 
-          :show="item.show" 
-          @change-show="show => modelChangeShow(item.raw, show)" 
-          @delete="modelDelete(item.raw)"
-        />
+        <ModelSign :model="item.raw" @delete="modelDelete(item.raw)" />
       </template>
     </List>
   </LeftPano>
-  <RightPano>
-    <ModelEdit />
+  <RightPano v-if="selectModel">
+    <ModelEdit :model="selectModel" />
   </RightPano>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue'
+import { computed, ref, toRaw } from 'vue'
 import { LeftPano, RightPano } from '@/layout'
-import { models, showModels } from '@/store'
+import { models } from '@/store'
+import { getSceneModel } from '@/sdk'
 import List from '@/components/list/index.vue'
 import ModelSign from './sign.vue'
 import ModelEdit from './edit.vue'
 
 import type { Model } from '@/store'
 
+const selectModel = ref<Model | null>(null)
 const modelList = computed(() => 
   models.value.map(model => ({
     raw: model,
-    show: showModels.value.includes(model)
+    select: selectModel.value === model
   }))
 )
 
-const modelChangeShow = (model: Model, show: boolean) => {
-  const index = showModels.value.indexOf(model)
-  if (~index && !show) {
-    showModels.value.splice(index, 1)
-  } else if (!~index && show) {
-    showModels.value.push(model)
+const modelChangeSelect = (model: Model) => {
+  if (selectModel.value) {
+    getSceneModel(selectModel.value)?.changeSelect(false)
+  }
+  if (toRaw(selectModel.value) !== toRaw(model)) {
+    getSceneModel(model)?.changeSelect(true)
+    selectModel.value = model
+  } else {
+    selectModel.value = null
   }
 }
 

+ 6 - 9
src/views/merge/sign.vue

@@ -1,12 +1,8 @@
 <template>
   <div class="model-header">
     <p>{{ model.title }}</p>
-    <div class="model-action">
-      <ui-input 
-        type="checkbox" 
-        :modelValue="show" 
-        @update:modelValue="(show: boolean) => $emit('changeShow', show)" 
-      />
+    <div class="model-action" @click.stop>
+      <ui-input type="checkbox" v-model="model.show"/>
       <ui-icon type="del" ctrl @click="$emit('delete')" />
     </div>
   </div>
@@ -20,14 +16,15 @@
 <script lang="ts" setup>
 import type { Model } from '@/store'
 
-type ModelProps = { model: Model, show: boolean }
+type ModelProps = { model: Model }
 defineProps<ModelProps>()
 
 type ModelEmits = {
-  (e: 'changeShow', show: boolean): void
+  (e: 'changeSelect', selected: boolean): void
   (e: 'delete'): void
 }
-defineEmits<ModelEmits>()
+defineEmits<ModelEmits>();
+
 </script>
 
 <style lang="scss" scoped src="./style.scss"></style>

+ 0 - 5
src/views/merge/style.scss

@@ -23,8 +23,3 @@
     margin-left: 20px;
   }
 }
-
-.edit-header {
-  display: flex;
-  justify-content: space-between;
-}

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

@@ -5,3 +5,7 @@ declare module '*.vue' {
   const component: DefineComponent<{}, {}, any>
   export default component
 }
+
+type ToChangeAPI<T extends Record<string, any>> = {
+  [key in keyof T as `change${Capitalize<key & string>}`]: (prop: T[key]) => void
+}