caner 1 year ago
parent
commit
48e68d57f9

+ 2 - 1
.eslintrc.json

@@ -87,6 +87,7 @@
     "no-fallthrough": 0,
     "no-case-declarations": 0,
     "no-cond-assign": 0,
-    "prefer-destructuring": 1
+    "prefer-destructuring": 1,
+    "no-await-in-loop": 0
   }
 }

+ 3 - 3
package.json

@@ -15,6 +15,8 @@
     "echarts": "^5.5.1",
     "html2canvas": "^1.4.1",
     "jspdf": "^2.5.1",
+    "leaflet": "^1.9.4",
+    "moment": "^2.30.1",
     "naive-ui": "^2.38.2",
     "openseadragon": "^4.1.1",
     "pinia": "^2.1.7",
@@ -23,9 +25,7 @@
     "socket.io-client": "^4.7.5",
     "vue": "^3.4.31",
     "vue-router": "^4.4.0",
-    "xlsx": "^0.18.5",
-    "leaflet": "^1.9.4",
-    "uuid": "^10.0.0"
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@types/leaflet": "^1.9.12",

+ 14 - 2
src/assets/native-plugin.ts

@@ -6,7 +6,13 @@ import {
   NInputGroup,
   NInputGroupLabel,
   NInputNumber,
-  NSelect
+  NSelect,
+  NTreeSelect,
+  NScrollbar,
+  NInput,
+  NSwitch,
+  NFormItem,
+  NForm
 } from 'naive-ui'
 
 const naive = create({
@@ -17,7 +23,13 @@ const naive = create({
     NInputGroup,
     NInputGroupLabel,
     NInputNumber,
-    NSelect
+    NSelect,
+    NTreeSelect,
+    NScrollbar,
+    NInput,
+    NSwitch,
+    NFormItem,
+    NForm
   ]
 })
 

+ 4 - 2
src/pages/App.vue

@@ -19,7 +19,7 @@ import useStore from './store/index'
 import { computed } from 'vue'
 import { zhCN, dateZhCN } from 'naive-ui'
 import Theme from '@/assets/naive-theme'
-import GlobalNotif from '@/component/notifaiction.vue'
+import GlobalNotif from '@/components/notifaiction.vue'
 
 const store = useStore()
 const show = computed(() => store.loading)
@@ -31,7 +31,9 @@ console.log(
 3. '/edit' 富文本编辑
 4. '/fly' 二维动态导向
 5. '/plantool' 平面图缩放
-6. '/draw' canvas画图`
+6. '/draw' canvas画图
+7. '/face' canvas画图
+8. '/leaflet' leaflet画图`
 )
 </script>
 <style>

+ 2 - 2
src/pages/store/index.ts

@@ -31,8 +31,8 @@ const useStore = defineStore('index', {
     token: '',
     isCheckPermission: false,
     loading: false,
-    obtainedDevices:[] as ObtainedDevices[], //当前系统绑定的设备
-    currentStratum: {} as CurrentStratumType, // 当前层级
+    obtainedDevices: [] as ObtainedDevices[], // 当前系统绑定的设备
+    currentStratum: {} as CurrentStratumType // 当前层级
   }),
   actions: {
     setUserInfo(data: UserInfo) {

+ 10 - 16
src/pages/views/face/index.vue

@@ -185,10 +185,10 @@
       </defs>
       <!-- 层级倒序处理 -->
       <!-- 已开挖 -->
-      <!-- <use
-        x="12"
+      <use
+        x="0"
         xlink:href="#y-0"
-        @click="clickFace(3)"
+        @click="clickFace(1)"
       />
       <use
         x="6"
@@ -196,27 +196,21 @@
         @click="clickFace(2)"
       />
       <use
-        x="0"
+        x="12"
         xlink:href="#y-0"
-        @click="clickFace(1)"
-      /> -->
-
+        @click="clickFace(3)"
+      />
       <!-- 未开挖 -->
       <use
-        x="12"
-        fill="url(#w-0)"
-        xlink:href="#j"
-      />
-      <!-- <use
         x="18"
-        fill="url(#w-1)"
+        fill="url(#w-0)"
         xlink:href="#w"
       />
       <use
         x="24"
         fill="url(#w-1)"
         xlink:href="#w"
-      /> -->
+      />
 
     </svg>
   </div>
@@ -224,11 +218,11 @@
 
 <script setup lang='ts'>
 
-function clickFace(num:number) {
+function clickFace(num: number) {
   console.log('掌子面点击', num)
 }
 </script>
-<style lang="less" scoped>
+<style lang="scss" scoped>
 .svgBox {
   width: 100%;
   height: 100%;

+ 9 - 0
src/pages/views/face/route.ts

@@ -0,0 +1,9 @@
+import { RouteRecordRaw } from 'vue-router'
+import face from './index.vue'
+export default {
+  path: '/face',
+  meta: {
+    authorize: true
+  },
+  component: face
+} as RouteRecordRaw

+ 1 - 1
src/pages/views/home/index.vue

@@ -191,4 +191,4 @@ onUnmounted(() => {
   }
 
 }
-</style>./assets/js/Painter.js./assets/js/Recorder.js
+</style>

+ 684 - 0
src/pages/views/leaflet/index.vue

@@ -0,0 +1,684 @@
+<template>
+  <div class="mapBox">
+    <div
+      id="map"
+      @dragover="e => e.preventDefault()"
+      @dragleave="isCheck = false"
+      @dragenter="isCheck = true"
+    />
+    <div
+      v-if="isEdit"
+      class="mapBox-menu"
+    >
+      <n-tree-select
+        v-model:value="selected"
+        :options="options"
+        label-field="name"
+        key-field="uuid"
+        children-field="childList"
+        check-strategy="child"
+        placeholder="请选择车站"
+        @update:value="changeMap"
+      />
+      <div class="mapBox-menu-content">
+        <n-scrollbar>
+          <template
+            v-for="(item, index) in iconList"
+            :key="index"
+          >
+            <div
+              class="mapBox-menu-content-item"
+              draggable="true"
+              @dragstart="currentItem = item"
+              @dragend="itemDragEnd"
+            >
+              <img
+                :src="item.icon"
+                alt=""
+                srcset=""
+                draggable="false"
+              >
+              <p>
+                {{ item.name }}
+              </p>
+            </div>
+          </template>
+        </n-scrollbar>
+      </div>
+    </div>
+    <div
+      v-if="isEdit && showPoup"
+      class="mapBox-poup"
+    >
+      <n-form
+        ref="formRef"
+        :model="formMode"
+        label-placement="left"
+        label-width="auto"
+        size="small"
+        :rules="rules"
+      >
+        <n-form-item
+          label="元素编号"
+          path="uuid"
+        >
+          <n-input
+            v-model:value="formMode.uuid"
+            disabled
+          />
+        </n-form-item>
+        <n-form-item
+          label="资产编号"
+          path="guid"
+        >
+          <n-select
+            v-model:value="formMode.guid"
+            placeholder="请选择"
+            label-field="name"
+            value-field="guid"
+            :options="assetOption"
+            @update:value="selectChange"
+          />
+        </n-form-item>
+        <n-form-item
+          label="元素类型"
+          path="type"
+        >
+          <n-input
+            v-model:value="formMode.type"
+            disabled
+          />
+        </n-form-item>
+        <n-form-item
+          label="x 坐 标 "
+          path="x"
+        >
+          <n-input-number
+            v-model:value="formMode.x"
+            clearable
+            :show-button="false"
+            @blur="resetMarker('lng', formMode.x)"
+          />
+        </n-form-item>
+        <n-form-item
+          label="y 坐 标 "
+          path="y"
+        >
+          <n-input-number
+            v-model:value="formMode.y"
+            clearable
+            :show-button="false"
+            @blur="resetMarker('lat', formMode.y)"
+          />
+        </n-form-item>
+        <n-form-item
+          label="横向缩放"
+          path="scaleX"
+        >
+          <n-input-number
+            v-model:value="formMode.scaleX"
+            clearable
+            :min="0.1"
+            :show-button="false"
+            @blur="resetMarker('scaleX', formMode.scaleX, '0')"
+          />
+        </n-form-item>
+        <n-form-item
+          label="纵向缩放"
+          path="scaleY"
+        >
+          <n-input-number
+            v-model:value="formMode.scaleY"
+            clearable
+            :min="0.1"
+            :show-button="false"
+            @blur="resetMarker('scaleY', formMode.scaleY, '1')"
+          />
+        </n-form-item>
+        <n-form-item
+          label="水平翻转"
+          path="flipX"
+        >
+          <n-switch
+            v-model:value="formMode.flipX"
+            @update:value="resetMarker('flipX', formMode.flipX)"
+          />
+        </n-form-item>
+        <n-form-item
+          label="垂直翻转"
+          path="flipY"
+        >
+          <n-switch
+            v-model:value="formMode.flipY"
+            @update:value="resetMarker('flipY', formMode.flipY)"
+          />
+        </n-form-item>
+      </n-form>
+      <div class="mapBox-poup-btns">
+        <n-button
+          type="error"
+          ghost
+          @click="del"
+        >
+          删除
+        </n-button>
+        <n-button
+          type="primary"
+          @click="save"
+        >
+          保存
+        </n-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import * as L from 'leaflet'
+import {
+  onMounted,
+  onUnmounted,
+  Ref, ref, watch
+} from 'vue'
+import '@/../node_modules/leaflet/dist/leaflet.css'
+import useStore from '@/pages/store'
+// ----- 修复L 增加tooltip 缩放报错 重要!!
+
+(L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
+  if (!(this as any)._map) {
+    return
+  }
+  const pos = (this as any)._map._latLngToNewLayerPoint((this as any)._latlng, e.zoom, e.center) as any
+  (this as any)._setPosition(pos)
+};
+
+(L.Tooltip as any).prototype._updatePosition = function () {
+  if (!(this as any)._map) {
+    return
+  }
+  const pos = (this as any)._map.latLngToLayerPoint((this as any)._latlng) as any
+  (this as any)._setPosition(pos)
+}
+// ---------
+
+export interface Item {
+  icon: string,
+  x?: number,
+  y?: number,
+  scaleX?: number,
+  scaleY?: number,
+  guid?: string,
+  type?: string,
+  uuid?: string,
+  item?: string,
+  isAdd?: boolean,
+  deviceId?: string,
+  itemName?: string
+}
+
+export interface BaseMap {
+  DID?: number,
+  width: number,
+  height: number,
+  mapId: string,
+  name?: string
+  imgUrl: string,
+  trackType?: number, // 1电扶梯 2给排水 3通风 4照明 5车站环境检测 6车站暖通 7车站动环
+  showTip?: boolean
+}
+
+const props = withDefaults(defineProps<{
+  isEdit?: boolean, // 是否编辑
+  baseMap?: BaseMap
+}>(), {
+  isEdit: false,
+  baseMap: () => ({
+    imgUrl: '', width: 0, height: 0, mapId: '', showTip: false
+  })
+})
+const emit = defineEmits<{(evt: 'markerClick', value: Item): void }>()
+const Map = ref()
+const iconList = ref([] as { icon: string, name: string }[])
+const currentItem: Ref<Item | undefined> = ref()
+const isCheck = ref(false)
+const options = ref([] as BaseMap[])
+const selected = ref('')
+const assetOption = ref([])
+const formRef = ref()
+const formMode = ref({
+  uuid: '',
+  guid: '',
+  type: '',
+  x: 0,
+  y: 0,
+  scaleX: 1,
+  scaleY: 1,
+  flipX: false,
+  flipY: false,
+  icon: '',
+  tip: '',
+  isAdd: false
+})
+const rules = { guid: { required: true, message: '请选择资产' } }
+const showPoup = ref(false)
+const currentMap: Ref<BaseMap | undefined> = ref()
+const currentMarker = ref()
+const allMarker = ref([] as L.Marker[])
+const store = useStore()
+
+// 资产下拉
+function selectChange(_: string, option: Item) {
+  formMode.value.type = option.type || ''
+}
+
+// 重置marker样式
+function resetMarker(type: string, num: number | boolean, id?: string) {
+  if (type === 'lat' || type === 'lng') {
+    const obj = currentMarker.value.getLatLng()
+    obj[type] = num
+    currentMarker.value.setLatLng(obj)
+    console.log('位置更新', obj)
+  } else {
+    const newIcon = currentMarker.value.getIcon()
+    if (type === 'scaleX' || type === 'scaleY') {
+      newIcon.options.iconSize[id!] = newIcon.options.iconSize[id!] * (num as number)
+    } else {
+      const newHtml = newIcon.options.html.replace(/style="[^=>]*"/g, (s: string) => {
+        const a = s.split(' ')
+        const c = type === 'flipY' ? 'rotateX' : 'rotateY'
+        const n = type === 'flipY' ? 1 : 2
+        a[n] = num ? `${c}(180deg)${n === 2 ? ';"' : ''}` : `${c}(0deg)${n === 2 ? ';"' : ''}`
+        const b = a.join(' ')
+        return b
+      })
+      newIcon.options.html = newHtml
+    }
+    currentMarker.value.setIcon(newIcon)
+  }
+}
+
+// marker选中状态
+function selectMaker(marker: L.Marker) {
+  for (let k = 0; k < allMarker.value.length; k++) {
+    const el = allMarker.value[k]
+    const dom = el.getElement()
+    if ((el as Any)._leaflet_id === (marker as Any)._leaflet_id) {
+      dom!.style.boxShadow = '0 0 4px 2px #ff891a'
+    } else {
+      dom!.style.boxShadow = 'none'
+    }
+  }
+  currentMarker.value = marker
+}
+
+// 查询设备列表+设置poup
+function setPoupData(marker: L.Marker, option: Item) {
+  // 查询设备列表
+  // socketService.send('item.query', { itemtype: option.deviceId, isPaging: 0, region: selected.value }).then((res) => {
+  //   const { success, data: { list } } = res
+  //   if (success && list) {
+  //     assetOption.value = list
+  //   }
+  // })
+  formMode.value.uuid = option.uuid || ''
+  formMode.value.guid = option.guid || option.item || ''
+  formMode.value.type = option.type || ''
+  formMode.value.x = option.x || 0
+  formMode.value.y = option.y || 0
+  formMode.value.isAdd = option.isAdd || false
+  // 移除其它选中状态
+  selectMaker(marker)
+  showPoup.value = true
+}
+
+// 添加marker
+function addMarker(option: Item) {
+  if (!Map.value) throw 'Map 未初始化'
+  if (!option.icon) return console.log('缺少参数')
+
+  // 自定义图标
+  const img = `<img src="${option.icon}" style="width:100%;height:100%;transform: rotateX(0deg) rotateY(0deg);"/>`
+  const ICON = L.divIcon({
+    iconSize: [ (option.scaleX || 1) * 30, (option.scaleY || 1) * 30 ],
+    html: img
+  })
+  const marker = L.marker([ option.y || 0, option.x || 0 ], {
+    icon: ICON,
+    draggable: props.isEdit, // 是否可通过鼠标/触摸拖动。
+    riseOnHover: true
+  }).addTo(Map.value)
+  // add
+  allMarker.value.push(marker)
+  // 显示tip
+  if (props.baseMap.showTip && option.itemName) marker.bindTooltip(option.itemName, { direction: 'top', className: 'resetTips' })
+  // 显示poup
+  if (isCheck.value) {
+    console.log('添加marker', option)
+    setPoupData(marker, option)
+  }
+  // 事件
+  marker.addEventListener('click', () => {
+    console.log('marker点击', option)
+    setPoupData(marker, option)
+    emit('markerClick', option)
+  })
+  marker.addEventListener('dragend', (e) => {
+    console.log('marker拖动', option)
+    const { lat, lng } = e.target.getLatLng()
+    option.x = lng
+    option.y = lat
+    setPoupData(marker, option)
+  })
+}
+
+// 拖拽=>添加marker
+function itemDragEnd(e: DragEvent) {
+  e.preventDefault()
+  const point = Map.value.containerPointToLayerPoint([ e.layerX, e.layerY ]) // 给定相对于origin pixel的相应像素坐标
+  const lnglat = Map.value.layerPointToLatLng(point) // 给定地理坐标,转换为相对于origin pixel的相应像素坐标
+  if (isCheck.value) {
+    addMarker({
+      ...currentItem.value, x: lnglat.lng, y: lnglat.lat, isAdd: true
+    } as Item)
+  }
+  isCheck.value = false
+}
+
+// 查询marker+绑定
+async function bindMarker(mapId: string) {
+  if (!mapId) return
+  // 清除
+  if (allMarker.value.length) {
+    for (let k = 0; k < allMarker.value.length; k++) {
+      const marker = allMarker.value[k]
+      marker.remove()
+    }
+    allMarker.value = []
+  }
+  // 添加所有
+  // let page = 1
+  // let pages = 0
+  // do {
+  //   const obj = {
+  //     map: mapId,
+  //     pageSize: 100,
+  //     isPaging: 0,
+  //     page: page++
+  //   } as { trackType?: number }
+  //   if (props.baseMap.trackType) obj.trackType = props.baseMap.trackType
+  //   const { data } = await socketService.send('monitor.itemQuery', obj)
+  //   store.setObtainedDevices(data.list)
+  //   console.log('底图关联的marker', data.list)
+  //   if (data.list) {
+  //     for (let k = 0; k < data.list.length; k++) {
+  //       const el = data.list[k]
+  //       const obj = {
+  //         parent: el,
+  //         ...el.mapElement,
+  //         icon: import.meta.env.VITE_IMG_URL + el.icon,
+  //         showTip: props.baseMap.showTip
+  //       }
+  //       addMarker(obj)
+  //     }
+  //   }
+  //   pages = data.pages
+  // } while (page <= pages)
+}
+
+// 切换底图
+async function changeMap(_: string, option: BaseMap) {
+  console.log('切换底图', option)
+  if (!Map.value) return
+  // 清除layer
+  Map.value.eachLayer((layer: L.Layer) => {
+    Map.value.removeLayer(layer)
+  })
+  // 添加底图
+  if (!option.imgUrl || !option.width || !option.height || !option.mapId) return window.$notification.error({ content: '底图缺少相关参数', duration: 3000, keepAliveOnHover: true })
+  currentMap.value = option
+  L.imageOverlay(import.meta.env.VITE_IMG_URL + option.imgUrl, [ [ 0, 0 ], [ option.height, option.width ] ]).addTo(Map.value)
+  Map.value.setView([ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ], 0)
+  // 获取底图绑定的maker
+  bindMarker(option.mapId)
+}
+
+// 初始化底图
+function initMap(option: BaseMap) {
+  if (!option) return
+  Map.value = L.map('map', {
+    zoom: 0,
+    maxZoom: 1,
+    minZoom: 0,
+    zoomSnap: 0.1,
+    center: [ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ],
+    crs: L.CRS.Simple,
+    attributionControl: false,
+    zoomControl: false
+  }).addEventListener('click', (e) => {
+    console.log('map点击', e)
+    if (currentMarker.value) currentMarker.value.getElement().style.boxShadow = 'none'
+    formMode.value.flipX = false
+    formMode.value.flipY = false
+    formMode.value.scaleX = 1
+    formMode.value.scaleY = 1
+    formMode.value.uuid = ''
+    showPoup.value = false
+  })
+
+  // 获取底图列表
+  // socketService.send('region.map').then((res) => {
+  //   const { success, data } = res
+  //   if (success && data.length) {
+  //     options.value = data.map((el: { childList: Any[]; }) => {
+  //       if (el.childList) {
+  //         el.childList.forEach((es) => {
+  //           if (es.mapId === props.baseMap.mapId) selected.value = es.id
+  //           try {
+  //             const { width, height, imgUrl } = JSON.parse(es.mapConfig)
+  //             es.width = width
+  //             es.height = height
+  //             es.imgUrl = imgUrl
+  //           } catch (error) {
+  //             console.log('底图配置数据格式错误')
+  //           }
+  //         })
+  //       }
+  //       return el
+  //     })
+  //   }
+  // })
+
+  // 获取所有maker类型
+  // socketService.send('itemType.search', { belongs: '3' }).then((res) => {
+  //   const { success, data } = res
+  //   if (success && data.length) iconList.value = data.map((el: { imageUrl: string; name: string; guid: string }) => ({ icon: import.meta.env.VITE_IMG_URL + el.imageUrl, name: el.name, deviceId: el.guid }))
+  // })
+
+  if (props.isEdit) { window.$notification.warning({ content: '请选择车站', duration: 3000, keepAliveOnHover: true }) } else {
+    changeMap('', option)
+  }
+}
+
+// 保存->更新marker
+async function save() {
+  await formRef.value.validate()
+  const mapElementList = [ {
+    flipX: formMode.value.flipX,
+    flipY: formMode.value.flipY,
+    item: formMode.value.guid,
+    map: currentMap.value!.mapId,
+    scaleX: formMode.value.scaleX,
+    scaleY: formMode.value.scaleY,
+    type: formMode.value.type,
+    uuid: formMode.value.uuid,
+    visiable: true,
+    x: formMode.value.x,
+    y: formMode.value.y
+  } ]
+  console.log('保存', mapElementList, formMode.value.isAdd)
+  // const url = formMode.value.isAdd ? 'mapElement.insert' : 'mapElement.update'
+  // const { success } = await socketService.send(url, { mapElementList })
+  // if (success) {
+  //   bindMarker(currentMap.value!.mapId)
+  //   window.$notification.success({ content: formMode.value.isAdd ? '添加成功' : '修改成功', duration: 2000 })
+  //   showPoup.value = false
+  // }
+}
+
+// 删除当前
+async function del() {
+  if (!currentMarker.value) return
+  const fdid = allMarker.value.findIndex((el: Any) => el._leaflet_id === currentMarker.value._leaflet_id)
+  if (fdid !== -1) {
+    if (!formMode.value.isAdd) {
+      // const { success } = await socketService.send('mapElement.delete', [ formMode.value.uuid ])
+      // if (success) window.$notification.success({ content: '删除成功', duration: 2000 })
+    }
+    allMarker.value.splice(fdid, 1)
+    currentMarker.value.remove()
+    showPoup.value = false
+  } else {
+    throw '删除失败!'
+  }
+}
+
+// 销毁
+function destory() {
+  if (!Map.value) return
+  console.log('销毁')
+  Map.value.eachLayer((layer: L.Layer) => {
+    Map.value.removeLayer(layer)
+  })
+  allMarker.value = []
+  currentMarker.value = undefined
+  currentMap.value = undefined
+  Map.value = null
+}
+
+onMounted(() => {
+  if (Object.keys(store.currentStratum).length) {
+    if (Map.value) {
+      changeMap('', store.currentStratum)
+    } else {
+      initMap(store.currentStratum)
+    }
+  }
+})
+
+watch(() => props.baseMap, (v) => {
+  if (Map.value) {
+    changeMap('', v)
+  } else {
+    initMap(v)
+  }
+}, { deep: true })
+
+onUnmounted(() => destory())
+</script>
+<style lang="scss" scoped>
+.mapBox {
+  width: 100vw;
+  height: 100vh;
+  background: none;
+  position: relative;
+
+  #map {
+    width: 100%;
+    height: 100%;
+    background: #011719;
+
+    :deep(.leaflet-div-icon) {
+      background: none;
+      border: none;
+      border-radius: 6px;
+    }
+
+    :deep(.resetTips) {
+      height: 33px;
+      background: #06353A;
+      border: 1px solid #34C0AE;
+      color: white;
+
+      &::before {
+        border-top-color: #34C0AE;
+      }
+
+      p {
+        margin: 0;
+        font-size: 14px;
+      }
+    }
+  }
+
+  &-menu {
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 601;
+    background: white;
+    width: 380px;
+    height: 100%;
+    padding: 10px;
+
+    &-content {
+      width: 100%;
+      height: calc(100% - 44px);
+
+      :deep(.n-scrollbar-content) {
+        display: flex;
+        flex-wrap: wrap;
+        align-content: flex-start;
+      }
+
+      &-item {
+        text-align: center;
+        width: 78px;
+        height: 78px;
+        padding: 10px 0;
+        border: 1px solid #C9D3DD;
+        cursor: grab;
+        margin-top: 16px;
+
+        &:not(:nth-child(4n)) {
+          margin-right: 16px;
+        }
+
+        img {
+          width: 50px;
+          height: 40px;
+        }
+
+        p {
+          margin: 0;
+          color: #2c3e50;
+          font-size: 12px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+    }
+
+  }
+
+  &-poup {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 334px;
+    background: #FFFFFF;
+    box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.1);
+    backdrop-filter: blur(29.99999943901511px);
+    z-index: 601;
+    padding: 20px;
+
+    &-btns {
+      text-align: center;
+
+      &>button:first-child {
+        margin-right: 20px;
+      }
+    }
+  }
+}
+</style>

+ 9 - 0
src/pages/views/leaflet/route.ts

@@ -0,0 +1,9 @@
+import { RouteRecordRaw } from 'vue-router'
+import leaflet from './index.vue'
+export default {
+  path: '/leaflet',
+  meta: {
+    authorize: true
+  },
+  component: leaflet
+} as RouteRecordRaw

+ 0 - 687
src/pages/views/leaft/index.vue

@@ -1,687 +0,0 @@
-<template>
-    <div class="mapBox">
-      <div
-        id="map"
-        @dragover="e => e.preventDefault()"
-        @dragleave="isCheck = false"
-        @dragenter="isCheck = true"
-      />
-      <div
-        v-if="isEdit"
-        class="mapBox-menu"
-      >
-        <n-tree-select
-          v-model:value="selected"
-          :options="options"
-          label-field="name"
-          key-field="uuid"
-          children-field="childList"
-          check-strategy="child"
-          placeholder="请选择车站"
-          @update:value="changeMap"
-        />
-        <div class="mapBox-menu-content">
-          <n-scrollbar>
-            <template
-              v-for="(item, index) in iconList"
-              :key="index"
-            >
-              <div
-                class="mapBox-menu-content-item"
-                draggable="true"
-                @dragstart="currentItem = item"
-                @dragend="itemDragEnd"
-              >
-                <img
-                  :src="item.icon"
-                  alt=""
-                  srcset=""
-                  draggable="false"
-                >
-                <p>
-                  {{ item.name }}
-                </p>
-              </div>
-            </template>
-          </n-scrollbar>
-        </div>
-      </div>
-      <div
-        v-if="isEdit && showPoup"
-        class="mapBox-poup"
-      >
-        <n-form
-          ref="formRef"
-          :model="formMode"
-          label-placement="left"
-          label-width="auto"
-          size="small"
-          :rules="rules"
-        >
-          <n-form-item
-            label="元素编号"
-            path="uuid"
-          >
-            <n-input
-              v-model:value="formMode.uuid"
-              disabled
-            />
-          </n-form-item>
-          <n-form-item
-            label="资产编号"
-            path="guid"
-          >
-            <n-select
-              v-model:value="formMode.guid"
-              placeholder="请选择"
-              label-field="name"
-              value-field="guid"
-              :options="assetOption"
-              @update:value="selectChange"
-            />
-          </n-form-item>
-          <n-form-item
-            label="元素类型"
-            path="type"
-          >
-            <n-input
-              v-model:value="formMode.type"
-              disabled
-            />
-          </n-form-item>
-          <n-form-item
-            label="x 坐 标 "
-            path="x"
-          >
-            <n-input-number
-              v-model:value="formMode.x"
-              clearable
-              :show-button="false"
-              @blur="resetMarker('lng', formMode.x)"
-            />
-          </n-form-item>
-          <n-form-item
-            label="y 坐 标 "
-            path="y"
-          >
-            <n-input-number
-              v-model:value="formMode.y"
-              clearable
-              :show-button="false"
-              @blur="resetMarker('lat', formMode.y)"
-            />
-          </n-form-item>
-          <n-form-item
-            label="横向缩放"
-            path="scaleX"
-          >
-            <n-input-number
-              v-model:value="formMode.scaleX"
-              clearable
-              :min="0.1"
-              :show-button="false"
-              @blur="resetMarker('scaleX', formMode.scaleX, '0')"
-            />
-          </n-form-item>
-          <n-form-item
-            label="纵向缩放"
-            path="scaleY"
-          >
-            <n-input-number
-              v-model:value="formMode.scaleY"
-              clearable
-              :min="0.1"
-              :show-button="false"
-              @blur="resetMarker('scaleY', formMode.scaleY, '1')"
-            />
-          </n-form-item>
-          <n-form-item
-            label="水平翻转"
-            path="flipX"
-          >
-            <n-switch
-              v-model:value="formMode.flipX"
-              @update:value="resetMarker('flipX', formMode.flipX)"
-            />
-          </n-form-item>
-          <n-form-item
-            label="垂直翻转"
-            path="flipY"
-          >
-            <n-switch
-              v-model:value="formMode.flipY"
-              @update:value="resetMarker('flipY', formMode.flipY)"
-            />
-          </n-form-item>
-        </n-form>
-        <div class="mapBox-poup-btns">
-          <n-button
-            type="error"
-            ghost
-            @click="del"
-          >
-            删除
-          </n-button>
-          <n-button
-            type="primary"
-            @click="save"
-          >
-            保存
-          </n-button>
-        </div>
-      </div>
-    </div>
-  </template>
-  
-  <script setup lang='ts'>
-  import * as L from 'leaflet'
-  import {
-    onMounted,
-    onUnmounted,
-    Ref, ref, watch
-  } from 'vue'
-  import '@/../node_modules/leaflet/dist/leaflet.css'
-  import SocketService from '@/services/socket.service'
-  import { v1 as uuid } from 'uuid'
-  import useStore from '@/pages/store'
-  // ----- 修复L 增加tooltip 缩放报错 重要!!
-  
-  (L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
-    if (!(this as any)._map) {
-      return
-    }
-    const pos = (this as any)._map._latLngToNewLayerPoint((this as any)._latlng, e.zoom, e.center) as any
-    (this as any)._setPosition(pos)
-  };
-  
-  (L.Tooltip as any).prototype._updatePosition = function () {
-    if (!(this as any)._map) {
-      return
-    }
-    const pos = (this as any)._map.latLngToLayerPoint((this as any)._latlng) as any
-    (this as any)._setPosition(pos)
-  }
-  // ---------
-  
-  export interface Item {
-    icon: string,
-    x?: number,
-    y?: number,
-    scaleX?: number,
-    scaleY?: number,
-    guid?: string,
-    type?: string,
-    uuid?: string,
-    item?: string,
-    isAdd?: boolean,
-    deviceId?: string,
-    itemName?: string
-  }
-  
-  export interface BaseMap {
-    DID?: number,
-    width: number,
-    height: number,
-    mapId: string,
-    name?: string
-    imgUrl: string,
-    trackType?: number, // 1电扶梯 2给排水 3通风 4照明 5车站环境检测 6车站暖通 7车站动环
-    showTip?: boolean
-  }
-  
-  const socketService = new SocketService()
-  const props = withDefaults(defineProps<{
-    isEdit?: boolean, // 是否编辑
-    baseMap?: BaseMap
-  }>(), {
-    isEdit: false,
-    baseMap: () => ({
-      imgUrl: '', width: 0, height: 0, mapId: '', showTip: false
-    })
-  })
-  const emit = defineEmits<{(evt: 'markerClick', value: Item): void }>()
-  const Map = ref()
-  const iconList = ref([] as { icon: string, name: string }[])
-  const currentItem: Ref<Item | undefined> = ref()
-  const isCheck = ref(false)
-  const options = ref([] as BaseMap[])
-  const selected = ref('')
-  const assetOption = ref([])
-  const formRef = ref()
-  const formMode = ref({
-    uuid: '',
-    guid: '',
-    type: '',
-    x: 0,
-    y: 0,
-    scaleX: 1,
-    scaleY: 1,
-    flipX: false,
-    flipY: false,
-    icon: '',
-    tip: '',
-    isAdd: false
-  })
-  const rules = { guid: { required: true, message: '请选择资产' } }
-  const showPoup = ref(false)
-  const currentMap: Ref<BaseMap | undefined> = ref()
-  const currentMarker = ref()
-  const allMarker = ref([] as L.Marker[])
-  const store = useStore()
-  
-  // 资产下拉
-  function selectChange(_: string, option: Item) {
-    formMode.value.type = option.type || ''
-  }
-  
-  // 重置marker样式
-  function resetMarker(type: string, num: number | boolean, id?: string) {
-    if (type === 'lat' || type === 'lng') {
-      const obj = currentMarker.value.getLatLng()
-      obj[type] = num
-      currentMarker.value.setLatLng(obj)
-      console.log('位置更新', obj)
-    } else {
-      const newIcon = currentMarker.value.getIcon()
-      if (type === 'scaleX' || type === 'scaleY') {
-        newIcon.options.iconSize[id!] = newIcon.options.iconSize[id!] * (num as number)
-      } else {
-        const newHtml = newIcon.options.html.replace(/style="[^=>]*"/g, (s: string) => {
-          const a = s.split(' ')
-          const c = type === 'flipY' ? 'rotateX' : 'rotateY'
-          const n = type === 'flipY' ? 1 : 2
-          a[n] = num ? `${c}(180deg)${n === 2 ? ';"' : ''}` : `${c}(0deg)${n === 2 ? ';"' : ''}`
-          const b = a.join(' ')
-          return b
-        })
-        newIcon.options.html = newHtml
-      }
-      currentMarker.value.setIcon(newIcon)
-    }
-  }
-  
-  // marker选中状态
-  function selectMaker(marker: L.Marker) {
-    for (let k = 0; k < allMarker.value.length; k++) {
-      const el = allMarker.value[k]
-      const dom = el.getElement()
-      if ((el as Any)._leaflet_id === (marker as Any)._leaflet_id) {
-        dom!.style.boxShadow = '0 0 4px 2px #ff891a'
-      } else {
-        dom!.style.boxShadow = 'none'
-      }
-    }
-    currentMarker.value = marker
-  }
-  
-  // 查询设备列表+设置poup
-  function setPoupData(marker: L.Marker, option: Item) {
-    // 查询设备列表
-    socketService.send('item.query', { itemtype: option.deviceId, isPaging: 0, region: selected.value }).then((res) => {
-      const { success, data: { list } } = res
-      if (success && list) {
-        assetOption.value = list
-      }
-    })
-  
-    formMode.value.uuid = option.uuid || uuid()
-    formMode.value.guid = option.guid || option.item || ''
-    formMode.value.type = option.type || ''
-    formMode.value.x = option.x || 0
-    formMode.value.y = option.y || 0
-    formMode.value.isAdd = option.isAdd || false
-    // 移除其它选中状态
-    selectMaker(marker)
-    showPoup.value = true
-  }
-  
-  // 添加marker
-  function addMarker(option: Item) {
-    if (!Map.value) throw 'Map 未初始化'
-    if (!option.icon) return console.log('缺少参数')
-  
-    // 自定义图标
-    const img = `<img src="${option.icon}" style="width:100%;height:100%;transform: rotateX(0deg) rotateY(0deg);"/>`
-    const ICON = L.divIcon({
-      iconSize: [ (option.scaleX || 1) * 30, (option.scaleY || 1) * 30 ],
-      html: img
-    })
-    const marker = L.marker([ option.y || 0, option.x || 0 ], {
-      icon: ICON,
-      draggable: props.isEdit, // 是否可通过鼠标/触摸拖动。
-      riseOnHover: true
-    }).addTo(Map.value)
-    // add
-    allMarker.value.push(marker)
-    // 显示tip
-    if (props.baseMap.showTip && option.itemName) marker.bindTooltip(option.itemName, { direction: 'top', className: 'resetTips' })
-    // 显示poup
-    if (isCheck.value) {
-      console.log('添加marker', option)
-      setPoupData(marker, option)
-    }
-    // 事件
-    marker.addEventListener('click', () => {
-      console.log('marker点击', option)
-      setPoupData(marker, option)
-      emit('markerClick', option)
-    })
-    marker.addEventListener('dragend', (e) => {
-      console.log('marker拖动', option)
-      const { lat, lng } = e.target.getLatLng()
-      option.x = lng
-      option.y = lat
-      setPoupData(marker, option)
-    })
-  }
-  
-  // 拖拽=>添加marker
-  function itemDragEnd(e: DragEvent) {
-    e.preventDefault()
-    const point = Map.value.containerPointToLayerPoint([ e.layerX, e.layerY ]) // 给定相对于origin pixel的相应像素坐标
-    const lnglat = Map.value.layerPointToLatLng(point) // 给定地理坐标,转换为相对于origin pixel的相应像素坐标
-    if (isCheck.value) {
-      addMarker({
-        ...currentItem.value, x: lnglat.lng, y: lnglat.lat, isAdd: true
-      } as Item)
-    }
-    isCheck.value = false
-  }
-  
-  // 查询marker+绑定
-  async function bindMarker(mapId: string) {
-    if (!mapId) return
-    // 清除
-    if (allMarker.value.length) {
-      for (let k = 0; k < allMarker.value.length; k++) {
-        const marker = allMarker.value[k]
-        marker.remove()
-      }
-      allMarker.value = []
-    }
-    // 添加
-    let page = 1
-    let pages = 0
-    do {
-      const obj = {
-        map: mapId,
-        pageSize: 100,
-        isPaging: 0,
-        page: page++
-      } as { trackType?: number }
-      if (props.baseMap.trackType) obj.trackType = props.baseMap.trackType
-      const { data } = await socketService.send('monitor.itemQuery', obj)
-      store.setObtainedDevices(data.list)
-      console.log('底图关联的marker', data.list)
-      if (data.list) {
-        for (let k = 0; k < data.list.length; k++) {
-          const el = data.list[k]
-          const obj = {
-            parent: el,
-            ...el.mapElement,
-            icon: import.meta.env.VITE_IMG_URL + el.icon,
-            showTip: props.baseMap.showTip
-          }
-          addMarker(obj)
-        }
-      }
-      pages = data.pages
-    } while (page <= pages)
-  }
-  
-  // 切换底图
-  async function changeMap(_: string, option: BaseMap) {
-    console.log('切换底图', option)
-    if (!Map.value) return
-    // 清除layer
-    Map.value.eachLayer((layer: L.Layer) => {
-      Map.value.removeLayer(layer)
-    })
-    // 添加底图
-    if (!option.imgUrl || !option.width || !option.height || !option.mapId) return window.$notification.error({ content: '底图缺少相关参数', duration: 3000, keepAliveOnHover: true })
-    currentMap.value = option
-    L.imageOverlay(import.meta.env.VITE_IMG_URL + option.imgUrl, [ [ 0, 0 ], [ option.height, option.width ] ]).addTo(Map.value)
-    Map.value.setView([ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ], 0)
-    // 获取底图绑定的maker
-    bindMarker(option.mapId)
-  }
-  
-  // 初始化底图
-  function initMap(option: BaseMap) {
-    if (!option) return
-    Map.value = L.map('map', {
-      zoom: 0,
-      maxZoom: 1,
-      minZoom: 0,
-      zoomSnap: 0.1,
-      center: [ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ],
-      crs: L.CRS.Simple,
-      attributionControl: false,
-      zoomControl: false
-    }).addEventListener('click', (e) => {
-      console.log('map点击', e)
-      if (currentMarker.value) currentMarker.value.getElement().style.boxShadow = 'none'
-      formMode.value.flipX = false
-      formMode.value.flipY = false
-      formMode.value.scaleX = 1
-      formMode.value.scaleY = 1
-      formMode.value.uuid = ''
-      showPoup.value = false
-    })
-  
-    // 获取底图列表
-    socketService.send('region.map').then((res) => {
-      const { success, data } = res
-      if (success && data.length) {
-        options.value = data.map((el: { childList: Any[]; }) => {
-          if (el.childList) {
-            el.childList.forEach((es) => {
-              if (es.mapId === props.baseMap.mapId) selected.value = es.id
-              try {
-                const { width, height, imgUrl } = JSON.parse(es.mapConfig)
-                es.width = width
-                es.height = height
-                es.imgUrl = imgUrl
-              } catch (error) {
-                console.log('底图配置数据格式错误')
-              }
-            })
-          }
-          return el
-        })
-      }
-    })
-  
-    // 获取所有maker类型
-    socketService.send('itemType.search', { belongs: '3' }).then((res) => {
-      const { success, data } = res
-      if (success && data.length) iconList.value = data.map((el: { imageUrl: string; name: string; guid: string }) => ({ icon: import.meta.env.VITE_IMG_URL + el.imageUrl, name: el.name, deviceId: el.guid }))
-    })
-  
-    if (props.isEdit) { window.$notification.warning({ content: '请选择车站', duration: 3000, keepAliveOnHover: true }) } else {
-      changeMap('', option)
-    }
-  }
-  
-  // 保存->更新marker
-  async function save() {
-    await formRef.value.validate()
-    const mapElementList = [ {
-      flipX: formMode.value.flipX,
-      flipY: formMode.value.flipY,
-      item: formMode.value.guid,
-      map: currentMap.value!.mapId,
-      scaleX: formMode.value.scaleX,
-      scaleY: formMode.value.scaleY,
-      type: formMode.value.type,
-      uuid: formMode.value.uuid,
-      visiable: true,
-      x: formMode.value.x,
-      y: formMode.value.y
-    } ]
-    console.log('保存', mapElementList, formMode.value.isAdd)
-    const url = formMode.value.isAdd ? 'mapElement.insert' : 'mapElement.update'
-    const { success } = await socketService.send(url, { mapElementList })
-    if (success) {
-      bindMarker(currentMap.value!.mapId)
-      window.$notification.success({ content: formMode.value.isAdd ? '添加成功' : '修改成功', duration: 2000 })
-      showPoup.value = false
-    }
-  }
-  
-  // 删除当前
-  async function del() {
-    if (!currentMarker.value) return
-    const fdid = allMarker.value.findIndex((el: Any) => el._leaflet_id === currentMarker.value._leaflet_id)
-    if (fdid !== -1) {
-      if (!formMode.value.isAdd) {
-        const { success } = await socketService.send('mapElement.delete', [ formMode.value.uuid ])
-        if (success) window.$notification.success({ content: '删除成功', duration: 2000 })
-      }
-      allMarker.value.splice(fdid, 1)
-      currentMarker.value.remove()
-      showPoup.value = false
-    } else {
-      throw '删除失败!'
-    }
-  }
-  
-  // 销毁
-  function destory() {
-    if (!Map.value) return
-    console.log('销毁')
-    Map.value.eachLayer((layer: L.Layer) => {
-      Map.value.removeLayer(layer)
-    })
-    allMarker.value = []
-    currentMarker.value = undefined
-    currentMap.value = undefined
-    Map.value = null
-  }
-  
-  onMounted(() => {
-    if (Object.keys(store.currentStratum).length) {
-      if (Map.value) {
-        changeMap('', store.currentStratum)
-      } else {
-        initMap(store.currentStratum)
-      }
-    }
-  })
-  
-  watch(() => props.baseMap, (v) => {
-    if (Map.value) {
-      changeMap('', v)
-    } else {
-      initMap(v)
-    }
-  }, { deep: true })
-  
-  onUnmounted(() => destory())
-  </script>
-  <style lang="scss" scoped>
-  .mapBox {
-    width: 100%;
-    height: 100%;
-    background: none;
-    position: relative;
-  
-    #map {
-      width: 100%;
-      height: 100%;
-      background: #011719;
-  
-      :deep(.leaflet-div-icon) {
-        background: none;
-        border: none;
-        border-radius: 6px;
-      }
-  
-      :deep(.resetTips) {
-        height: 33px;
-        background: #06353A;
-        border: 1px solid #34C0AE;
-        color: white;
-  
-        &::before {
-          border-top-color: #34C0AE;
-        }
-  
-        p {
-          margin: 0;
-          font-size: 14px;
-        }
-      }
-    }
-  
-    &-menu {
-      position: absolute;
-      left: 0;
-      top: 0;
-      z-index: 601;
-      background: white;
-      width: 380px;
-      height: 100%;
-      padding: 10px;
-  
-      &-content {
-        width: 100%;
-        height: calc(100% - 44px);
-        :deep(.n-scrollbar-content) {
-          display: flex;
-          flex-wrap: wrap;
-          align-content: flex-start;
-        }
-  
-        &-item {
-          text-align: center;
-          width: 78px;
-          height: 78px;
-          padding: 10px 0;
-          border: 1px solid #C9D3DD;
-          cursor: grab;
-          margin-top: 16px;
-  
-          &:not(:nth-child(4n)) {
-            margin-right: 16px;
-          }
-  
-          img {
-            width: 50px;
-            height: 40px;
-          }
-  
-          p {
-            margin: 0;
-            color: #2c3e50;
-            font-size: 12px;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-          }
-        }
-      }
-  
-    }
-  
-    &-poup {
-      position: absolute;
-      right: 0;
-      top: 0;
-      width: 334px;
-      background: #FFFFFF;
-      box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.1);
-      backdrop-filter: blur(29.99999943901511px);
-      z-index: 601;
-      padding: 20px;
-  
-      &-btns {
-        text-align: center;
-  
-        &>button:first-child {
-          margin-right: 20px;
-        }
-      }
-    }
-  }
-  </style>

+ 11 - 11
src/pages/views/planTool/index.vue

@@ -233,7 +233,7 @@ const drawText2 = (cRing, path = tunnelPath[0]) => {
   text.setAttribute('transform', `scale(${1 / scale.value}) rotate(${80} ${Pnt.x} ${Pnt.y}) `)
   return text
 }
-  
+
 /**
  * 自定义每环
  * @params i 当前环
@@ -243,7 +243,7 @@ const drawText2 = (cRing, path = tunnelPath[0]) => {
  * @params t 位置是否空心
  * @returns dom-text
  */
-const drawRing=(i: number, c: string, y: number, w = 1, t = false)=> {
+const drawRing = (i, c, y, w = 1, t = false) => {
   const length = tunnelPath.value[0].getTotalLength()
   const Pnt = tunnelPath.value[0].getPointAtLength(length * (1 / cadConfig.value.totalRing) * i) // 每环位置
   const ringWidth = (1 / cadConfig.value.totalRing) * length * w // 每环宽度
@@ -265,11 +265,11 @@ const drawRing=(i: number, c: string, y: number, w = 1, t = false)=> {
 }
 
 //  格式化掌子面里程
-const mileageFormat = (num: any, type = '') => {
+const mileageFormat = (num, type = '') => {
   if (type && (typeof num === 'number')) {
-    const a = Math.floor((num as number) / 1000).toString()
-    const ab = parseFloat(((num as number) % 1000).toFixed(1))
-    const b = Math.floor((num as number) % 1000).toString()
+    const a = Math.floor((num) / 1000).toString()
+    const ab = parseFloat(((num) % 1000).toFixed(1))
+    const b = Math.floor((num) % 1000).toString()
     const c = b.length === 1 ? `00${ab}` : b.length === 2 ? `0${ab}` : ab
     return `${type || ''}${a}+${c}`
   }
@@ -327,9 +327,9 @@ const initViewer = async () => {
       }
     }
   })
-  //open,animation
-  viewe.addHandler('zoom',()=>{
-  	console.log(1)
+  // open,animation
+  viewer.addHandler('zoom', () => {
+    console.log(1)
   })
   //
   overlay = viewer.svgOverlay().node()
@@ -355,7 +355,7 @@ onMounted(() => {
   document.title = '瓦片图缩放'
   getTunnelPath()
 })
-  
+
 onUnmounted(() => {
   if (viewer) {
     viewer.removeAllHandlers()
@@ -363,7 +363,7 @@ onUnmounted(() => {
     viewer = null
     overlay = null
   }
-}) 
+})
 </script>
 <style lang="scss" scoped>
 #apps {

+ 0 - 305
src/services/socket.service.ts

@@ -1,305 +0,0 @@
-import { injectable, Service } from './service'
-import io from 'socket.io-client'
-import TimerService from './timer.service'
-import useStore from './user.data'
-
-/**
- * socket io 返回数据格式
- */
-type ResponseData = {
-  status: number;
-  body: object | null;
-};
-
-/**
- * 网络服务返回数据格式
- */
-interface Result<T = Any> {
-  success: boolean;
-  data: T;
-}
-
-interface Request<T = Any> {
-  code: string;
-  timestamp: number;
-  nonestr: string;
-  body: T;
-}
-
-/**
- * 约束socket 请求响应参数,code声明,规范请求、返回参数
- */
-interface SendKey<T = Record<string, Any>, P = Record<string, Any>> { }
-
-/**
- * API网络请求服务
- */
-@injectable
-export default class SocketService extends Service {
-  private URL = import.meta.env.VITE_SOCKET_URL
-
-  private userData = useStore()
-
-  private timerService = new TimerService()
-
-  /**
-     * 是否已经完成时间同步
-     * 防止刷新之后因为时间同步问题导致超时
-     */
-  private timeSynced_ = false
-
-  /**
-     * 是否已经完成时间同步
-     */
-  public get timeSynced() {
-    return this.timeSynced_
-  }
-
-  /**
-   * 网络请求监听集合
-   */
-  private listeners: { [code: string]: [(response: object) => void] } = {}
-
-  /**
-     * 请求回调集合
-     */
-  private callbacks: { [key: string]: (response: ResponseData) => void } = {}
-
-  /**
-     * 服务器当前时间戳
-     */
-  private timestamp = Math.floor(Date.now() / 1000)
-
-  /**
-     * socketio对象
-     */
-  private socket!: SocketIOClient.Socket
-
-  /**
-     * 接口超时列表
-     */
-  private timeoutList: { [code: string]: number } = {
-    'system.common': 60 * 1000
-  }
-
-  constructor() {
-    super()
-    // 建立连接
-    this.reset()
-    // 设置定时心跳
-    this.timerService.on('10s', this.ping.bind(this))
-    this.timerService.on('5m', this.syncTime.bind(this))
-    this.timerService.on('5s', () => {
-      this.timestamp += 5
-    })
-    this.aysncTime()
-  }
-
-  public async aysncTime() {
-    while (true) {
-      await (new Promise((res) => {
-        setTimeout(res, 500)
-      }))
-      if (this.timeSynced_) {
-        break
-      }
-      console.log('等待时间同步')
-    }
-  }
-
-  /**
-    * 全局发送消息
-    * @param code 代码
-    * @param message 信息
-    * @param timeout 超时 默认6秒
-    */
-  public async send<R = Any, Q = Any>(code: string | SendKey<Q, R>, data?: Q, timeout = 1000 * 15): Promise<Result<R | null>> {
-    if (this.timeoutList[code as string]) {
-      timeout = this.timeoutList[code as string]
-    }
-    // if (!this.timeSynced_) throw 没同步成功会阻断请求
-    return await this.request(code, data || {}, timeout)
-  }
-
-  /**
-     * 全局监听socketio事件
-     * @param code 命令
-     * @param handle 处理接口
-     */
-  public on(code: string, handle: (response: Any) => void, unique = false) {
-    if (unique) {
-      this.listeners[code] = [ handle ]
-    } else {
-      this.listeners[code] = this.listeners[code] || []
-      this.listeners[code].push(handle)
-    }
-    return true
-  }
-
-  /**
-     * 取消全局监听socketio事件
-     * @param code 命令 * 代表清除所有
-     * @param handle 处理接口
-     */
-  public off(code: string, handle?: (response: object) => void) {
-    if (code === '*') {
-      this.listeners = {}
-      return true
-    }
-    this.listeners[code] = this.listeners[code] || []
-    const index = this.listeners[code].indexOf(handle!)
-    if (index > -1) {
-      this.listeners[code].splice(index, 1)
-      return true
-    }
-    return false
-  }
-
-  /**
-    * 设置身份令牌
-    * @param token 令牌
-    */
-  public setToken(token: string) {
-    this.userData.token = token
-  }
-
-  /**
-     * 发起请求
-     * @param code 命令码
-     * @param data 数据
-     */
-  private request<T, P>(code: string | SendKey<T, P>, data: T | {}, timeout = 1000 * 6): Promise<Result> {
-    const that = this
-    const nonestr = `${Math.floor(Math.random() * 1000000)}`
-    // proto 加密
-    // 发送
-    this.socket.emit(
-      'request',
-      JSON.stringify({
-        code,
-        body: data,
-        timestamp: this.timestamp,
-        nonestr,
-        token: this.userData.token
-      })
-    )
-    return new Promise((res, rej) => {
-      const timer = setTimeout(() => {
-        if (that.callbacks[nonestr]) {
-          delete that.callbacks[nonestr]
-        }
-        throw '请求超时'
-      }, timeout)
-      that.callbacks[nonestr] = (response: ResponseData) => {
-        clearTimeout(timer)
-        delete that.callbacks[nonestr]
-        const status = +response.status
-        if (status !== 0) {
-          if (status === 99) this.reset()
-          if (status === 401) {
-            this.userData.setToken('')
-            throw 401
-          }
-          throw response.body
-        } else {
-          res({ success: true, data: response.body })
-        }
-      }
-    })
-  }
-
-  /**
-     * 心跳监测
-     */
-  private async ping() {
-    if (!this.userData.token) {
-      return
-    }
-    await this.request('user.heart', {})
-  }
-
-  /**
-     * 时间同步
-     */
-  private async syncTime() {
-    const { success, data } = await this.request('server.getNowTime', {})
-    if (success && data) {
-      this.timestamp = data
-      this.timeSynced_ = true
-      console.log('同步成功')
-    }
-  }
-
-  /**
-     * 接收socketio返回的消息
-     * @param res 回复消息体
-     */
-  private onResponse(res: string) {
-    let response = null
-    try {
-      response = JSON.parse(res)
-    } catch (error) {
-      throw `接口返回数据异常 ${res}`
-    }
-    if (response && this.callbacks[response.nonestr]) {
-      this.callbacks[response.nonestr](response)
-    }
-  }
-
-  /**
-     * 接收socketio 的请求消息
-     * @param request 回复消息体
-     */
-  private onRequest(request: string | Request) {
-    let data!: Request
-    // 解析数据
-    if (typeof request === 'string') {
-      const temp = JSON.parse(request)
-      if (temp) data = temp
-    } else {
-      data = request
-    }
-    // 调用监听接口
-    if (this.listeners[data.code]) {
-      const errs: string[] = []
-      for (const handle of this.listeners[data.code]) {
-        try {
-          handle(JSON.parse(JSON.stringify(data)))
-        } catch (err) {
-          errs.push((err as Any).stack)
-        }
-      }
-      if (errs.length) {
-        throw `${errs}`
-      }
-    }
-  }
-
-  /**
-     * 重置连接
-     */
-  private reset() {
-    if (this.socket) {
-      this.socket.close()
-    }
-    try {
-      if (process.env.NODE_ENV === 'development') {
-        const opts = { transports: [ 'websocket' ] }
-        this.URL.includes('https') && Object.assign(opts, { forceNew: true, path: '/wss' })
-        this.socket = io(this.URL, opts)
-      } else {
-        this.socket = io({
-          forceNew: true,
-          transports: [ 'websocket' ],
-          path: '/wss'
-        })
-      }
-      this.socket.on('request', this.onRequest.bind(this))
-      this.socket.on('response', this.onResponse.bind(this))
-      setTimeout(this.ping.bind(this), 300)
-      setTimeout(this.syncTime.bind(this), 100)
-    } catch (err) {
-      throw `${err}`
-    }
-  }
-}

+ 0 - 176
src/services/timer.service.ts

@@ -1,176 +0,0 @@
-import moment from 'moment'
-import { injectable, Service } from './service'
-import useStore from './user.data'
-
-/**
- * 回调定义
- */
-declare interface Callback {
-    /**
-     * 类型 0 永久 1 单次
-     */
-    type: number;
-    fn: () => void;
-}
-
-const INTERVALS = [
-  '1s', '5s', '10s', '30s', '1m', '5m', '10m', '12h'
-]
-
-/**
- * 时间服务
- */
-@injectable
-export default class TimerService extends Service {
-  private callbacks: { [interval: string]: Callback[] } = {}
-
-  private userData = useStore()
-
-  /**
-     * 累计秒数
-     */
-  private ticks = 0
-
-  constructor() {
-    super()
-    setInterval(this.onTick.bind(this), 1000)
-  }
-
-  /**
-     * 监听定时器回调
-     *
-     * @param interval - 事件间隔,必须是 1s、5s、10s、30s 1m、5m 中的1个
-     * @param callback - 定时器回调函数
-     * @returns 成功返回 `true`
-     */
-  public on(interval: '1s' | '5s' | '10s' | '30s' | '1m' | '5m' | '10m' | '12h', callback: () => void) {
-    if (!INTERVALS.includes(interval)) {
-      console.error(`定时器时间间隔必须为: ${INTERVALS.join(',')}`)
-      return false
-    }
-    this.callbacks[interval] = this.callbacks[interval] || []
-    this.callbacks[interval].push({
-      type: 0,
-      fn: callback
-    })
-    return true
-  }
-
-  /**
-     * 监听定时器回调 单次
-     *
-     * @param interval - 事件间隔,必须是 1s、5s、10s、30s 1m、5m、10m 中的1个
-     * @param callback - 定时器回调函数
-     * @returns 成功返回 `true`
-     */
-  public once(interval: '1s' | '5s' | '10s' | '30s' | '1m' | '5m' | '10m' | '12h', callback: () => void) {
-    if (!INTERVALS.includes(interval)) {
-      console.error(`定时器时间间隔必须在 ${INTERVALS.join(',')}`)
-      return false
-    }
-    this.callbacks[interval] = this.callbacks[interval] || []
-    this.callbacks[interval].push({
-      type: 1,
-      fn: callback
-    })
-    return true
-  }
-
-  /**
-     * 取消计时器监听
-     *
-     * @param interval - 事件间隔,必须是 1s、5s、10s、30s 1m、5m、10m 中的1个
-     * @param callback - 定时器回调函数
-     * @returns 成功返回 `true`
-     */
-  public off(interval: '1s' | '5s' | '10s' | '30s' | '1m' | '5m' | '10m' | '12h', callback: () => void) {
-    if (!this.callbacks[interval]) {
-      return false
-    }
-    const index = this.callbacks[interval].findIndex(callback)
-    if (index > -1) {
-      this.callbacks[interval].splice(index, 1)
-    }
-    return true
-  }
-
-  /**
-     * 每秒回调
-     */
-  private onTick() {
-    this.ticks++
-    // 1 秒
-    this.fireCallback('1s')
-    // 5 秒
-    if (this.ticks % 5 === 0) {
-      this.fireCallback('5s')
-    }
-    // 10 秒
-    if (this.ticks % 10 === 0) {
-      this.fireCallback('10s')
-    }
-    // 30 秒
-    if (this.ticks % 30 === 0) {
-      this.fireCallback('30s')
-    }
-    // 1 分钟
-    if (this.ticks % 60 === 0) {
-      this.fireCallback('1m')
-    }
-    // 5 分钟
-    if (this.ticks % 300 === 0) {
-      this.fireCallback('5m')
-    }
-    // 10 分钟
-    if (this.ticks % 600 === 0) {
-      this.fireCallback('10m')
-    }
-
-    // 每小时检测一下是否快过期,快过期就直接刷新token
-    if (this.ticks % 3600 === 0) {
-      const nowTime = Math.floor(Date.now() / 1000)
-      const { user } = this.userData
-      if (!user || !this.userData.token) return
-      const loginTime = user.timestamp ? Math.floor(user.timestamp / 1000) : Math.floor(this.userData.user.timestamp / 1000)
-      if (nowTime - loginTime > 60 * 60 * 11) {
-        this.fireCallback('12h')
-        user.timestamp = Date.now()
-        this.userData.setUser(user)
-      }
-    }
-  }
-
-  /**
-     * 触发定时执行函数
-     * @param cb 回调配置
-     */
-  private fireCallback(cbs: string) {
-    const callbacks = this.callbacks[cbs] || []
-    callbacks.forEach((cb) => {
-      try {
-        cb.fn()
-      } catch (e) {
-        console.log(e)
-      }
-    })
-    this.callbacks[cbs] = callbacks.filter((cb) => cb.type === 0)
-  }
-
-  /**
-     * 获取时间 今天23:59:59 和 多少天前的 00:00:00
-     * @param num 多少天前
-     */
-  public defaultDate(num: number) {
-    // 今天时间
-    const date = new Date()
-    const y = date.getFullYear()
-    const m = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1
-    const d = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate()
-    const timeStr = `${y}-${m}-${d}`
-    const endTime = timeStr
-    // num 天前时间
-    const sT = new Date(timeStr).getTime() / 1000 - num * 24 * 60 * 60 - 8 * 60 * 60
-    const sTime = moment(sT * 1000).format('YYYY-MM-DD')
-    return { sTime, endTime }
-  }
-}

+ 0 - 37
src/services/user.data.ts

@@ -1,37 +0,0 @@
-import { defineStore } from 'pinia'
-
-export interface User {
-  timestamp:number,
-  user:{
-    name:string
-  }
-}
-
-// id必填,且需要唯一
-const useStore = defineStore('userData', {
-  state: () => ({
-    token: '',
-    user: {
-      timestamp: 0,
-      user: {
-        name: ''
-      }
-    }
-  }),
-  actions: {
-    setUser(data: User) {
-      this.user = data
-    },
-    setToken(data:string) {
-      this.token = data
-    },
-    clear() {
-      this.token = ''
-      this.user.timestamp = 0
-    }
-  },
-  persist: {
-    enabled: true // true 表示开启持久化保存
-  }
-})
-export default useStore