index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <template>
  2. <RightFillPano>
  3. <ui-group title="初始画面" borderBottom>
  4. <ui-group-option>
  5. <div class="init-pic" :class="{ disabled: isEdit }">
  6. <img :src="getFileUrl(setting!.cover)" class="init-puc-cover" />
  7. <div class="init-pic-set" @click="enterSetPic">设置</div>
  8. </div>
  9. </ui-group-option>
  10. </ui-group>
  11. <ui-group title="指北针">
  12. <template #icon>
  13. <ui-icon
  14. ctrl
  15. :type="setting?.openCompass ? 'eye-s' : 'eye-n'"
  16. @click="
  17. changeBack(
  18. setting!.back,
  19. setting!.backType,
  20. !setting!.openCompass,
  21. setting!.mapOpen,
  22. setting!.mapType,
  23. { scale: setting!.scale!, rotate: setting?.rotate! }
  24. )
  25. "
  26. />
  27. </template>
  28. </ui-group>
  29. <ui-group title="地图" v-if="caseProject!.tmProject?.latlng">
  30. <template #icon>
  31. <ui-icon
  32. ctrl
  33. :type="setting?.mapOpen ? 'eye-s' : 'eye-n'"
  34. @click="
  35. changeBack(
  36. setting!.back,
  37. setting!.backType,
  38. setting!.openCompass,
  39. !setting!.mapOpen,
  40. setting!.mapType,
  41. { scale: setting!.scale!, rotate: setting?.rotate! }
  42. )
  43. "
  44. />
  45. </template>
  46. <ui-group-option v-if="setting?.mapOpen">
  47. <ui-input
  48. type="select"
  49. width="100%"
  50. :options="[
  51. { label: '卫星地图', value: 'satellite' },
  52. { label: '矢量地图', value: 'standard' },
  53. ]"
  54. :modelValue="setting!.mapType"
  55. @update:modelValue="(e: string )=> changeBack(setting!.back, setting!.backType, setting!.openCompass, setting!.mapOpen, e, {scale: setting!.scale!, rotate: setting?.rotate!})"
  56. />
  57. </ui-group-option>
  58. </ui-group>
  59. <ui-group title="视角范围">
  60. <ui-group-option class="ant-modal-root">
  61. <Slider
  62. :value="setting!.fov || 70"
  63. :min="40"
  64. :step="1"
  65. :max="100"
  66. @update:value="(val: any) => changeFov(val)"
  67. />
  68. </ui-group-option>
  69. </ui-group>
  70. <ui-group title="设置背景">
  71. <ui-group-option>
  72. <div class="back-layout">
  73. <div
  74. v-for="back in settingResources"
  75. :key="back.resource"
  76. class="back-item"
  77. :class="{ [back.backType]: true, active: setting!.back === back.resource }"
  78. @click="
  79. setting!.back !== back.resource &&
  80. changeBack(
  81. back.resource,
  82. back.backType,
  83. setting!.openCompass,
  84. setting!.mapOpen,
  85. setting!.mapType,
  86. { scale: setting!.scale!, rotate: setting?.rotate! }
  87. )
  88. "
  89. >
  90. <img
  91. :src="back.covre || back.resource"
  92. v-if="back.backType === 'img' || back.backType === 'bimg'"
  93. />
  94. <i
  95. class="iconfont"
  96. :class="back.resource"
  97. v-else-if="back.backType === 'icon'"
  98. />
  99. <span :style="{ background: back.resource }" v-else></span>
  100. <a class="back-item-desc">
  101. {{
  102. (settingResourceTypeDesc[back.backType] &&
  103. settingResourceTypeDesc[back.backType] + "-") + back.name
  104. }}
  105. </a>
  106. <ui-icon
  107. v-if="!back.sys"
  108. type="close"
  109. class="del"
  110. @click.stop="delBack(back)"
  111. />
  112. </div>
  113. <ui-input
  114. class="input"
  115. preview
  116. accept=".jpg, .jpeg, .png"
  117. @update:modelValue="iconUpload"
  118. type="file"
  119. >
  120. <template v-slot:replace>
  121. <div class="back-item icon">
  122. <i class="iconfont icon-add" />
  123. </div>
  124. </template>
  125. </ui-input>
  126. </div>
  127. </ui-group-option>
  128. </ui-group>
  129. <Teleport
  130. to="#layout-app"
  131. v-if="
  132. (setting?.backType === SettingResourceType.bottomImage &&
  133. $router.currentRoute.value.name === RoutesName.setting)
  134. "
  135. >
  136. <div class="slider-demo-block-group">
  137. <div class="slider-demo-block ant-modal-root">
  138. <span> 缩放</span>
  139. <Slider
  140. :value="setting!.scale || 1"
  141. :min="0.1"
  142. :step="0.01"
  143. :max="3"
  144. @update:value="(val: any) => changeBack(setting!.back, setting!.backType, setting!.openCompass, setting!.mapOpen, setting!.mapType, {scale: val, rotate: setting?.rotate!})"
  145. />
  146. </div>
  147. <div class="slider-demo-block ant-modal-root">
  148. <span> 旋转</span>
  149. <Slider
  150. :value="setting!.rotate || 0"
  151. :min="0.1"
  152. :step="0.01"
  153. :max="360"
  154. @update:value="(val: any) => changeBack(setting!.back, setting!.backType, setting!.openCompass, setting!.mapOpen, setting!.mapType, {scale: setting!.scale!, rotate: val})"
  155. />
  156. </div>
  157. </div>
  158. </Teleport>
  159. </RightFillPano>
  160. <div class="edit-add-type" v-if="addTemp">
  161. <div class="edit-hot-item">
  162. <h3 class="edit-title">
  163. 背景图
  164. <ui-icon
  165. type="close"
  166. ctrl
  167. @click.stop="addTemp = undefined"
  168. class="edit-close"
  169. />
  170. </h3>
  171. <ui-input
  172. require
  173. class="input"
  174. width="100%"
  175. placeholder="请输入背景图名称标注"
  176. type="text"
  177. v-model="addTemp.name"
  178. maxlength="15"
  179. />
  180. <ui-input
  181. require
  182. class="input"
  183. width="100%"
  184. placeholder="请输入背景图名称标注"
  185. type="select"
  186. :options="options"
  187. v-model="addTemp.backType"
  188. maxlength="15"
  189. />
  190. <div class="edit-hot">
  191. <a @click="addBack">
  192. <ui-icon type="nav-edit" />
  193. 确定
  194. </a>
  195. </div>
  196. </div>
  197. </div>
  198. </template>
  199. <script lang="ts" setup>
  200. import { RightFillPano } from "@/layout";
  201. import {
  202. enterEdit,
  203. enterOld,
  204. setting,
  205. isEdit,
  206. updataSetting,
  207. caseProject,
  208. createTemploraryID,
  209. save,
  210. } from "@/store";
  211. import { ref } from "vue";
  212. import { togetherCallback, getFileUrl, loadPack, asyncTimeout } from "@/utils";
  213. import { showRightPanoStack, showRightCtrlPanoStack } from "@/env";
  214. import { sdk, setBackdrop, setMap } from "@/sdk";
  215. import {
  216. delSettingResource,
  217. fetchSettingResources,
  218. settingResources,
  219. settingResourceTypeDesc,
  220. } from "@/api/setting-resource";
  221. import { uploadFile } from "@/api";
  222. import { SettingResource, addSettingResource } from "@/api/setting-resource";
  223. import { SettingResourceType } from "@/api/setting-resource";
  224. import { Dialog } from "bill/index";
  225. import { Slider } from "ant-design-vue";
  226. import { RoutesName } from "@/router";
  227. fetchSettingResources();
  228. const addBack = async () => {
  229. if (!addTemp.value!.name.trim()) {
  230. Dialog.alert("请输入名称");
  231. return;
  232. }
  233. await addSettingResource(addTemp.value!);
  234. addTemp.value = undefined;
  235. await fetchSettingResources();
  236. };
  237. const enterSetPic = () => {
  238. enterEdit(
  239. togetherCallback([
  240. showRightPanoStack.push(ref(false)),
  241. showRightCtrlPanoStack.push(ref(false)),
  242. ])
  243. );
  244. enterOld(async () => {
  245. const dataURL = await sdk.screenshot(300, 150);
  246. const res = await fetch(dataURL);
  247. const blob = await res.blob();
  248. setting.value = {
  249. ...setting.value!,
  250. cover: { url: dataURL, blob },
  251. pose: sdk.getPose(),
  252. };
  253. await updataSetting();
  254. });
  255. };
  256. const initBack = setting.value!.back;
  257. const initType = setting.value!.backType;
  258. const initOpenCompass = setting.value!.openCompass;
  259. const initopenMap = setting.value!.mapOpen;
  260. const initmapType = setting.value!.mapType;
  261. const initScale = setting.value!.scale;
  262. const initRotate = setting.value!.rotate;
  263. let isFirst = true;
  264. const changeBack = (
  265. back: string,
  266. type: SettingResourceType,
  267. openCompass: boolean,
  268. openMap: boolean,
  269. mapType: string,
  270. tb: { scale: number; rotate: number } = { scale: 1, rotate: 0 }
  271. ) => {
  272. if (
  273. type === SettingResourceType.map &&
  274. !caseProject.value!.tmProject?.latlng
  275. ) {
  276. Dialog.alert("当前案件没绑定经纬度,无法开启地图功能");
  277. return;
  278. }
  279. setting.value!.back = back;
  280. setting.value!.backType = type;
  281. setting.value!.openCompass = openCompass;
  282. setting.value!.mapOpen = openMap;
  283. setting.value!.mapType = mapType;
  284. setting.value!.scale = tb.scale;
  285. setting.value!.rotate = tb.rotate;
  286. setBackdrop(back, type, tb);
  287. setMap(openMap, mapType);
  288. (document.querySelector("#direction") as HTMLDivElement)!.style.display =
  289. openCompass ? "block" : "none";
  290. if (isFirst) {
  291. let isSave = false;
  292. isFirst = false;
  293. enterEdit(() => {
  294. if (!isSave) {
  295. setting.value!.back = initBack;
  296. setting.value!.backType = initType;
  297. setting.value!.openCompass = initOpenCompass;
  298. setting.value!.mapOpen = initopenMap;
  299. setting.value!.mapType = initmapType;
  300. setting.value!.scale = initScale;
  301. setting.value!.rotate = initRotate;
  302. setBackdrop(initBack, initType, {
  303. scale: initScale,
  304. rotate: initRotate,
  305. });
  306. setMap(initopenMap, initmapType);
  307. (document.querySelector(
  308. "#direction"
  309. ) as HTMLDivElement)!.style.display = initOpenCompass
  310. ? "block"
  311. : "none";
  312. }
  313. isFirst = true;
  314. });
  315. enterOld(async () => {
  316. isSave = true;
  317. await loadPack(updataSetting());
  318. });
  319. }
  320. };
  321. const delBack = async (back: SettingResource) => {
  322. if (setting.value?.back === back.resource) {
  323. changeBack(
  324. settingResources.value[0].resource,
  325. settingResources.value[0].backType,
  326. setting!.value.openCompass,
  327. setting!.value.mapOpen,
  328. setting!.value.mapType,
  329. { scale: setting!.value.scale!, rotate: setting!.value.rotate! }
  330. );
  331. await asyncTimeout(100);
  332. await save();
  333. }
  334. const ndx = settingResources.value.indexOf(back);
  335. if (~ndx) {
  336. settingResources.value.splice(ndx, 1);
  337. }
  338. await delSettingResource(back);
  339. };
  340. const delBack1 = (() => {
  341. let isFirst = true;
  342. let oldResources: SettingResource[];
  343. let dels: SettingResource[] = [];
  344. return (back: SettingResource) => {
  345. if (setting.value?.back === back.resource) {
  346. changeBack(
  347. settingResources.value[0].resource,
  348. settingResources.value[0].backType,
  349. setting!.value.openCompass,
  350. setting!.value.mapOpen,
  351. setting!.value.mapType,
  352. { scale: setting!.value.scale!, rotate: setting!.value.rotate! }
  353. );
  354. }
  355. if (isFirst) {
  356. dels = [];
  357. oldResources = [...settingResources.value];
  358. }
  359. const ndx = settingResources.value.indexOf(back);
  360. if (~ndx) {
  361. settingResources.value.splice(ndx, 1);
  362. dels.push(back);
  363. }
  364. if (isFirst) {
  365. let isSave = false;
  366. isFirst = false;
  367. enterEdit(() => {
  368. if (!isSave) {
  369. settingResources.value = oldResources;
  370. }
  371. isFirst = true;
  372. });
  373. enterOld(async () => {
  374. isSave = true;
  375. await loadPack(Promise.all(dels.map(delSettingResource)));
  376. });
  377. }
  378. };
  379. })();
  380. const changeFov = (() => {
  381. let isFirst = true;
  382. let initFov: number;
  383. return (fov: number) => {
  384. if (isFirst) {
  385. initFov = setting.value!.fov;
  386. }
  387. setting.value!.fov = fov;
  388. if (isFirst) {
  389. let isSave = false;
  390. isFirst = false;
  391. enterEdit(() => {
  392. if (!isSave) {
  393. setting.value!.fov = initFov;
  394. }
  395. isFirst = true;
  396. });
  397. enterOld(async () => {
  398. isSave = true;
  399. await loadPack(updataSetting());
  400. });
  401. }
  402. };
  403. })();
  404. const options = [
  405. {
  406. value: SettingResourceType.envImage,
  407. label: settingResourceTypeDesc[SettingResourceType.envImage],
  408. },
  409. {
  410. value: SettingResourceType.bottomImage,
  411. label: settingResourceTypeDesc[SettingResourceType.bottomImage],
  412. },
  413. ];
  414. const addTemp = ref<SettingResource>();
  415. const iconUpload = async (data: any) => {
  416. addTemp.value = {
  417. resource: await uploadFile({ blob: data.file as any, url: "" }),
  418. name: "",
  419. backType: SettingResourceType.envImage,
  420. };
  421. };
  422. </script>
  423. <style scoped lang="scss">
  424. .init-pic {
  425. height: 150px;
  426. border-radius: 4px;
  427. overflow: hidden;
  428. position: relative;
  429. }
  430. .init-puc-cover {
  431. width: 100%;
  432. height: 100%;
  433. object-fit: cover;
  434. }
  435. .init-pic-set {
  436. position: absolute;
  437. bottom: 0;
  438. left: 0;
  439. right: 0;
  440. background-color: rgba(0, 0, 0, 0.5);
  441. font-size: 12px;
  442. color: #fff;
  443. line-height: 32px;
  444. z-index: 1;
  445. text-align: center;
  446. cursor: pointer;
  447. }
  448. .back-layout {
  449. display: grid;
  450. grid-template-columns: repeat(3, 1fr);
  451. gap: 20px;
  452. }
  453. .back-item {
  454. position: relative;
  455. > span,
  456. .iconfont,
  457. img {
  458. display: block;
  459. height: 88px;
  460. cursor: pointer;
  461. outline: 2px solid transparent;
  462. transition: all 0.3s;
  463. border-radius: 4px;
  464. width: 88px;
  465. object-fit: cover;
  466. }
  467. .del {
  468. position: absolute;
  469. background: #ef4347;
  470. width: 20px;
  471. height: 20px;
  472. top: -10px;
  473. right: -10px;
  474. opacity: 0;
  475. // opacity: 1;
  476. border-radius: 50%;
  477. &.iconfont {
  478. font-size: 10px;
  479. color: #fff;
  480. }
  481. &:hover {
  482. opacity: 1 !important;
  483. }
  484. }
  485. &:hover .del {
  486. opacity: 0.9;
  487. }
  488. .iconfont {
  489. display: flex;
  490. align-items: center;
  491. justify-content: center;
  492. color: #525252;
  493. font-size: 32px;
  494. }
  495. img {
  496. object-fit: cover;
  497. }
  498. &.active {
  499. > span,
  500. .iconfont,
  501. img {
  502. outline-color: #00c8af;
  503. }
  504. }
  505. }
  506. :deep(.back-item .del.iconfont) {
  507. outline-style: none;
  508. }
  509. .back-item-desc {
  510. font-size: 14px;
  511. color: #fff;
  512. margin-top: 10px;
  513. display: block;
  514. text-align: center;
  515. overflow: hidden;
  516. white-space: nowrap;
  517. text-overflow: ellipsis;
  518. -o-text-overflow: ellipsis;
  519. white-space: nowrap; //文本不会换行
  520. width: 88px;
  521. }
  522. .edit-add-type {
  523. position: fixed;
  524. inset: 0;
  525. background: rgba(0, 0, 0, 0.3);
  526. backdrop-filter: blur(4px);
  527. z-index: 2000;
  528. padding: 20px;
  529. overflow-y: auto;
  530. .edit-hot-item {
  531. margin: 100px auto 20px;
  532. width: 400px;
  533. padding: 20px;
  534. background: rgba(27, 27, 28, 0.8);
  535. box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
  536. border-radius: 4px;
  537. .input {
  538. margin-bottom: 10px;
  539. }
  540. }
  541. }
  542. .edit-hot {
  543. margin-top: 20px;
  544. text-align: right;
  545. span {
  546. font-size: 14px;
  547. color: rgba(255, 255, 255, 0.6);
  548. cursor: pointer;
  549. }
  550. }
  551. .edit-close {
  552. position: absolute;
  553. cursor: pointer;
  554. top: calc((100% - 18px) / 2);
  555. right: 0;
  556. transform: translateY(-50%);
  557. }
  558. .edit-title {
  559. padding-bottom: 18px;
  560. margin-bottom: 18px;
  561. position: relative;
  562. &::after {
  563. content: "";
  564. position: absolute;
  565. left: -20px;
  566. right: -20px;
  567. height: 1px;
  568. bottom: 0;
  569. background-color: rgba(255, 255, 255, 0.16);
  570. }
  571. }
  572. .slider-demo-block-group {
  573. position: absolute;
  574. z-index: 10000;
  575. padding: 8px 24px;
  576. background-color: rgba(0, 0, 0, 0.3);
  577. left: 50%;
  578. border-radius: 5px;
  579. bottom: 20px;
  580. transform: translateX(-50%);
  581. margin-right: 20px;
  582. z-index: 99;
  583. // height: 300px;
  584. width: 386px;
  585. display: flex;
  586. flex-wrap: wrap;
  587. flex-direction: row;
  588. }
  589. .slider-demo-block {
  590. flex: 1 1 100%;
  591. display: inline-flex;
  592. flex-direction: row;
  593. justify-content: center;
  594. align-items: center;
  595. span {
  596. padding-right: 10px;
  597. }
  598. .ant-slider {
  599. flex: 1;
  600. }
  601. }
  602. :global(.ant-modal-root .ant-slider-track) {
  603. background-color: #00c8af;
  604. }
  605. :global(.ant-modal-root .ant-slider:hover .ant-slider-track) {
  606. background-color: #00c8af;
  607. }
  608. :global(.ant-modal-root .ant-slider-handle) {
  609. border: solid 2px #00c8af !important;
  610. }
  611. :global(.ant-modal-root .ant-slider-handle.ant-tooltip-open) {
  612. border-color: #00c8af;
  613. }
  614. :global(
  615. .ant-modal-root .ant-slider-handle:focus,
  616. .ant-modal-root .ant-slider-handle:hover,
  617. .ant-modal-root .ant-slider-handle:active
  618. ) {
  619. border: solid 2px #03ad99 !important;
  620. box-shadow: none;
  621. }
  622. </style>