Files
phone-app------test1-/pages/WorkSpace/WorkSpace.vue

677 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="status-bar"></view>
<!-- ===== 新建文件夹设置弹窗 ===== -->
<view v-if="showNewFolder" class="popup-overlay" @click="closeNewFolderModal">
<view class="popup-card" @click.stop>
<!-- 关闭按钮 -->
<view class="close-popup-card" @click="closeNewFolderModal">
<view class="iconfont icon-quxiao"></view>
</view>
<!-- 标题 -->
<view class="popup-title">新建工作区目录</view>
<!-- 目录名称输入 -->
<view class="new-folder-name">目录名称</view>
<view class="create-folder-name" :class="{'has-value': isNFNFocused || newFolderName}">
<input v-model="newFolderName" @focus="isNFNFocused=true" @blur="isNFNFocused=false" type="text"
placeholder="输入目录名称,如:项目资料/设计图" />
</view>
<!-- 当前路径 -->
<view class="nf-path-label">当前路径</view>
<view class="nf-path-display">{{ currentFolderPath }}</view>
<!-- 路径预览 -->
<view class="nf-path-label">路径预览</view>
<view class="nf-path-preview">{{ folderPathPreview }}</view>
<!-- 底部按钮 -->
<view class="option-wrapper">
<view class="opt-btn opt-cancel" @click="closeNewFolderModal">取消</view>
<view class="opt-btn opt-confirm" @click="confirmCreateFolder">确认</view>
</view>
</view>
</view>
<view class="workspace-container page-container">
<view class="workspace-header">
<view class="custom-navbar">
<view class="navbar-left" @click="handleBackOrCheck ">
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
<view class="navbar-before-title workspace-text">{{isSelectFolder?'全选':navbarBeforeTitle}}</view>
</view>
<view class="navbar-title workspace-text">{{navbarTitle}}</view>
<view class="navbar-right">
<view v-if="isSelectFolder" class="navbar-right-text workspace-text" @click="handleSelectFolder()">
完成</view>
<view v-else class="iconfont icon-gengduo" :class="isMenuOpen ?'menu-open':'custom-navbar-icon'"
@click="handleMenu"></view>
</view>
<view v-if="isMenuOpen" class="menu-card">
<!-- <view class="menu-card-item" @click="handleSelectFolder();handleMenu()">选择</view>
<view class="solid-line"></view> -->
<view class="menu-card-item" @click="handleUploadFile();handleMenu()">上传文件</view>
<view class="solid-line"></view>
<view class="menu-card-item" @click="newWorkspaceFolder();handleMenu()">新建文件夹</view>
</view>
</view>
<view class="search-file-warpper">
<view class="search-file">
<view class="iconfont icon-sousuo"></view>
<input class="search-file-input" placeholder="搜索" @focus="handleSearchFocus"
v-model="searchKeyword" />
</view>
<view v-if="isSearchFocus" class="cancel-search" @click="handleSearchFocus">取消</view>
</view>
</view>
<view class="workspace-content">
<view class="folder-null" v-if="currentFolderList.length===0">
<view class="iconfont icon-wenjianjia"></view>
文件夹为空
</view>
<view class="folder-grid" v-if="currentFolderList.length>0">
<view class="folder-item" :class="{'select-folder':selectFileList.includes(folder.path)}"
v-for="folder in currentFolderList" :key="folder.path"
@click="isSelectFolder?selectFolder(folder.path):handleFolderClick(folder)"
@longpress="handleLongPress(folder)">
<view v-if="folder.type === 'directory'"
class="iconfont icon-a-wenjianjiawenjian folder-item-style"></view>
<view v-else class="iconfont folder-item-style" :class="getFileIcon(folder.extension)"></view>
<view class="folder-name">{{folder.name}}</view>
<view v-if="isSelectFolder" class="folder-checkbox"
:class="{'select-folder':selectFileList.includes(folder.path)}">
<uni-icons v-if="selectFileList.includes(folder.path)" type="checkmarkempty"
color="#fff"></uni-icons>
</view>
</view>
</view>
</view>
<view v-if="isSelectFolder" class="workspace-footer" :class="{'folder-actions':selectFileList.length>0}">
<view class="iconfont icon-shangchuan" @click="handleUploadFile"></view>
<!-- <view class="iconfont icon-fuzhi"></view> -->
<!-- <view class="iconfont icon-wenjian"></view> -->
<view class="iconfont icon-del"></view>
<!-- <view class="iconfont icon-gengduo"></view> -->
</view>
</view>
</template>
<script setup>
import {
onMounted,
ref,
computed
} from 'vue';
import {
getWorkspaceId,
getToken
} from '@/utils/user-info.js'
import {
chooseFile
} from '@/uni_modules/lime-choose-file';
import {
getWorkspaceList,
createWorkspaceFolder,
daleteWorkspace,
getWorkspaceFileURL,
uploadFileToShortStorage,
workspaceUploadFileByURL,
updateWorkspaceFileName
} from '@/utils/cloud-api.js'
const UserToken = ref('')
const workspaceId = ref('')
const navbarBeforeTitle = ref('');
const navbarTitle = ref('工作区');
// 选择文件模式
const isSelectFolder = ref(false);
const selectFileList = ref([]);
const handleSelectFolder = () => {
if (isSelectFolder.value) {
selectFileList.value = []
}
isSelectFolder.value = !isSelectFolder.value;
}
const selectFolder = (id) => {
const index = selectFileList.value.indexOf(id);
if (index !== -1) {
selectFileList.value.splice(index, 1);
} else {
selectFileList.value.push(id);
}
}
// 新建文件夹
const showNewFolder = ref(false)
const isNFNFocused = ref(false)
const newFolderName = ref('')
const closeNewFolderModal = () => {
showNewFolder.value = false
newFolderName.value = ''
}
const newWorkspaceFolder = () => {
showNewFolder.value = true
}
// ========== 上传文件 ==========
const handleUploadFile = () => {
chooseFile({
count: 10,
type: 'all',
success: async (res) => {
const files = res.tempFiles || [];
if (files.length === 0) {
uni.showToast({
title: '未选择文件',
icon: 'none'
});
return;
}
await doUploadWorkspaceFiles(files);
},
fail: (err) => {
console.error('选择文件失败:', err);
}
});
};
const doUploadWorkspaceFiles = async (files) => {
uni.showLoading({
title: '上传中...'
});
try {
const dirPath = folderStack.value.length > 0 ?
'./' + folderStack.value.map(f => f.name).join('/') + '/' :
'/';
// 1. 一次性上传所有文件到短存云端,获取 URL 数组
const results = await uploadFileToShortStorage(
workspaceId.value,
files,
dirPath
);
// results: [{ url, success }, ...]
// 2. 收集成功的 URL批量上传到工作区
const successUrls = results
.filter(r => r.success)
.map(r => r.url);
if (successUrls.length === 0) {
const errors = results.filter(r => !r.success).map(r => r.error).join('; ');
throw new Error(errors || '所有文件上传失败');
}
await workspaceUploadFileByURL(
UserToken.value,
workspaceId.value,
successUrls,
dirPath
);
uni.hideLoading();
const total = files.length;
const failed = total - successUrls.length;
const msg = failed > 0 ?
`上传完成:成功 ${successUrls.length},失败 ${failed}` :
`成功上传 ${total} 个文件`;
uni.showToast({
title: msg,
icon: successUrls.length > 0 ? 'success' : 'error'
});
// 刷新文件列表
initCurrentFolderList();
} catch (error) {
uni.hideLoading();
console.error('上传文件失败:', error);
uni.showToast({
title: typeof error === 'string' ? error : '上传失败,请重试',
icon: 'error'
});
}
};
// 当前文件夹路径
const currentFolderPath = computed(() => {
if (folderStack.value.length === 0) return '根目录'
return '/' + folderStack.value.map(f => f.name).join('/')
})
// 路径预览:./ + 当前路径 + 新文件夹名
const folderPathPreview = computed(() => {
const folderPath = folderStack.value.map(f => f.name).join('/')
const name = newFolderName.value.trim()
if (name) {
return folderPath ? `./${folderPath}/${name}` : `./${name}`
}
return folderPath ? `./${folderPath}/` : './'
})
// 确认创建文件夹
const confirmCreateFolder = async () => {
const name = newFolderName.value.trim()
if (!name) {
uni.showToast({
title: '请输入目录名称',
icon: 'none'
})
return
}
try {
// 拼接路径:./ 开头,如 ./项目资料/设计图
const folderPath = folderStack.value.map(f => f.name).join('/')
const dirPath = folderPath ? `./${folderPath}/${name}` : `./${name}`
await createWorkspaceFolder(UserToken.value, workspaceId.value, dirPath)
uni.showToast({
title: '创建成功',
icon: 'success'
})
closeNewFolderModal()
initCurrentFolderList()
} catch (error) {
console.error('创建文件夹失败:', error)
uni.showToast({
title: '创建失败,请重试',
icon: 'error'
})
}
}
// 长按文件
const handleLongPress = (folder) => {
if (isSelectFolder.value) return;
const isWyxd = folder.name && folder.name.toLowerCase().endsWith('.wyxd');
const itemList = isWyxd ? ['下载', '删除', 'WYXD查看器'] : ['下载', '删除'];
uni.showActionSheet({
itemList: itemList,
success: (res) => {
switch (res.tapIndex) {
case 0:
handleDownload(folder);
break;
case 1:
handleDelete(folder);
break;
case 2:
if (isWyxd) openWyxdViewer(folder);
break;
}
}
})
}
// 重命名
const handleRename = async (folder) => {
uni.showModal({
title: '重命名',
content: '请输入新名称',
editable: true,
placeholderText: folder.name,
success: async (res) => {
if (res.confirm && res.content) {
try {
uni.showLoading({
title: '重命名中...'
});
// 构建文件路径:当前目录 + 文件名
const dirPath = folderStack.value.length > 0 ?
'./' + folderStack.value.map(f => f.name).join('/') + '/' :
'/';
const filePath = dirPath + folder.name;
await updateWorkspaceFileName(
workspaceId.value,
filePath,
res.content
);
uni.hideLoading();
uni.showToast({
title: '重命名成功',
icon: 'success'
});
// 刷新文件列表
initCurrentFolderList();
} catch (error) {
uni.hideLoading();
console.error('重命名失败:', error);
uni.showToast({
title: typeof error === 'string' ? error : '重命名失败,请重试',
icon: 'error'
});
}
}
}
});
};
const handleDownload = async (folder) => {
// 目录不能下载
if (folder.type === 'directory') {
uni.showToast({
title: '暂不支持下载目录',
icon: 'none'
});
return;
}
try {
uni.showLoading({
title: '获取下载链接...'
});
// 构建文件路径,去掉开头的 ./
const filePath = folder.path.startsWith('./') ? folder.path.slice(2) : folder.path;
const result = await getWorkspaceFileURL(UserToken.value, workspaceId.value, filePath);
uni.hideLoading();
// 接口返回数组,取第一个文件的 url
const fileInfo = Array.isArray(result) ? result[0] : result;
if (!fileInfo || !fileInfo.url) {
uni.showToast({
title: '获取下载链接失败',
icon: 'error'
});
return;
}
const downloadUrl = fileInfo.url;
const fileName = folder.name;
uni.showLoading({
title: '下载中...'
});
uni.downloadFile({
url: downloadUrl,
name: fileName,
success: (downloadRes) => {
uni.hideLoading();
if (downloadRes.statusCode === 200) {
const tempPath = downloadRes.tempFilePath;
// 下载成功,直接打开文件
console.log("// 下载成功,文件下载路径为:",tempPath);
openFile(tempPath);
} else {
uni.showToast({
title: '下载失败',
icon: 'error'
});
}
},
fail: (err) => {
uni.hideLoading();
console.error('下载文件失败:', err);
uni.showToast({
title: '下载失败,请重试',
icon: 'error'
});
}
});
} catch (error) {
uni.hideLoading();
console.error('获取下载链接失败:', error);
uni.showToast({
title: '获取下载链接失败,请重试',
icon: 'error'
});
}
};
// 使用系统默认应用打开
const openFile = (filePath) => {
uni.openDocument({
filePath: filePath,
success: () => uni.showToast({
title: '打开成功',
icon: 'success'
}),
fail: (err) => {
console.error('打开文件失败:', err);
uni.showToast({
title: '无法打开此文件',
icon: 'error'
});
}
});
};
// 选择其他应用打开
const openFileWithMenu = (filePath) => {
uni.openDocument({
filePath: filePath,
showMenu: true,
success: () => uni.showToast({
title: '打开成功',
icon: 'success'
}),
fail: (err) => {
console.error('打开文件失败:', err);
uni.showToast({
title: '无法打开此文件',
icon: 'error'
});
}
});
};
// WYXD 查看器入口
const openWyxdViewer = (folder) => {
const filePath = folder.path.startsWith('./') ? folder.path.slice(2) : folder.path;
console.log("解析文件路径:",filePath);
console.log("folder",folder);
console.log("encode",encodeURIComponent(filePath));
console.log("encode",encodeURIComponent(folder.name));
uni.navigateTo({
url: '/pages/WYXD/WYXD?workspaceId=' + encodeURIComponent(workspaceId.value)
+ '&filePath=' + encodeURIComponent(filePath)
+ '&fileName=' + encodeURIComponent(folder.name)
+ '&userToken=' + encodeURIComponent(UserToken.value)
});
};
// 删除
const handleDelete = (folder) => {
uni.showModal({
title: '提示',
content: `确定要删除"${folder.name}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
// 路径加 ./ 前缀
const filePath = `./${folder.path}`
console.log("删除的是:", filePath);
await daleteWorkspace(UserToken.value, workspaceId.value, filePath)
uni.showToast({
title: '删除成功',
icon: 'success'
})
initCurrentFolderList()
} catch (error) {
console.error('删除失败:', error)
uni.showToast({
title: '删除失败,请重试',
icon: 'error'
})
}
}
}
});
};
// const handleFolderClick = (id) => {
// console.log(id);
// }
// 菜单
const isMenuOpen = ref(false)
const handleMenu = () => {
isMenuOpen.value = !isMenuOpen.value;
}
// 搜索框
const searchKeyword = ref('')
const isSearchFocus = ref(false)
const handleSearchFocus = () => {
isSearchFocus.value = !isSearchFocus.value
}
// 根据文件后缀返回对应图标
const getFileIcon = (ext) => {
const iconMap = {
'.png': 'icon-wenjiantupian',
'.jpg': 'icon-wenjiantupian',
'.jpeg': 'icon-wenjiantupian',
'.gif': 'icon-wenjiantupian',
'.svg': 'icon-SVG',
'.bmp': 'icon-wenjiantupian',
'.webp': 'icon-wenjiantupian',
'.mp4': 'icon-wenjianshipin',
'.avi': 'icon-wenjianshipin',
'.mov': 'icon-wenjianshipin',
'.wmv': 'icon-wenjianshipin',
'.flv': 'icon-wenjianshipin',
'.mkv': 'icon-wenjianshipin',
'.mp3': 'icon-wenjianyinpin',
'.wav': 'icon-wenjianyinpin',
'.flac': 'icon-wenjianyinpin',
'.aac': 'icon-wenjianyinpin',
'.pdf': 'icon-PDF1',
'.doc': 'icon-word',
'.docx': 'icon-word',
'.xls': 'icon-execl',
'.xlsx': 'icon-execl',
'.ppt': 'icon-PPT1',
'.pptx': 'icon-PPT1',
'.txt': 'icon-TXT1',
'.md': 'icon-file-markdown-fill',
'.zip': 'icon-wenjianyasuo',
'.rar': 'icon-wenjianyasuo',
'.7z': 'icon-wenjianyasuo',
'.tar': 'icon-wenjianyasuo',
'.gz': 'icon-wenjianyasuo',
'.js': 'icon-JS',
'.ts': 'icon-daimawenjia',
'.vue': 'icon-daimawenjia',
'.html': 'icon-HTML',
'.css': 'icon-CSS',
'.py': 'icon-daima',
'.java': 'icon-daima',
'.json': 'icon-JSON',
};
return iconMap[ext?.toLowerCase()] || 'icon-qitalingyu';
};
const allFoldersData = ref({})
// 文件夹路径栈
const folderStack = ref([]);
// 当前显示的文件夹列表
const currentFolderList = ref([]);
const getCurrentFolderList = () => {
console.log("当前路径为:", JSON.stringify(folderStack.value));
// 当前路径:栈为空时在根目录,路径为 "."
const currentPath = folderStack.value.length > 0 ? folderStack.value[folderStack.value.length - 1].path : '.';
// 在根目录下,直接返回根节点的 children
if (currentPath === '.') {
return allFoldersData.value?.children || [];
}
// 递归在树中按 path 查找目标节点
const findFolderByPath = (list, targetPath) => {
for (const item of list) {
if (item.path === targetPath) return item;
if (item.type === 'directory' && item.children?.length) {
const res = findFolderByPath(item.children, targetPath);
if (res) return res;
}
}
return null;
};
// 从根节点的 children 开始搜索
const currentFolder = findFolderByPath(allFoldersData.value?.children || [], currentPath);
return currentFolder ? currentFolder.children : [];
}
// 初始化当前文件夹列表
const initCurrentFolderList = async () => {
workspaceId.value = getWorkspaceId();
UserToken.value = getToken();
console.log("getWorkspaceId:", workspaceId.value);
allFoldersData.value = await getWorkspaceList(UserToken.value, workspaceId.value);
currentFolderList.value = getCurrentFolderList();
};
// 处理文件夹点击(非选择模式下进入文件夹,仅目录可点击进入)
const handleFolderClick = (folder) => {
if (folder.type !== 'directory') {
handleLongPress(folder)
return
}
folderStack.value.push({
path: folder.path,
name: folder.name
});
navbarTitle.value = folder.name;
navbarBeforeTitle.value = folderStack.value.length > 1 ? folderStack.value[folderStack.value.length - 2].name :
'工作区';
currentFolderList.value = getCurrentFolderList();
searchKeyword.value = '';
isSearchFocus.value = false;
};
// 全选/取消全选
const handleSelectAll = () => {
if (selectFileList.value.length === currentFolderList.value.length) {
selectFileList.value = [];
} else {
selectFileList.value = currentFolderList.value.map(f => f.path);
}
};
// 返回上一级
const handleBackOrCheck = () => {
if (isSelectFolder.value) {
// 选择模式下点击全选/取消全选
handleSelectAll();
return;
}
if (folderStack.value.length > 0) {
folderStack.value.pop();
if (folderStack.value.length === 0) {
navbarTitle.value = '工作区';
navbarBeforeTitle.value = null;
} else {
navbarTitle.value = folderStack.value[folderStack.value.length - 1].name;
navbarBeforeTitle.value = folderStack.value.length > 1 ?
folderStack.value[folderStack.value.length - 2].name :
'工作区';
}
initCurrentFolderList();
// 返回后清空搜索
searchKeyword.value = '';
isSearchFocus.value = false;
} else {
// 已在根目录,返回到聊天界面
uni.navigateBack({
delta: 1,
fail() {
console.log("返回失败,进入兜底跳转")
uni.reLaunch({
url: '/pages/Chat/Chat'
})
}
})
}
};
onMounted(() => {
initCurrentFolderList();
})
</script>
<style scoped>
@import url("WorkSpace.css");
</style>