|
|
@@ -1,10 +1,16 @@
|
|
|
<template>
|
|
|
<div class="home-container">
|
|
|
<div class="header-section">
|
|
|
+ <div class="breadcrumb-bar" v-if="$route.query.path && $route.query.path !== '/'">
|
|
|
+ <van-icon name="folder-o" />
|
|
|
+ <span class="path-text">{{ $route.query.path }}</span>
|
|
|
+ <span class="back-btn" @click="goBack">返回上一级</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
<van-nav-bar fixed placeholder z-index="10">
|
|
|
<template #left>
|
|
|
<van-icon
|
|
|
- v-if="$route.query.folderId"
|
|
|
+ v-if="$route.query.path"
|
|
|
name="arrow-left"
|
|
|
size="20"
|
|
|
@click="goBack"
|
|
|
@@ -18,7 +24,7 @@
|
|
|
</template>
|
|
|
</van-nav-bar>
|
|
|
|
|
|
- <div class="breadcrumb-bar" v-if="$route.query.folderId">
|
|
|
+ <div class="breadcrumb-bar" v-if="$route.query.path">
|
|
|
<span class="path-text">当前目录: {{ currentFolderName }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -33,19 +39,19 @@
|
|
|
>
|
|
|
<div
|
|
|
v-for="item in fileList"
|
|
|
- :key="item.id"
|
|
|
+ :key="item.fileId"
|
|
|
class="file-item-card"
|
|
|
@click="handleItemClick(item)"
|
|
|
>
|
|
|
<div class="file-icon">
|
|
|
<van-icon
|
|
|
- :name="getFileIcon(item.type)"
|
|
|
- :style="{ color: getIconColor(item.type) }"
|
|
|
+ :name="getFileIcon(item.fileTypeStr)"
|
|
|
+ :style="{ color: getIconColor(item.fileTypeStr) }"
|
|
|
class="icon-size-custom"
|
|
|
/>
|
|
|
</div>
|
|
|
<div class="file-info van-hairline--bottom">
|
|
|
- <div class="file-name van-ellipsis">{{ item.name }}</div>
|
|
|
+ <div class="file-name van-ellipsis">{{ item.filename }}</div>
|
|
|
<div class="file-meta">{{ item.updateTime || '2024-05-09' }} · {{ item.size }}</div>
|
|
|
</div>
|
|
|
<div class="file-action" @click.stop="showActionSheet(item)">
|
|
|
@@ -87,10 +93,31 @@
|
|
|
accept="*"
|
|
|
/>
|
|
|
|
|
|
- <van-popup v-model="showPlayerModal" position="bottom" :style="{ height: '100%' }" closeable>
|
|
|
+ <van-popup
|
|
|
+ v-model="showPlayerModal"
|
|
|
+ position="bottom"
|
|
|
+ :style="{ height: '100%' }"
|
|
|
+ closeable
|
|
|
+ @closed="handleVideoClose"
|
|
|
+ >
|
|
|
<div v-if="currentPlayItem !== null" class="player-container">
|
|
|
- <div class="player-header">{{ currentPlayItem.name }}</div>
|
|
|
- <div id="dplayer" style="height: 300px"></div>
|
|
|
+ <div class="player-header van-ellipsis">{{ currentPlayItem.filename }}</div>
|
|
|
+
|
|
|
+ <div class="video-wrapper">
|
|
|
+ <video
|
|
|
+ ref="videoPlayer"
|
|
|
+ :src="currentPlayItem.url"
|
|
|
+ controls
|
|
|
+ autoplay
|
|
|
+ playsinline
|
|
|
+ webkit-playsinline
|
|
|
+ x5-video-player-type="h5-page"
|
|
|
+ class="native-video"
|
|
|
+ >
|
|
|
+ 您的浏览器不支持视频播放。
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="player-info">
|
|
|
<van-cell title="文件大小" :value="currentPlayItem.size" />
|
|
|
<van-cell title="修改时间" :value="currentPlayItem.updateTime" />
|
|
|
@@ -101,12 +128,17 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import DPlayer from 'dplayer'
|
|
|
import { ImagePreview } from 'vant'
|
|
|
+import { getDiskFile, getFileDetail } from '@/api/disk'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
+ queryForm: {
|
|
|
+ pn: 1,
|
|
|
+ path: '/',
|
|
|
+ fileType: null
|
|
|
+ },
|
|
|
fileList: [],
|
|
|
loading: false,
|
|
|
finished: false,
|
|
|
@@ -134,50 +166,69 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
|
- // 仅监听 folderId 的变化(从一个目录跳到另一个目录)
|
|
|
- '$route.query.folderId'(newVal) {
|
|
|
- this.resetAndReload()
|
|
|
+ // 仅监听 path 的变化(从一个目录跳到另一个目录)
|
|
|
+ '$route.query.path': {
|
|
|
+ handler(newVal) {
|
|
|
+ this.queryForm.path = newVal || '/'
|
|
|
+ this.resetAndReload()
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
- mounted() {
|
|
|
- // 确保初次进入页面时执行加载
|
|
|
- this.onLoad()
|
|
|
- },
|
|
|
methods: {
|
|
|
resetAndReload() {
|
|
|
this.fileList = []
|
|
|
this.pagination.page = 1
|
|
|
this.finished = false
|
|
|
- this.loading = true // 显示转圈
|
|
|
+ this.loading = true
|
|
|
this.onLoad()
|
|
|
},
|
|
|
|
|
|
async onLoad() {
|
|
|
- // 防止重复加载
|
|
|
if (this.refreshing) return
|
|
|
-
|
|
|
+ const currentPath = this.$route.query.path || '/'
|
|
|
try {
|
|
|
- const folderId = this.$route.query.folderId || 'root'
|
|
|
- const response = await this.fetchFilesFromServer(folderId, this.pagination)
|
|
|
-
|
|
|
- // 如果是第一页,直接赋值;否则追加
|
|
|
- if (this.pagination.page === 1) {
|
|
|
- this.fileList = response.items
|
|
|
- } else {
|
|
|
- this.fileList.push(...response.items)
|
|
|
+ // 构造后端需要的参数
|
|
|
+ const params = {
|
|
|
+ path: currentPath,
|
|
|
+ pn: this.pagination.page // 注意:通常后端分页参数名为 pn
|
|
|
+ // fileType: this.queryForm.fileType
|
|
|
}
|
|
|
|
|
|
- this.loading = false
|
|
|
+ const resp = await getDiskFile(params)
|
|
|
+ if (resp.code === 0) {
|
|
|
+ const { pageList, namePathList } = resp.data
|
|
|
+
|
|
|
+ // 1. 处理文件列表
|
|
|
+ const items = pageList.list
|
|
|
+ if (this.pagination.page === 1) {
|
|
|
+ this.fileList = items
|
|
|
+ } else {
|
|
|
+ this.fileList.push(...items)
|
|
|
+ }
|
|
|
|
|
|
- if (this.fileList.length >= response.total) {
|
|
|
+ // 2. 更新面包屑/标题 (假设 namePathList 是当前路径的拆解)
|
|
|
+ if (namePathList && namePathList.length > 0) {
|
|
|
+ this.currentFolderName = namePathList[namePathList.length - 1].filename
|
|
|
+ } else {
|
|
|
+ this.currentFolderName = '我的文件'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 判断是否加载完成
|
|
|
+ this.loading = false
|
|
|
this.finished = true
|
|
|
+ if (this.fileList.length >= pageList.totalCount) {
|
|
|
+ this.finished = true
|
|
|
+ } else {
|
|
|
+ this.pagination.page++
|
|
|
+ }
|
|
|
} else {
|
|
|
- this.pagination.page++
|
|
|
+ this.$toast(resp.msg)
|
|
|
+ this.finished = true
|
|
|
}
|
|
|
} catch (error) {
|
|
|
this.loading = false
|
|
|
this.finished = true
|
|
|
- this.$toast('数据加载失败')
|
|
|
+ this.$toast('加载失败')
|
|
|
}
|
|
|
},
|
|
|
// 获取图标名称 (对应 Vant 的内置图标名)
|
|
|
@@ -218,38 +269,6 @@ export default {
|
|
|
// 触发加载逻辑
|
|
|
this.onLoad()
|
|
|
},
|
|
|
- async onLoad() {
|
|
|
- try {
|
|
|
- // 如果是在下拉刷新,则需要清空旧数据
|
|
|
- if (this.refreshing) {
|
|
|
- this.fileList = []
|
|
|
- this.refreshing = false
|
|
|
- }
|
|
|
-
|
|
|
- const folderId = this.$route.query.folderId || 'root'
|
|
|
-
|
|
|
- // 模拟 API 请求
|
|
|
- const response = await this.fetchFilesFromServer(folderId, this.pagination)
|
|
|
-
|
|
|
- // 将新获取的数据追加到当前列表
|
|
|
- this.fileList.push(...response.items)
|
|
|
-
|
|
|
- // 加载状态结束
|
|
|
- this.loading = false
|
|
|
-
|
|
|
- // 判断是否已经全部加载完
|
|
|
- if (this.fileList.length >= this.totalCount) {
|
|
|
- this.finished = true
|
|
|
- } else {
|
|
|
- // 如果还没加载完,页码 +1,为下次滚动做准备
|
|
|
- this.pagination.page++
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- this.loading = false
|
|
|
- this.finished = true
|
|
|
- this.$toast('数据加载失败')
|
|
|
- }
|
|
|
- },
|
|
|
onUploadSelect(action) {
|
|
|
this.showUploadSheet = false // 先关闭菜单
|
|
|
|
|
|
@@ -282,7 +301,7 @@ export default {
|
|
|
// 构建 FormData (后端通用格式)
|
|
|
const formData = new FormData()
|
|
|
formData.append('file', item.file)
|
|
|
- formData.append('folderId', this.$route.query.folderId || 'root')
|
|
|
+ formData.append('folderId', this.$route.query.path || 'root')
|
|
|
|
|
|
try {
|
|
|
// 这里替换为你真实的 Axios 请求
|
|
|
@@ -330,44 +349,7 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- fetchFilesFromServer(folderId, params) {
|
|
|
- return new Promise((resolve) => {
|
|
|
- setTimeout(() => {
|
|
|
- const mockNames = [
|
|
|
- { name: '复仇者联盟4.mp4', type: 'video', size: '2.4GB' },
|
|
|
- { name: '2024工作计划.mp4', type: 'audio', size: '1.2MB' },
|
|
|
- { name: '项目源码_最终版.zip', type: 'file', size: '45.8MB' },
|
|
|
- { name: '度假照片合集', type: 'folder', size: '-' },
|
|
|
- { name: 'Vue3入门教程.jpg', type: 'image', size: '890MB' },
|
|
|
- { name: '账单明细.text', type: 'text', size: '156KB' },
|
|
|
- { name: '壁纸图库', type: 'folder', size: '-' },
|
|
|
- { name: '系统补丁.exe', type: 'file', size: '12MB' }
|
|
|
- ]
|
|
|
-
|
|
|
- const items = Array.from({ length: params.limit }).map((_, index) => {
|
|
|
- // 随机取一个模板
|
|
|
- const template = mockNames[Math.floor(Math.random() * mockNames.length)]
|
|
|
- // 加上唯一 ID 防止 Key 冲突
|
|
|
- const id = `${folderId}-${params.page}-${index}`
|
|
|
-
|
|
|
- return {
|
|
|
- id: id,
|
|
|
- name: params.page === 1 && index < 2 ? `演示-${template.name}` : template.name,
|
|
|
- type: template.type,
|
|
|
- size: template.size,
|
|
|
- updateTime: '2024-05-09 14:20'
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- resolve({
|
|
|
- items: items,
|
|
|
- total: 60 // 模拟总共有 60 条数据
|
|
|
- })
|
|
|
- }, 1000) // 模拟 1 秒网络延迟
|
|
|
- })
|
|
|
- },
|
|
|
|
|
|
- // 补全缺失的 handleCreateFolder
|
|
|
handleCreateFolder() {
|
|
|
this.$dialog
|
|
|
.confirm({
|
|
|
@@ -383,91 +365,113 @@ export default {
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- // 补全 goBack
|
|
|
goBack() {
|
|
|
- this.$router.back()
|
|
|
+ const currentPath = this.$route.query.path || '/'
|
|
|
+ if (currentPath === '/') return
|
|
|
+
|
|
|
+ // 找到最后一个斜杠的位置
|
|
|
+ // 例如:/home/movies -> /home
|
|
|
+ // 例如:/home -> /
|
|
|
+ const pathArray = currentPath.split('/').filter((p) => p !== '')
|
|
|
+ pathArray.pop() // 移除最后一项
|
|
|
+ const parentPath = '/' + pathArray.join('/')
|
|
|
+
|
|
|
+ this.$router.push({
|
|
|
+ path: '/disk/file',
|
|
|
+ query: { path: parentPath }
|
|
|
+ })
|
|
|
},
|
|
|
|
|
|
handleItemClick(item) {
|
|
|
- if (item.type === 'folder') {
|
|
|
- this.$router
|
|
|
- .push({
|
|
|
- path: '/disk',
|
|
|
- query: { folderId: item.id }
|
|
|
- })
|
|
|
- .catch(() => {})
|
|
|
+ if (item.fileTypeStr === 'folder' || item.folder) {
|
|
|
+ // 兼容后端字段
|
|
|
+ // 获取当前路径并确保结尾有斜杠
|
|
|
+ const currentPath = this.$route.query.path || '/'
|
|
|
+ const separator = currentPath.endsWith('/') ? '' : '/'
|
|
|
+
|
|
|
+ // 拼接成:/old-path/new-folder
|
|
|
+ const newPath = `${currentPath}${separator}${item.filename}`
|
|
|
+
|
|
|
+ this.$router.push({
|
|
|
+ path: '/disk/file',
|
|
|
+ query: { path: newPath }
|
|
|
+ })
|
|
|
} else {
|
|
|
- // 根据类型进行预览
|
|
|
- switch (item.type) {
|
|
|
- case 'image':
|
|
|
- this.previewImage(item)
|
|
|
- break
|
|
|
- case 'video':
|
|
|
- this.previewVideo(item)
|
|
|
- break
|
|
|
- case 'audio':
|
|
|
- this.previewAudio(item)
|
|
|
- break
|
|
|
- case 'text':
|
|
|
- this.previewText(item)
|
|
|
- break
|
|
|
- default:
|
|
|
- this.$toast('该文件类型暂不支持预览')
|
|
|
- }
|
|
|
+ getFileDetail(item.fileId).then((resp) => {
|
|
|
+ if (resp.code === 0) {
|
|
|
+ /* this.fileDetail = resp.data
|
|
|
+ if (this.fileTypeStr === 'video') {
|
|
|
+ this.videoProp = {
|
|
|
+ videoUrl: this.fileDetail.url
|
|
|
+ }
|
|
|
+ }*/
|
|
|
+ var fileDetail = resp.data
|
|
|
+ // 根据类型进行预览
|
|
|
+ switch (item.fileTypeStr) {
|
|
|
+ case 'image':
|
|
|
+ this.previewImage(fileDetail)
|
|
|
+ break
|
|
|
+ case 'video':
|
|
|
+ this.previewVideo(fileDetail)
|
|
|
+ break
|
|
|
+ case 'audio':
|
|
|
+ this.previewAudio(fileDetail)
|
|
|
+ break
|
|
|
+ case 'text':
|
|
|
+ this.previewText(item)
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ this.$toast('该文件类型暂不支持预览')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.$toast(resp.msg)
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
},
|
|
|
- // --- 1. 图片预览 ---
|
|
|
+
|
|
|
previewImage(item) {
|
|
|
+ console.log(item.fileId)
|
|
|
ImagePreview({
|
|
|
- images: [
|
|
|
- // 实际开发中这里是后端返回的真实 URL
|
|
|
- 'https://img01.yzcdn.cn/vant/apple-1.jpg'
|
|
|
- ],
|
|
|
+ images: [item.url],
|
|
|
startPosition: 0,
|
|
|
closeable: true
|
|
|
})
|
|
|
},
|
|
|
|
|
|
- // --- 2. 视频预览 ---
|
|
|
previewVideo(item) {
|
|
|
- // 弹出全屏弹窗显示播放器
|
|
|
- this.showPlayerModal = true
|
|
|
this.currentPlayItem = item
|
|
|
-
|
|
|
- // 等 DOM 渲染后初始化播放器
|
|
|
- this.$nextTick(() => {
|
|
|
- const dp = new DPlayer({
|
|
|
- container: document.getElementById('dplayer'),
|
|
|
- video: {
|
|
|
- url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f823a691&userId=17&ext=.mp4', // 模拟地址
|
|
|
- thumbnails: '' // 视频缩略图
|
|
|
- }
|
|
|
- })
|
|
|
- dp.play()
|
|
|
- })
|
|
|
+ this.showPlayerModal = true
|
|
|
+ // 原生 video 标签通过 :src 绑定会自动加载
|
|
|
+ },
|
|
|
+ handleVideoClose() {
|
|
|
+ // 弹窗关闭时,务必暂停视频,否则后台会有声音
|
|
|
+ if (this.$refs.videoPlayer) {
|
|
|
+ this.$refs.videoPlayer.pause()
|
|
|
+ }
|
|
|
+ this.currentPlayItem = null
|
|
|
},
|
|
|
-
|
|
|
- // --- 3. 音频预览 ---
|
|
|
previewAudio(item) {
|
|
|
- // 音频可以直接用 Vant 的 Dialog 配合原生 audio 标签
|
|
|
+ const audioSrc = item.url || ''
|
|
|
+ // 映射 MIME 类型,如果是 'audio' 字符串通常需要转为 'audio/mpeg' 或 'audio/wav'
|
|
|
+ const mimeType = item.fileTypeStr === 'audio' ? 'audio/mpeg' : `audio/wav`
|
|
|
this.$dialog.alert({
|
|
|
- title: item.name,
|
|
|
+ title: item.filename,
|
|
|
message: `
|
|
|
- <div style="padding: 20px 0;">
|
|
|
- <audio controls autoplay style="width: 100%;">
|
|
|
- <source src="http://music.163.com/song/media/outer/url?id=1407551413.mp3" type="audio/mpeg">
|
|
|
- </audio>
|
|
|
- </div>
|
|
|
- `,
|
|
|
+ <div style="padding: 20px 0;">
|
|
|
+ <audio controls autoplay style="width: 100%;">
|
|
|
+ <source src="${audioSrc}" type="${mimeType}">
|
|
|
+ 您的浏览器不支持音频播放。
|
|
|
+ </audio>
|
|
|
+ </div>
|
|
|
+ `,
|
|
|
allowHtml: true
|
|
|
})
|
|
|
},
|
|
|
-
|
|
|
- // --- 4. 文本预览 ---
|
|
|
previewText(item) {
|
|
|
// 简单文本直接弹出展示,复杂文档(PDF/Word)建议通过 window.open 或专门的预览服务
|
|
|
this.$dialog.alert({
|
|
|
- title: item.name,
|
|
|
+ title: item.filename,
|
|
|
message: '这是模拟的文本内容:\n\nPikPak 是一款非常棒的网盘...',
|
|
|
messageAlign: 'left'
|
|
|
})
|
|
|
@@ -671,4 +675,55 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.breadcrumb-bar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8px 16px;
|
|
|
+ background: #fdfdfd;
|
|
|
+ border-bottom: 1px solid #ebedf0;
|
|
|
+ .path-text {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #969799;
|
|
|
+ margin-left: 5px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ .back-btn {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #1989fa;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.player-container {
|
|
|
+ background: #000;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.video-wrapper {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #000;
|
|
|
+}
|
|
|
+
|
|
|
+.native-video {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 70vh; // 保证不遮挡底部的详情信息
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.player-header {
|
|
|
+ padding: 15px 40px; // 避开关闭按钮
|
|
|
+ text-align: center;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
</style>
|