pano.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <template>
  2. <div class="pano-layout" v-loading="loading" :element-loading-text="loadingStr">
  3. <canvas ref="panoDomRef"></canvas>
  4. <div class="btns">
  5. <el-button
  6. size="large"
  7. type="primary"
  8. style="margin-right: 20px; width: 100px"
  9. @click="photo"
  10. >
  11. 屏幕拍照
  12. </el-button>
  13. <!-- <el-input-number
  14. style="margin-right: 20px"
  15. v-model="tempRadion"
  16. :precision="2"
  17. :step="0.01"
  18. :min="1"
  19. :max="3"
  20. /> -->
  21. <el-button
  22. size="large"
  23. style="margin-right: 20px; width: 100px"
  24. @click="copyGis"
  25. v-if="point && !noValidPoint(point)"
  26. >
  27. 复制经纬度
  28. </el-button>
  29. <el-button
  30. size="large"
  31. type="primary"
  32. style="width: 100px"
  33. @click="update = true"
  34. v-if="router.currentRoute.value.name === 'pano'"
  35. >
  36. 测点说明
  37. </el-button>
  38. </div>
  39. </div>
  40. <SingleInput
  41. v-if="point"
  42. :visible="update"
  43. @update:visible="update = false"
  44. :value="point"
  45. :update-value="(npoint) => updateScenePointName(npoint)"
  46. />
  47. </template>
  48. <script setup lang="ts">
  49. import SingleInput from "@/components/point-input.vue";
  50. import { router, setDocTitle } from "@/router";
  51. import { mergeFuns, round } from "@/util";
  52. import { glMatrix } from "gl-matrix";
  53. import { computed, nextTick, onMounted, onUnmounted, ref, watchEffect } from "vue";
  54. import { init } from "./env";
  55. import {
  56. updateScenePointName,
  57. getPointPano,
  58. ScenePoint,
  59. scenePoints,
  60. } from "@/store/scene";
  61. import { copyText, toDegrees, getTextBound } from "@/util";
  62. import { ElMessage } from "element-plus";
  63. import saveAs from "@/util/file-serve";
  64. import { DeviceType } from "@/store/device";
  65. import { initRelics, relics } from "@/store/relics";
  66. import { noValidPoint } from "../map/install";
  67. import { addWatermark } from "@/util/image";
  68. type Params = { pid?: string; relicsId?: string } | null;
  69. const params = computed(() => router.currentRoute.value.params as Params);
  70. const panoDomRef = ref<HTMLCanvasElement>();
  71. const destroyFns: (() => void)[] = [];
  72. const point = ref<ScenePoint>();
  73. const tempRadion = ref(3.0);
  74. watchEffect(() => {
  75. if (params.value?.pid) {
  76. const pid = Number(params.value!.pid);
  77. point.value = scenePoints.value.find((scene) => scene.id === pid);
  78. if (!point.value) {
  79. initRelics(Number(params.value.relicsId)).then(() => {
  80. point.value = scenePoints.value.find((scene) => scene.id === pid);
  81. if (!point.value) {
  82. router.replace({ name: "relics" });
  83. }
  84. });
  85. }
  86. }
  87. });
  88. const panoUrls = computed(() => {
  89. return (
  90. point.value && getPointPano(point.value, point.value.cameraType === DeviceType.CLUNT)
  91. );
  92. });
  93. const update = ref(false);
  94. const loading = ref(false);
  95. const loadingStr = ref("");
  96. const getGis = () => {
  97. const pos = point.value!.pos as number[];
  98. return `经度: ${toDegrees(pos[0])}\n纬度: ${toDegrees(pos[1])}\n高程: ${round(
  99. pos[2],
  100. 4
  101. )}`;
  102. };
  103. const copyGis = async () => {
  104. await copyText(getGis());
  105. ElMessage.success("经纬度高程复制成功");
  106. };
  107. const photo = async () => {
  108. loading.value = true;
  109. loadingStr.value = "原图提取中";
  110. await new Promise((resolve) => setTimeout(resolve, 300));
  111. const ration = tempRadion.value;
  112. console.log("ration", ration);
  113. setSize(ration, 1920, 1080);
  114. let dataURL: Blob | string = panoDomRef.value.toDataURL("image/jpeg", 1);
  115. if (!noValidPoint(point.value)) {
  116. dataURL = await addWatermark(dataURL, point.value!.pos, ration);
  117. }
  118. await saveAs(dataURL, `${relics.value?.name}.jpg`);
  119. ElMessage.success("图片导出成功");
  120. setSize(devicePixelRatio);
  121. loading.value = false;
  122. };
  123. let pano: ReturnType<typeof init>;
  124. const setSize = (ration: number, w?: number, h?: number) => {
  125. const canvas = panoDomRef.value!;
  126. canvas.width = (w || canvas.offsetWidth) * ration;
  127. canvas.height = (h || canvas.offsetHeight) * ration;
  128. pano.setSize([canvas.width, canvas.height]);
  129. };
  130. onMounted(() => {
  131. if (!panoDomRef.value) throw "没有canvas DOM";
  132. pano = init(panoDomRef.value, 0);
  133. const resizeHandler = () => {
  134. setSize(devicePixelRatio);
  135. };
  136. resizeHandler();
  137. window.addEventListener("resize", resizeHandler);
  138. destroyFns.push(
  139. watchEffect(() => {
  140. if (panoUrls.value) {
  141. loading.value = true;
  142. pano.changeUrls(panoUrls.value).then(() => (loading.value = false));
  143. pano.setYaw(
  144. point.value.cameraType === DeviceType.CLUNT ? glMatrix.toRadian(180) : 0
  145. );
  146. }
  147. }),
  148. pano.destory,
  149. () => {
  150. window.removeEventListener("resize", resizeHandler);
  151. }
  152. );
  153. });
  154. onUnmounted(() => mergeFuns(...destroyFns)());
  155. watchEffect(() => {
  156. if (router.currentRoute.value.name.toString().includes("pano") && point.value) {
  157. setDocTitle(point.value.index.toString() || relics.value.name);
  158. }
  159. });
  160. </script>
  161. <style scoped lang="scss">
  162. .pano-layout,
  163. canvas {
  164. width: 100%;
  165. height: 100%;
  166. }
  167. .pano-layout {
  168. position: relative;
  169. .btns {
  170. position: absolute;
  171. left: 50%;
  172. transform: translateX(-50%);
  173. bottom: 40px;
  174. z-index: 1;
  175. }
  176. }
  177. </style>