index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. <template>
  2. <a-spin :spinning="state.downloadLoading" tip="下载中...">
  3. <div class="mediaDetail">
  4. <Search :fetchList="fetchList" :selectedRowKeys="state.selectedRowKeys" :onClickDownload="onClickBatchDownload"
  5. :onClickDelete="onClickBatchDelete" :onClickSearch="onClickSearch" :onClickReset="onClickReset" />
  6. <div style="background: #FFFFFF;padding: 20px;">
  7. <div class="mediaDetail-info">
  8. <div class="mediaDetail-info-left">
  9. <div style="color: #7C7C7C;cursor: pointer;" @click="onClickGoBack">
  10. 媒体文件
  11. </div>
  12. <div style="margin: 0 10px;">
  13. /
  14. </div>
  15. <div>
  16. {{ state.dirName }}
  17. </div>
  18. </div>
  19. <div class="mediaDetail-info-right">
  20. <div class="mediaDetail-info-right-text">
  21. <div>
  22. 已选/全部:
  23. </div>
  24. <div>
  25. {{ state.selectedRowKeys.length }}/{{ paginationConfig.total }}
  26. </div>
  27. </div>
  28. </div>
  29. </div>
  30. <a-table :scroll="{ x: '100%', y: 500 }" rowKey="file_id" :loading="state.listLoading" :columns="columns"
  31. :dataSource="state.list" :rowClassName="rowClassName" :pagination="paginationConfig"
  32. :rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }">
  33. <!-- 文件夹名称 -->
  34. <template #file_name="{ record }">
  35. <a-tooltip :title="record.file_name">
  36. <div class="fileName">
  37. <div class="fileName-cover" @click="onClickLookFile(record)">
  38. <img class="fileName-cover-img" :src="record.thumbnail_url" />
  39. <img class="fileName-cover-icon" :src="panoramaSrc" v-if="record.media_type === 3" />
  40. <img class="fileName-cover-icon" :src="videoSrc" v-else-if="record.media_type === 4" />
  41. </div>
  42. <div style="margin-left: 5px;">
  43. <a-input v-model:value="record.file_name" v-if="state.editableData[record.file_id]" />
  44. <div @click="onClickLookFile(record)" v-else>
  45. {{ record.file_name }}
  46. </div>
  47. </div>
  48. </div>
  49. </a-tooltip>
  50. </template>
  51. <!-- 操作 -->
  52. <template #action="{ record }">
  53. <!-- 编辑态操作 -->
  54. <div v-if="state.editableData[record.file_id]">
  55. <a-tooltip title="确定">
  56. <CheckOutlined style="color: #28d445;margin-right: 10px;" @click="onClickSave(record)" />
  57. </a-tooltip>
  58. <a-tooltip title="取消">
  59. <CloseOutlined style="color: #e70102;" @click="() => delete state.editableData[record.file_id]" />
  60. </a-tooltip>
  61. </div>
  62. <!-- 非编辑态操作 -->
  63. <div class="flex-align-center flex-row" style="color: #2d8cf0" v-else>
  64. <div v-if="[1, 3].includes(record.media_type)">
  65. <a-tooltip title="在地图上加载" v-if="!record.element_id">
  66. <AimOutlined style="margin-right: 10px;" @click="onClickCreateMapElement(record.file_id)" />
  67. </a-tooltip>
  68. <a-tooltip title="在地图上取消加载" v-else>
  69. <AimOutlined style="color: #e70102;margin-right: 10px;"
  70. @click="onClickDeleteMapElement(record.file_id)" />
  71. </a-tooltip>
  72. </div>
  73. <a-tooltip title="重命名" v-else-if="record.media_type === 4">
  74. <EditOutlined style="margin-right: 10px;" @click="onClickRechristen(record)" />
  75. </a-tooltip>
  76. <a-tooltip title="删除">
  77. <DeleteOutlined style="margin-right: 10px;" @click="onClickDelete(record)" />
  78. </a-tooltip>
  79. <a-tooltip title="压缩下载">
  80. <DownloadOutlined @click="onClickDownload(record)" />
  81. </a-tooltip>
  82. </div>
  83. </template>
  84. </a-table>
  85. </div>
  86. </div>
  87. </a-spin>
  88. <FileInfo :fileId="state.fileId" :fileList="state.list" :onClose="fileInfoOnClickClose"
  89. v-if="state.fileInfoVisible" />
  90. </template>
  91. <script lang="ts" setup>
  92. import { reactive, onMounted } from 'vue';
  93. import { message, Modal } from 'ant-design-vue';
  94. import { EditOutlined, DeleteOutlined, DownloadOutlined, AimOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
  95. import Search from './components/Search.vue';
  96. import FileInfo from './components/FileInfo.vue';
  97. import panoramaSrc from '/@/assets/media/panorama.svg';
  98. import videoSrc from '/@/assets/media/video.svg';
  99. import { apis } from '/@/api/custom';
  100. import router from '/@/router/index';
  101. import { downloadFile } from '/@/utils/common';
  102. import { downloadMediaFile } from '/@/api/media';
  103. import { getWorkspaceId } from '/@/utils/index';
  104. interface State {
  105. query: any,
  106. listLoading: boolean,
  107. list: any[],
  108. dirName: string,
  109. selectedRowKeys: string[],
  110. editableData: {
  111. [key: string]: string,
  112. },
  113. downloadLoading: boolean,
  114. fileId: string,
  115. fileInfoVisible: boolean,
  116. };
  117. const state: State = reactive({
  118. query: undefined,
  119. listLoading: false,
  120. list: [],
  121. dirName: '',
  122. selectedRowKeys: [],
  123. editableData: {},
  124. downloadLoading: false,
  125. fileId: '',
  126. fileInfoVisible: false,
  127. })
  128. const paginationConfig = reactive({
  129. pageSizeOptions: ['20', '50', '100'],
  130. showQuickJumper: true,
  131. showSizeChanger: true,
  132. pageSize: 100,
  133. current: 1,
  134. total: 0,
  135. onChange: async (current: number) => {
  136. paginationConfig.current = current;
  137. await fetchList();
  138. },
  139. onShowSizeChange: async (current: number, pageSize: number) => {
  140. paginationConfig.pageSize = pageSize;
  141. await fetchList();
  142. }
  143. })
  144. const fetchList = async () => {
  145. state.listLoading = true;
  146. try {
  147. const dirId = router.currentRoute.value.params?.id;
  148. const res = await apis.fetchFileListByFolder(dirId as string, {
  149. ...state.query,
  150. page: paginationConfig.current,
  151. page_size: paginationConfig.pageSize,
  152. });
  153. state.dirName = res.data.group_name;
  154. state.list = res.data.list;
  155. paginationConfig.current = res.data.pagination.page;
  156. paginationConfig.pageSize = res.data.pagination.page_size;
  157. paginationConfig.total = res.data.pagination.total;
  158. } catch (e) {
  159. console.error(e);
  160. } finally {
  161. state.listLoading = false;
  162. }
  163. }
  164. onMounted(async () => {
  165. await fetchList();
  166. })
  167. const columns = [
  168. {
  169. title: '文件名称',
  170. dataIndex: 'file_name',
  171. width: 250,
  172. ellipsis: true,
  173. sorter: (a: any, b: any) => a.file_name.localeCompare(b.file_name),
  174. slots: { customRender: 'file_name' }
  175. },
  176. {
  177. title: '拍摄负载',
  178. dataIndex: 'payload',
  179. width: 150,
  180. ellipsis: true,
  181. },
  182. {
  183. title: '容量大小',
  184. dataIndex: 'size',
  185. width: 150,
  186. ellipsis: true,
  187. customRender: ({ text }: any) => {
  188. return text > 0 ? (text / 1024 / 1024).toFixed(1) + 'M' : '--';
  189. }
  190. },
  191. {
  192. title: '创建时间',
  193. dataIndex: 'create_time',
  194. width: 200,
  195. sorter: (a: any, b: any) => a.create_time.localeCompare(b.create_time),
  196. },
  197. {
  198. title: '媒体类型',
  199. dataIndex: 'media_type_text',
  200. width: 100,
  201. customRender: ({ text }: any) => {
  202. return text || '--';
  203. }
  204. },
  205. {
  206. title: '文件类型',
  207. dataIndex: 'picture_type',
  208. width: 100,
  209. customRender: ({ text }: any) => {
  210. return text || '--';
  211. }
  212. },
  213. {
  214. title: '操作',
  215. dataIndex: 'actions',
  216. fixed: 'right',
  217. width: 100,
  218. slots: { customRender: 'action' },
  219. },
  220. ]
  221. const rowClassName = (record: any, index: number) => {
  222. const className = []
  223. if ((index & 1) === 0) {
  224. className.push('table-striped')
  225. }
  226. return className.toString().replaceAll(',', ' ')
  227. }
  228. // 点击批量下载
  229. const onClickBatchDownload = async () => {
  230. state.downloadLoading = true;
  231. try {
  232. const bold = await apis.batchDownloadFile({
  233. id: state.selectedRowKeys.join(',')
  234. });
  235. const data = new Blob([bold], { type: 'application/zip' });
  236. downloadFile(data, state.dirName + '.zip');
  237. } catch (e) {
  238. console.error(e);
  239. } finally {
  240. state.downloadLoading = false;
  241. }
  242. }
  243. // 点击批量删除
  244. const onClickBatchDelete = async () => {
  245. const canDeleteList = state.list.filter(item => state.selectedRowKeys.includes(item.file_id));
  246. const data = {
  247. id: canDeleteList.map(item => item.file_id).join(',')
  248. }
  249. Modal.confirm({
  250. title: '删除',
  251. content: '您确认删除文件吗?',
  252. okType: 'danger',
  253. onOk: async () => {
  254. try {
  255. const res = await apis.batchDeletePicture(data);
  256. if (res.code === 0) {
  257. await fetchList();
  258. message.success('删除成功');
  259. }
  260. } catch (error) {
  261. message.error('删除失败: ' + error);
  262. }
  263. },
  264. })
  265. }
  266. // 点击搜索
  267. const onClickSearch = async (query: any) => {
  268. state.query = query;
  269. await fetchList();
  270. }
  271. // 点击重置
  272. const onClickReset = async (query: any) => {
  273. state.query = query;
  274. await fetchList();
  275. }
  276. // 点击返回
  277. const onClickGoBack = () => {
  278. router.push({ path: '/media' })
  279. }
  280. // 勾选
  281. const onSelectChange = (selectedRowKeys: string[]) => {
  282. state.selectedRowKeys = selectedRowKeys;
  283. }
  284. // 点击查看文件
  285. const onClickLookFile = (record: any) => {
  286. state.fileId = record.file_id;
  287. state.fileInfoVisible = true;
  288. }
  289. // 文件信息-点击关闭
  290. const fileInfoOnClickClose = async () => {
  291. state.fileId = '';
  292. state.fileInfoVisible = false;
  293. await fetchList();
  294. }
  295. // 点击在地图上加载
  296. const onClickCreateMapElement = async (id: string) => {
  297. try {
  298. await apis.createMapElement(id);
  299. await fetchList();
  300. message.success('在地图上加载成功');
  301. } catch (e: any) {
  302. console.error(e);
  303. }
  304. }
  305. // 点击在地图上取消加载
  306. const onClickDeleteMapElement = async (id: string) => {
  307. try {
  308. await apis.deleteMapElement(id);
  309. await fetchList();
  310. message.success('在地图上取消加载成功');
  311. } catch (e: any) {
  312. console.error(e);
  313. }
  314. }
  315. // 点击重命名
  316. const onClickRechristen = (record: any) => {
  317. state.editableData[record.file_id] = record;
  318. }
  319. // 点击保存
  320. const onClickSave = async (record: any) => {
  321. delete state.editableData[record.file_id];
  322. await apis.updateFileName(record.file_id, { file_name: record.file_name })
  323. await fetchList();
  324. }
  325. // 点击删除
  326. const onClickDelete = async (record: any) => {
  327. console.log(record, "record");
  328. const data = {
  329. id: record.file_id
  330. }
  331. Modal.confirm({
  332. title: '删除',
  333. content: '您确认删除文件吗?',
  334. okType: 'danger',
  335. onOk: async () => {
  336. try {
  337. const res = await apis.batchDeletePicture(data);
  338. if (res.code === 0) {
  339. await fetchList();
  340. message.success('删除成功');
  341. }
  342. } catch (error) {
  343. message.error('删除失败: ' + error);
  344. }
  345. },
  346. })
  347. }
  348. // 点击下载
  349. const onClickDownload = async (record: any) => {
  350. state.downloadLoading = true;
  351. try {
  352. const workspaceId: string = getWorkspaceId();
  353. const res = await downloadMediaFile(workspaceId, record.file_id)
  354. if (!res) {
  355. return
  356. }
  357. const data = new Blob([res])
  358. downloadFile(data, record.file_name)
  359. } catch (e) {
  360. console.error(e);
  361. } finally {
  362. state.downloadLoading = false;
  363. }
  364. }
  365. </script>
  366. <style lang="scss">
  367. .mediaDetail {
  368. padding: 20px;
  369. &-info {
  370. display: flex;
  371. justify-content: space-between;
  372. align-items: center;
  373. margin-bottom: 20px;
  374. &-left {
  375. display: flex;
  376. align-items: center;
  377. }
  378. &-right {
  379. display: flex;
  380. align-items: center;
  381. &-text {
  382. display: flex;
  383. align-items: center;
  384. margin-right: 20px;
  385. }
  386. }
  387. }
  388. .fileName {
  389. display: flex;
  390. align-items: center;
  391. cursor: pointer;
  392. &-cover {
  393. position: relative;
  394. &-img {
  395. width: 36px;
  396. height: 36px;
  397. }
  398. &-icon {
  399. width: 20px;
  400. height: 20px;
  401. position: absolute;
  402. z-index: 2;
  403. top: 50%;
  404. left: 50%;
  405. transform: translate(-50%, -50%);
  406. }
  407. }
  408. }
  409. }
  410. .ant-table {
  411. border-top: 1px solid rgb(0, 0, 0, 0.06);
  412. border-bottom: 1px solid rgb(0, 0, 0, 0.06);
  413. }
  414. .ant-table-tbody tr td {
  415. border: 0;
  416. }
  417. .table-striped {
  418. background-color: #f7f9fa;
  419. }
  420. .ant-card-body {
  421. padding: 0 10px 10px;
  422. }
  423. </style>