Browse Source

feat: 无人机文件管理

pull/19/head
freshman-white 2 years ago
parent
commit
555c105699
  1. 5
      package.json
  2. 8
      src/App.vue
  3. 19
      src/api/media.ts
  4. 1
      src/assets/icons/folder.svg
  5. 1
      src/assets/icons/zip.svg
  6. 230
      src/components/MediaPanel.vue
  7. 2
      src/main.ts
  8. 72
      src/pages/page-web/projects/media.vue

5
package.json

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
"agora-rtc-sdk-ng": "^4.12.1",
"ant-design-vue": "^2.2.8",
"axios": "^0.21.1",
"dayjs": "^1.11.9",
"eventemitter3": "^5.0.0",
"mitt": "^3.0.0",
"mqtt": "^4.3.7",
@ -70,6 +71,7 @@ @@ -70,6 +71,7 @@
"ant-design-vue/es/checkbox/style/css",
"ant-design-vue/es/col/style/css",
"ant-design-vue/es/collapse/style/css",
"ant-design-vue/es/date-picker/locale/zh_CN",
"ant-design-vue/es/date-picker/style/css",
"ant-design-vue/es/divider/style/css",
"ant-design-vue/es/drawer/style/css",
@ -80,6 +82,8 @@ @@ -80,6 +82,8 @@
"ant-design-vue/es/input-number/style/css",
"ant-design-vue/es/input/style/css",
"ant-design-vue/es/layout/style/css",
"ant-design-vue/es/locale-provider/style/css",
"ant-design-vue/es/locale/zh_CN",
"ant-design-vue/es/menu/style/css",
"ant-design-vue/es/message/style/css",
"ant-design-vue/es/modal/style/css",
@ -100,6 +104,7 @@ @@ -100,6 +104,7 @@
"ant-design-vue/es/tree/style/css",
"ant-design-vue/es/upload/style/css",
"axios",
"dayjs",
"eventemitter3",
"lodash",
"mitt",

8
src/App.vue

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
<template>
<div class="demo-app">
<router-view />
<a-locale-provider :locale="zhCN">
<router-view />
</a-locale-provider>
<!-- <div class="map-wrapper">
<GMap/>
</div> -->
@ -11,14 +13,14 @@ @@ -11,14 +13,14 @@
import { computed, defineComponent, ref } from 'vue'
import { useMyStore } from './store'
import GMap from '/@/components/GMap.vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
export default defineComponent({
name: 'App',
components: { GMap },
setup () {
const store = useMyStore()
return {}
return { zhCN }
}
})
</script>

19
src/api/media.ts

@ -3,8 +3,13 @@ import request, { IPage, IWorkspaceResponse } from '/@/api/http/request' @@ -3,8 +3,13 @@ import request, { IPage, IWorkspaceResponse } from '/@/api/http/request'
const HTTP_PREFIX = '/media/api/v1'
// Get Media Files
export const getMediaFiles = async function (wid: string, pagination: IPage, pathId: string): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}&path_id=${pathId}`
export const getMediaFiles = async function (wid: string, pagination: IPage, pathId?: string): Promise<IWorkspaceResponse<any>> {
let url = ''
if (pathId) {
url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}&father_id=${pathId}`
} else {
url = `${HTTP_PREFIX}/files/${wid}/files?page=${pagination.page}&page_size=${pagination.page_size}`
}
const result = await request.get(url)
return result.data
}
@ -26,8 +31,8 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st @@ -26,8 +31,8 @@ export const downloadMediaFile = async function (workspaceId: string, fileId: st
}
// Get Media getFolder
export const getFolder = async function (workspaceId: string, type: boolean): Promise<IWorkspaceResponse<any>> {
const url = `${HTTP_PREFIX}/files/${workspaceId}/folder/${type}`
const result = await request.get(url)
return result.data
}
// export const getFolder = async function (workspaceId: string): Promise<IWorkspaceResponse<any>> {
// const url = `${HTTP_PREFIX}/files/${workspaceId}/folder/2`
// const result = await request.get(url)
// return result.data
// }

1
src/assets/icons/folder.svg

@ -0,0 +1 @@ @@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M21.594 19.842H2.903c-.5 0-.903-.359-.903-.803V5.932c0-.444.403-.802.903-.802h18.69c.5 0 .903.358.903.802V19.04c0 .442-.405.803-.902.803z" fill="#FFE9B4"/><path d="M12.26 9.009H2V4.404c0-.5.404-.904.904-.904h7.021c.4 0 .751.26.865.643l1.47 4.866z" fill="#FFA000"/><path d="M21.594 19.842H2.903A.902.902 0 0 1 2 18.94V7.272c0-.5.403-.903.903-.903h18.69c.5 0 .903.404.903.903v11.667a.905.905 0 0 1-.902.903z" fill="#FFCA28"/></g></svg>

