FileInfo.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <div class="fileInfo">
  3. <a-spin :spinning="state.downloadLoading" tip="下载中...">
  4. <div class="fileInfo-detail">
  5. <div class="fileInfo-detail-left">
  6. <div class="fileInfo-detail-left-background" v-if="state.imgLoading">
  7. <a-spin tip="加载中..." />
  8. </div>
  9. <Panoramic :src="state.info.url" v-if="state.info.media_type === 3" />
  10. <video style="width: 100%;height: 100%;" controls :src="state.info.url"
  11. v-else-if="state.info.media_type === 4"></video>
  12. <a-image style="object-fit:cover;" width="100%" height="100%" :src="state.info.url" v-else />
  13. </div>
  14. <div class="fileInfo-detail-info">
  15. <CloseOutlined class="fileInfo-detail-info-close" @click="onClose" />
  16. <div class="fileInfo-detail-info-title">
  17. 详细信息
  18. </div>
  19. <div class="fileInfo-detail-info-item">
  20. <div class="fileInfo-detail-info-item-title">
  21. 文件名称
  22. </div>
  23. <div class="fileInfo-detail-info-item-content">
  24. {{ state.info.file_name || '--' }}
  25. </div>
  26. </div>
  27. <div class="fileInfo-detail-info-item">
  28. <div class="fileInfo-detail-info-item-title">
  29. 文件类型
  30. </div>
  31. <div class="fileInfo-detail-info-item-content">
  32. {{ state.info.picture_type || '--' }}
  33. </div>
  34. </div>
  35. <div class="fileInfo-detail-info-item">
  36. <div class="fileInfo-detail-info-item-title">
  37. 任务名称
  38. </div>
  39. <div class="fileInfo-detail-info-item-content">
  40. {{ state.info.task_name || '--' }}
  41. </div>
  42. </div>
  43. <div class="fileInfo-detail-info-item">
  44. <div class="fileInfo-detail-info-item-title">
  45. 航线名称
  46. </div>
  47. <div class="fileInfo-detail-info-item-content">
  48. {{ state.info.wayline_name || '--' }}
  49. </div>
  50. </div>
  51. <div class="fileInfo-detail-info-item">
  52. <div class="fileInfo-detail-info-item-title">
  53. 分辨率
  54. </div>
  55. <div class="fileInfo-detail-info-item-content">
  56. <div class="fileInfo-detail-info-item-content-line"
  57. v-if="state.info.image_width && state.info.image_height">
  58. {{ state.info.image_width }}*{{ state.info.image_height }}
  59. </div>
  60. <div v-else>
  61. --
  62. </div>
  63. </div>
  64. </div>
  65. <div class="fileInfo-detail-info-item">
  66. <div class="fileInfo-detail-info-item-title">
  67. 文件大小
  68. </div>
  69. <div class="fileInfo-detail-info-item-content">
  70. {{ state.info.size > 0 ? (state.info.size / 1024 / 1024).toFixed(1) + 'M' : '--' }}
  71. </div>
  72. </div>
  73. <div class="fileInfo-detail-info-item">
  74. <div class="fileInfo-detail-info-item-title">
  75. 拍摄负载
  76. </div>
  77. <div class="fileInfo-detail-info-item-content">
  78. {{ state.info.payload || '--' }}
  79. </div>
  80. </div>
  81. <div class="fileInfo-detail-info-item">
  82. <div class="fileInfo-detail-info-item-title">
  83. 拍摄时间
  84. </div>
  85. <div class="fileInfo-detail-info-item-content">
  86. {{ state.info.picture_time || '--' }}
  87. </div>
  88. </div>
  89. <div class="fileInfo-detail-info-map">
  90. <div class="fileInfo-detail-info-map-title">
  91. <div class="fileInfo-detail-info-map-title-text">
  92. 照片位置
  93. </div>
  94. <div class="fileInfo-detail-info-map-title-icon">
  95. <EnvironmentOutlined @click="onClickMapLocationReset" />
  96. </div>
  97. </div>
  98. <div class="fileInfo-detail-info-map-content">
  99. <div id="photoPositionMap" :style="{ width: '100%', height: '100%' }"></div>
  100. <div class="fileInfo-detail-info-map-content-title">
  101. <span>
  102. {{ state.info.latitude }}° N
  103. </span>
  104. <span>
  105. {{ state.info.longitude }}° E
  106. </span>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. <div class="fileInfo-area">
  113. <a-tooltip placement="bottom" title="下载">
  114. <DownloadOutlined style="font-size: 20px;margin-right: 20px;" @click="onClickDownload" />
  115. </a-tooltip>
  116. <div v-if="[1, 3].includes(state.info.media_type)">
  117. <a-tooltip placement="bottom" title="在地图上加载" v-if="!state.info.element_id">
  118. <AimOutlined style="font-size: 20px;margin-right: 20px;" @click="onClickCreateMapElement" />
  119. </a-tooltip>
  120. <a-tooltip placement="bottom" title="在地图上取消加载" v-else>
  121. <AimOutlined style="font-size: 20px;margin-right: 20px;" @click="onClickDeleteMapElement" />
  122. </a-tooltip>
  123. </div>
  124. </div>
  125. <div class="fileInfo-previewList">
  126. <div class="fileInfo-previewList-button" @click="onClickScroll('LEFT')" @mousedown.prevent>
  127. <VerticalRightOutlined />
  128. </div>
  129. <div ref="contentRef" class="fileInfo-previewList-content">
  130. <img
  131. :class="['fileInfo-previewList-content-item', state.currentId === item.file_id ? 'fileInfo-previewList-content-item-selected' : '']"
  132. v-for="item in fileList" :src="item.thumbnail_url" @click="onClickSelected(item.file_id)" />
  133. </div>
  134. <div class="fileInfo-previewList-button" @click="onClickScroll('RIGHT')" @mousedown.prevent>
  135. <VerticalLeftOutlined />
  136. </div>
  137. </div>
  138. </a-spin>
  139. </div>
  140. </template>
  141. <script lang="ts" setup>
  142. import { ref, reactive, onMounted } from 'vue';
  143. import { message } from 'ant-design-vue';
  144. import { CloseOutlined, EnvironmentOutlined, DownloadOutlined, AimOutlined, VerticalRightOutlined, VerticalLeftOutlined } from '@ant-design/icons-vue';
  145. import Panoramic from '/@/components/panoramic/index.vue';
  146. import { useGMapManage } from '/@/hooks/use-g-map';
  147. import { apis } from '/@/api/custom';
  148. import { downloadFile } from '/@/utils/common';
  149. import { downloadMediaFile } from '/@/api/media';
  150. import { wgs84togcj02 } from '../../../../../../vendors/coordtransform'
  151. import { getWorkspaceId } from '/@/utils/index'
  152. interface Props {
  153. fileId: string,
  154. fileList: any[],
  155. onClose: () => Promise<any>,
  156. };
  157. const props = withDefaults(defineProps<Props>(), {
  158. });
  159. const state = reactive({
  160. currentId: props.fileId,
  161. downloadLoading: false,
  162. imgLoading: false,// 图片加载
  163. info: {
  164. url: '',
  165. media_type: 0,
  166. file_id: '',
  167. element_id: '',
  168. file_name: '',
  169. picture_type: '',
  170. task_name: '',
  171. wayline_name: '',
  172. image_width: '',// 照片分辨率-宽度
  173. image_height: '',// 照片分辨率-高度
  174. size: 0,
  175. payload: '',
  176. picture_time: '',
  177. longitude: '',// 经度
  178. latitude: '',// 纬度
  179. coordinates: [],
  180. },
  181. map: null,// 高德地图实例
  182. })
  183. const contentRef = ref();
  184. // 高德地图Hook
  185. const AmapHook = useGMapManage();
  186. const init = async () => {
  187. try {
  188. const res = await apis.fetchFileDetail(state.currentId);
  189. state.info = {
  190. ...res.data,
  191. longitude: res.data.longitude ? res.data.longitude.toFixed(6) : '',
  192. latitude: res.data.latitude ? res.data.latitude.toFixed(6) : '',
  193. coordinates: (res.data.longitude && res.data.latitude) ? wgs84togcj02(res.data.longitude, res.data.latitude) : [],
  194. };
  195. if (res.data.media_type !== 4) {
  196. state.imgLoading = true;
  197. const img = new Image();
  198. img.src = state.info.url;
  199. img.onload = () => {
  200. state.imgLoading = false;
  201. };
  202. }
  203. if (state.info.coordinates.length) {
  204. const AMap = await AmapHook.asyncInitMap();
  205. const map = new AMap.Map('photoPositionMap', {
  206. layers: [new AMap.TileLayer.Satellite()],// 卫星图-图层
  207. viewMode: '3D',// 3D地图
  208. rotateEnable: true,// 开启地图旋转交互
  209. pitchEnable: true,// 开启地图倾斜交互
  210. zoom: 17, // 初始化地图层级
  211. center: state.info.coordinates,// 中心点
  212. })
  213. state.map = map;
  214. // 创建一个标记并将其添加到地图上
  215. new AMap.Marker({
  216. map: map,
  217. cursor: 'pointer',
  218. position: state.info.coordinates,
  219. });
  220. }
  221. } catch (e) {
  222. console.error(e);
  223. }
  224. }
  225. onMounted(async () => {
  226. await init()
  227. })
  228. // 点击下载
  229. const onClickDownload = async () => {
  230. state.downloadLoading = true;
  231. try {
  232. const workspaceId: string = getWorkspaceId();
  233. const res = await downloadMediaFile(workspaceId, state.info.file_id)
  234. if (!res) {
  235. return
  236. }
  237. const data = new Blob([res])
  238. downloadFile(data, state.info.file_name)
  239. } catch (e) {
  240. console.error(e);
  241. } finally {
  242. state.downloadLoading = false;
  243. }
  244. }
  245. // 点击在地图上加载
  246. const onClickCreateMapElement = async () => {
  247. try {
  248. const id = state.info.file_id;
  249. const res = await apis.createMapElement(id);
  250. state.info.element_id = res.data.element_id;
  251. message.success('在地图上加载成功');
  252. } catch (e: any) {
  253. console.error(e);
  254. }
  255. }
  256. // 点击在地图上取消加载
  257. const onClickDeleteMapElement = async () => {
  258. try {
  259. const id = state.info.file_id;
  260. await apis.deleteMapElement(id);
  261. state.info.element_id = '';
  262. message.success('在地图上取消加载成功');
  263. } catch (e: any) {
  264. console.error(e);
  265. }
  266. }
  267. // 点击地图位置重置
  268. const onClickMapLocationReset = () => {
  269. const markerPosition = state.info.coordinates;
  270. const map: any = state.map;
  271. map.setCenter(markerPosition);
  272. map.setZoom(17);
  273. }
  274. // 点击滚动
  275. const onClickScroll = (type: 'LEFT' | 'RIGHT') => {
  276. const contentElement = contentRef.value;
  277. const scrollAmount = 80; // 滚动的像素数等于图片宽度
  278. if (type === 'LEFT') {
  279. contentElement.scrollLeft -= scrollAmount; // 向左滚动
  280. } else if (type === 'RIGHT') {
  281. contentElement.scrollLeft += scrollAmount; // 向右滚动
  282. }
  283. };
  284. // 点击选中
  285. const onClickSelected = async (id: string) => {
  286. state.currentId = id;
  287. state.downloadLoading = false;
  288. state.imgLoading = false;
  289. state.info = {
  290. url: '',
  291. media_type: 0,
  292. file_id: '',
  293. element_id: '',
  294. file_name: '',
  295. picture_type: '',
  296. task_name: '',
  297. wayline_name: '',
  298. image_width: '',// 照片分辨率-宽度
  299. image_height: '',// 照片分辨率-高度
  300. size: 0,
  301. payload: '',
  302. picture_time: '',
  303. longitude: '',// 经度
  304. latitude: '',// 纬度
  305. coordinates: [],
  306. }
  307. await init()
  308. }
  309. </script>
  310. <style lang="scss">
  311. .fileInfo {
  312. width: 100%;
  313. height: 100vh;
  314. background: #1C1C1C;
  315. color: #FFFFFF;
  316. position: absolute;
  317. top: 0;
  318. left: 0;
  319. z-index: 9;
  320. overflow: auto;
  321. ::-webkit-scrollbar {
  322. width: 8px;
  323. height: 8px;
  324. background: transparent;
  325. }
  326. ::-webkit-scrollbar-thumb {
  327. border-radius: 4px;
  328. border: none;
  329. background: rgb(89, 89, 89);
  330. }
  331. &-detail {
  332. display: flex;
  333. &-left {
  334. flex: 1;
  335. height: calc(100vh - 130px);
  336. overflow: hidden;
  337. &-background {
  338. width: 100%;
  339. height: 100%;
  340. padding: 100px;
  341. display: flex;
  342. justify-content: center;
  343. align-items: center;
  344. }
  345. }
  346. &-info {
  347. width: 420px;
  348. height: calc(100vh - 130px);
  349. padding: 24px;
  350. overflow: auto;
  351. &-close {
  352. font-size: 20px;
  353. position: absolute;
  354. right: 24px;
  355. top: 24px;
  356. cursor: pointer;
  357. }
  358. &-title {
  359. font-size: 16px;
  360. margin-bottom: 16px;
  361. }
  362. &-item {
  363. width: 100%;
  364. height: 38px;
  365. display: flex;
  366. align-items: center;
  367. &-title {
  368. width: 85px;
  369. color: hsla(0, 0%, 100%, .45);
  370. margin-right: 16px;
  371. }
  372. &-content {
  373. flex: 1;
  374. &-line {
  375. display: flex;
  376. align-items: center;
  377. }
  378. }
  379. }
  380. &-map {
  381. &-title {
  382. height: 38px;
  383. display: flex;
  384. justify-content: space-between;
  385. align-items: center;
  386. &-text {
  387. color: hsla(0, 0%, 100%, .45);
  388. }
  389. &-icon {
  390. color: $primary;
  391. }
  392. }
  393. &-content {
  394. width: 100%;
  395. height: 250px;
  396. background: #dddddd;
  397. border-radius: 4px;
  398. margin-top: 10px;
  399. position: relative;
  400. overflow: hidden;
  401. &-title {
  402. width: 100%;
  403. height: 28px;
  404. padding-left: 10px;
  405. background: rgba(0, 0, 0, .5);
  406. line-height: 28px;
  407. font-size: 14px;
  408. color: #fff;
  409. position: absolute;
  410. bottom: 0;
  411. left: 0;
  412. }
  413. }
  414. }
  415. }
  416. }
  417. &-area {
  418. width: 100%;
  419. height: 50px;
  420. display: flex;
  421. justify-content: center;
  422. align-items: center;
  423. }
  424. &-previewList {
  425. width: 100%;
  426. height: 60px;
  427. display: flex;
  428. justify-content: space-between;
  429. align-items: center;
  430. margin-bottom: 20px;
  431. &-content {
  432. width: calc(100vw - 80px);
  433. display: flex;
  434. overflow: auto;
  435. scrollbar-width: none;
  436. &-item {
  437. width: 80px;
  438. height: 60px;
  439. object-fit: cover;
  440. border: 2px solid transparent;
  441. border-radius: 4px;
  442. margin-right: 10px;
  443. cursor: pointer;
  444. &-selected {
  445. border-color: $primary;
  446. }
  447. }
  448. &-item:last-child {
  449. margin: 0;
  450. }
  451. }
  452. &-button {
  453. width: 30px;
  454. height: 60px;
  455. border: 1px solid #FFFFFF;
  456. display: flex;
  457. justify-content: center;
  458. align-items: center;
  459. cursor: pointer;
  460. }
  461. }
  462. }
  463. .amap-logo {
  464. display: none !important;
  465. }
  466. .amap-copyright {
  467. display: none !important;
  468. }
  469. </style>