After

Width:  |  Height:  |  Size: 540 B

1
src/assets/icons/zip.svg

@ -0,0 +1 @@ @@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M2 12V2.889c0-.49.398-.889.889-.889H21.11c.49 0 .889.398.889.889V12H2z" fill="#00C9FC"/><path d="M22 12v9.111c0 .49-.398.889-.889.889H2.89A.889.889 0 0 1 2 21.111V12h20z" fill="#5ED300"/><path fill="#FF5056" d="M2 9.332h20v5.334H2z"/><path fill="#FFAB00" d="M9.332 2h5.334v20H9.332z"/><path d="M14.445 14.444H9.556a.89.89 0 0 1-.89-.888v-3.112c0-.492.399-.889.89-.889h4.889c.49 0 .889.398.889.889v3.111a.89.89 0 0 1-.889.889zm-4-4a.89.89 0 0 0-.889.89v1.332c0 .492.398.89.889.89h3.111a.889.889 0 0 0 .889-.89v-1.333a.889.889 0 0 0-.889-.889h-3.111z" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 680 B

230
src/components/MediaPanel.vue

@ -1,26 +1,95 @@ @@ -1,26 +1,95 @@
<template>
<div class="header">Media Files</div>
<a-spin :spinning="loading" :delay="1000" tip="downloading" size="large">
<div class="header">
<a-button type="primary" large class="btn-primary" @click='createFile'>创建文件夹</a-button>
<div v-show="state.selectedRowIds.length" class="other-btn">
<a-button large class="btn-primary">压缩并下载</a-button>
<a-button large class="btn-primary">移动</a-button>
</div>
<div class="search-content">
<a-form>
<a-row :gutter="16">
<a-col :span="10">
<a-form-item name="timestamp">
<a-range-picker v-model:value="searchParam.timestamp" show-time/>
</a-form-item>
</a-col>
<a-col :span="7">
<a-form-item name="type">
<a-select placeholder="所有类型">
<a-select-option value="searchParam.type">所有类型</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="7">
<a-form-item name="type">
<a-select placeholder="所有负载">
<a-select-option value="searchParam.type">所有负载</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="7">
<a-form-item name="type">
<a-select placeholder="标签筛选">
<a-select-option value="searchParam.type">所有类型</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="7">
<a-form-item name="name">
<a-input v-model:value="searchParam.name" placeholder="按文件名称搜索">
<template #addonAfter>
<ZoomInOutlined />
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</div>
<a-spin :spinning="loading" tip="加载中..." size="large">
<div class="media-panel-wrapper">
<a-table class="media-table" :columns="columns" :data-source="mediaData.data" row-key="fingerprint"
:pagination="paginationProp" :scroll="{ x: '100%', y: 600 }" @change="refreshData">
<template v-for="col in ['name', 'path']" #[col]="{ text }" :key="col">
<a-tooltip :title="text">
<a v-if="col === 'name'">{{ text }}</a>
<span v-else>{{ text }}</span>
</a-tooltip>
</template>
<template #original="{ text }">
{{ text }}
<div class="bread-content">
<span v-for="(item,index) in breadList" :key="item">{{ item }}<span v-if="index!=breadList.length-1" >/</span></span>
</div>
<a-table :columns="columns" :data-source="mediaData.data" :scroll="{ x: '100%', y: 400 }" :row-selection="rowSelection" :pagination="paginationProp">
<template v-for="col in ['file_name']" #[col]="{ text,index }" :key="col">
<div @click="goDetail(mediaData.data[index])">
<img src="../assets/icons/folder.svg" v-if='mediaData.data[index].file_type==0'>
<img src="../assets/icons/zip.svg" v-if='mediaData.data[index].file_type==2'>
<a-image :width="30" :hight="30" v-if='mediaData.data[index].file_type==1' :src="mediaData.data[index].picture_url" :preview="{
src: mediaData.data[index].picture_url}"
/>
<a-tooltip :title="text">
<a>&nbsp;{{ text }}</a>
</a-tooltip>
</div>
</template>
<template #action="{ record }">
<a-tooltip title="download">
<a class="fz18" @click="downloadMedia(record)"><DownloadOutlined /></a>
</a-tooltip>
</template>
</a-table>
</template>
</a-table>
</div>
</a-spin>
<a-modal v-model:visible="state.folderOpen" :closable="false" :maskClosable="false">
<template #title>
<div ref="modalTitleRef" style="width: 100%; cursor: move" class="model-title">创建文件夹</div>
</template>
<a-input class="ml10"
v-model:value="state.fileName"
placeholder="请输入文件名"></a-input>
<template #footer>
<div>
<a-button large class="btn-primary" @click='state.folderOpen=false'>取消</a-button>
<a-button type="primary" large class="btn-primary" @click='state.folderOpen=true'>确定</a-button>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
@ -31,52 +100,46 @@ import { IPage } from '../api/http/type' @@ -31,52 +100,46 @@ import { IPage } from '../api/http/type'
import { ELocalStorageKey } from '../types/enums'
import { downloadFile } from '../utils/common'
import { downloadMediaFile, getMediaFiles } from '/@/api/media'
import { DownloadOutlined } from '@ant-design/icons-vue'
import {
ZoomInOutlined,
DownloadOutlined
} from '@ant-design/icons-vue'
import { message, Pagination } from 'ant-design-vue'
import { load } from '@amap/amap-jsapi-loader'
import EventBus from '/@/event-bus'
const workspaceId = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
const loading = ref(false)
const pathId = ref('')
const searchParam = reactive({ timestamp: [], type: [], name: '' })
const state = reactive({ selectedRowIds: [], folderOpen: false, fileName: '' })
const breadList = ref(['全部文件'])
const breadNum = ref(0)
const columns = [
{
title: 'File Name',
title: '文件名称',
dataIndex: 'file_name',
ellipsis: true,
slots: { customRender: 'name' }
slots: { customRender: 'file_name' }
},
{
title: 'File Path',
dataIndex: 'file_path',
title: '拍摄负载',
dataIndex: 'payload',
ellipsis: true,
slots: { customRender: 'path' }
},
// {
// title: 'FileSize',
// dataIndex: 'size',
// },
{
title: 'Drone',
title: '大小',
dataIndex: 'drone'
},
{
title: 'Payload Type',
dataIndex: 'payload'
},
{
title: 'Original',
dataIndex: 'is_original',
slots: { customRender: 'original' }
},
{
title: 'Created',
title: '创建时间',
dataIndex: 'create_time'
},
{
title: 'Action',
title: '操作',
key: 'operation',
fixed: 'right',
width: 100,
slots: { customRender: 'action' }
}
]
const body: IPage = {
@ -88,8 +151,8 @@ const paginationProp = reactive({ @@ -88,8 +151,8 @@ const paginationProp = reactive({
pageSizeOptions: ['20', '50', '100'],
showQuickJumper: true,
showSizeChanger: true,
pageSize: 50,
current: 1,
page_size: 50,
page: 1,
total: 0
})
@ -111,20 +174,18 @@ const mediaData = reactive({ @@ -111,20 +174,18 @@ const mediaData = reactive({
})
onMounted(() => {
//
EventBus.on('getFolderFiles', (val) => {
pathId.value = val
getFiles()
})
// pathId.value = val
getFiles()
})
function getFiles () {
getMediaFiles(workspaceId, body, pathId.value).then(res => {
loading.value = true
getMediaFiles(workspaceId, paginationProp).then(res => {
mediaData.data = res.data.list
paginationProp.total = res.data.pagination.total
paginationProp.current = res.data.pagination.page
console.info(mediaData.data[0])
paginationProp.page = res.data.pagination.page
}).finally(() => {
loading.value = false
})
}
@ -147,29 +208,82 @@ function downloadMedia (media: MediaFile) { @@ -147,29 +208,82 @@ function downloadMedia (media: MediaFile) {
})
}
const rowSelection = {
onChange: (selectedRowKeys:any, selectedRows:any) => {
state.selectedRowIds = selectedRows.map((item:any) => item.father_id)
},
}
const createFile = () => {
state.folderOpen = true
}
const goDetail = (param:any) => {
if (param.file_type !== 0) { return }
loading.value = true
breadNum.value++
breadList.value[breadNum.value] = param.file_name
getMediaFiles(workspaceId, paginationProp, param.father_id).then(res => {
mediaData.data = res.data.list
paginationProp.total = res.data.pagination.total
paginationProp.page = res.data.pagination.page
}).finally(() => {
loading.value = false
})
}
</script>
<style lang="scss" scoped>
.media-panel-wrapper {
width: 100%;
padding: 16px;
.bread-content{
background: #fff;
padding: 16px;
}
.media-table {
background: #fff;
margin-top: 10px;
}
.action-area {
color: $primary;
cursor: pointer;
}
}
.video-img{
width:25px;
height: 25px;
margin-right: 8px;
}
.header {
width: 100%;
height: 60px;
background: #fff;
padding: 16px;
font-size: 20px;
font-weight: bold;
text-align: start;
padding: 16px 16px 0 16px;
color: #000;
.other-btn{
display: inline-block;
}
&::v-deep{
.ant-btn{
margin-right: 8px !important;
}
.ant-calendar-picker{
width:100% !important;
}
.ant-form-item{
margin-bottom: 8px !important;
}
}
.btn-primary{
margin-bottom: 16px;
}
}
.model-title{
text-align: center;
border: none;
}
::v-deep{
.ant-modal-header{
padding: 0 0 !important;
}
}
</style>

2
src/main.ts

@ -5,6 +5,7 @@ import { CommonComponents } from './use-common-components' @@ -5,6 +5,7 @@ import { CommonComponents } from './use-common-components'
import 'virtual:svg-icons-register'
import store, { storeKey } from './store'
import { createInstance } from '/@/root'
import dayjs from 'dayjs'
import '/@/styles/index.scss'
const app = createInstance(App)
app.use(store, storeKey)
@ -12,3 +13,4 @@ app.use(router) @@ -12,3 +13,4 @@ app.use(router)
app.use(CommonComponents)
app.use(antComponents)
app.mount('#demo-app')
app.config.globalProperties.day = dayjs

72
src/pages/page-web/projects/media.vue

@ -1,36 +1,40 @@ @@ -1,36 +1,40 @@
<template>
<div class="folder-herder">
<a-radio-group @change="changeType" v-model:value="typeValue" button-style="solid">
<a-radio-button class="radio-button" :value="true"
>机场文件</a-radio-button
>
<a-radio-button class="radio-button" :value="false"
>普通文件</a-radio-button
<div>
<!-- 根据需求这部分先注释 -->
<!-- <div class="folder-herder">
<a-radio-group @change="changeType" v-model:value="typeValue" button-style="solid">
<a-radio-button class="radio-button" :value="true"
>机场文件</a-radio-button
>
<a-radio-button class="radio-button" :value="false"
>普通文件</a-radio-button
>
</a-radio-group>
</div>
<div class="height-100 project-media-wrapper">
<div
class="folder-container"
v-for="(item, key) in folderData.data"
:key="key"
@click="selectFolder(key)"
>
</a-radio-group>
</div>
<div class="height-100 project-media-wrapper">
<div
class="folder-container"
v-for="(item, key) in folderData.data"
:key="key"
@click="selectFolder(key)"
>
<a-tag color="blue" v-if="folderData.id === item.id">now</a-tag>
<div>
<div class="title">{{ item.name }}</div>
<div style="font-size: 13px; margin-left: 12px">
时间-{{ item.time }}
<a-tag color="blue" v-if="folderData.id === item.id">now</a-tag>
<div>
<div class="title">{{ item.name }}</div>
<div style="font-size: 13px; margin-left: 12px">
时间-{{ item.time }}
</div>
</div>
</div>
</div>
</div> -->
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'
import { ELocalStorageKey } from '/@/types/enums'
import { getFolder } from '/@/api/media'
// import { getFolder } from '/@/api/media'
import EventBus from '/@/event-bus'
const workspaceId: string = localStorage.getItem(ELocalStorageKey.WorkspaceId)!
@ -41,21 +45,21 @@ const folderData = reactive<any>({ @@ -41,21 +45,21 @@ const folderData = reactive<any>({
id: null,
})
onMounted(() => {
getFolderArr()
})
// onMounted(() => {
// getFolderArr()
// })
async function getFolderArr () {
folderData.data = []
const result = await getFolder(workspaceId, typeValue.value)
folderData.id = result.data[0].id
folderData.data = result.data
loadFiles()
}
// async function getFolderArr () {
// folderData.data = []
// const result = await getFolder(workspaceId)
// folderData.id = result.data[0].id
// folderData.data = result.data
// loadFiles()
// }
function changeType (val: any) {
typeValue.value = val.target.value
getFolderArr()
// getFolderArr()
}
function selectFolder (index: number) {

Loading…
Cancel
Save