|
|
@@ -1,39 +1,43 @@
|
|
|
<template>
|
|
|
+ <!-- 查看他人的分享时,保存文件到个人网盘 -->
|
|
|
<a-modal
|
|
|
title="保存文件到网盘"
|
|
|
- v-model:visible="visible"
|
|
|
- :maskClosable="false"
|
|
|
+ :visible="internalVisible"
|
|
|
+ :confirm-loading="sureBtnLoading"
|
|
|
+ @ok="handleDialogSure"
|
|
|
@cancel="handleDialogClose"
|
|
|
- @afterVisibleChange="handleVisibleChange"
|
|
|
+ :afterClose="handleDialogClose"
|
|
|
+ :maskClosable="false"
|
|
|
>
|
|
|
- <div class="dialog-content">
|
|
|
+ <div class="dialog-div-custom">
|
|
|
+ <!-- 选择的目标路径 -->
|
|
|
<div class="target-path">
|
|
|
<span class="label">目标路径:</span>
|
|
|
- <a-input v-model:value="targetPath" readonly size="small" />
|
|
|
+ <a-input class="content" v-model:value="targetPath" readonly size="small"></a-input>
|
|
|
</div>
|
|
|
-
|
|
|
+ <!-- 文件目录树 -->
|
|
|
<a-spin :spinning="loading">
|
|
|
- <a-directory-tree
|
|
|
- :treeData="fileTree"
|
|
|
- :fieldNames="{
|
|
|
- children: 'children',
|
|
|
- title: 'label'
|
|
|
- }"
|
|
|
- :defaultExpandedKeys="defaultExpandedKeys"
|
|
|
+ <a-tree
|
|
|
+ :tree-data="fileTree"
|
|
|
+ :field-names="{ children: 'children', title: 'label', key: 'id' }"
|
|
|
+ highlight-current
|
|
|
+ :expandedKeys="defaultExpandedKeys"
|
|
|
+ @update:expandedKeys="(val) => (defaultExpandedKeys = val)"
|
|
|
+ node-key="id"
|
|
|
@select="handleNodeClick"
|
|
|
+ :showLine="true"
|
|
|
>
|
|
|
- <template #title="{ node, data }">
|
|
|
- <div class="custom-tree-node">
|
|
|
- <span class="label">{{ node.title }}</span>
|
|
|
- <a-button type="link" size="small" class="add-folder-btn" @click.stop="handleAddFolderBtnClick(data)">
|
|
|
+ <template #title="{ dataRef }">
|
|
|
+ <span class="custom-tree-node">
|
|
|
+ <span class="label">{{ dataRef.label }}</span>
|
|
|
+ <a-button class="add-folder-btn" type="link" size="small" @click.stop="handleAddFolderBtnClick(dataRef)">
|
|
|
新建文件夹
|
|
|
</a-button>
|
|
|
- </div>
|
|
|
+ </span>
|
|
|
</template>
|
|
|
- </a-directory-tree>
|
|
|
+ </a-tree>
|
|
|
</a-spin>
|
|
|
</div>
|
|
|
-
|
|
|
<template #footer>
|
|
|
<a-button @click="handleDialogClose">取 消</a-button>
|
|
|
<a-button type="primary" :loading="sureBtnLoading" @click="handleDialogSure"> 确 定 </a-button>
|
|
|
@@ -42,65 +46,124 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
- import { ref } from 'vue'
|
|
|
- import { message } from 'ant-design-vue'
|
|
|
+ import { ref, getCurrentInstance, watch } from 'vue'
|
|
|
import { getFoldTree, saveShareFile } from '@/api/myResource/file'
|
|
|
+ import { message } from 'ant-design-vue'
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
+
|
|
|
const props = defineProps({
|
|
|
- userFileIds: String,
|
|
|
- shareBatchNum: String,
|
|
|
+ visible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ userFileIds: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ shareBatchNum: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
callback: Function
|
|
|
})
|
|
|
|
|
|
- const visible = ref(false)
|
|
|
- const targetPath = ref('/')
|
|
|
- const fileTree = ref([])
|
|
|
- const loading = ref(false)
|
|
|
+ const emit = defineEmits(['update:visible'])
|
|
|
+ const internalVisible = ref(props.visible) // 使用 internalVisible 作为主要的响应式状态
|
|
|
+ const targetPath = ref('/') // 目标路径
|
|
|
+ const fileTree = ref([]) // 文件夹目录树
|
|
|
+ const loading = ref(false) // 文件夹目录树 loading 状态
|
|
|
const defaultExpandedKeys = ref([])
|
|
|
- const sureBtnLoading = ref(false)
|
|
|
+ const sureBtnLoading = ref(false) // 确定按钮 loading 状态
|
|
|
|
|
|
+ watch(
|
|
|
+ internalVisible, // 监听内部的 internalVisible ref
|
|
|
+ (newVal, oldVal) => {
|
|
|
+ if (newVal && !oldVal) {
|
|
|
+ // 仅在从 false 变为 true 时调用,即对话框打开时
|
|
|
+ handleDialogOpen()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ // 如果父组件通过 prop 更新 visible,也需要同步到 internalVisible
|
|
|
+ watch(
|
|
|
+ () => props.visible,
|
|
|
+ (newVal) => {
|
|
|
+ internalVisible.value = newVal
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消按钮点击事件 & 对话框关闭的回调
|
|
|
+ */
|
|
|
const handleDialogClose = () => {
|
|
|
- visible.value = false
|
|
|
+ internalVisible.value = false
|
|
|
+ emit('update:visible', false) // 如果父组件需要 .sync 修饰符或者 v-model:visible
|
|
|
props.callback('cancel')
|
|
|
}
|
|
|
|
|
|
- const handleVisibleChange = (val) => {
|
|
|
- if (val) {
|
|
|
- initFileTree()
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * 对话框打开的回调
|
|
|
+ */
|
|
|
+ const handleDialogOpen = () => {
|
|
|
+ initFileTree()
|
|
|
+ targetPath.value = '/' // 重置目标路径
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 初始化文件目录树
|
|
|
+ */
|
|
|
const initFileTree = async (id) => {
|
|
|
+ loading.value = true
|
|
|
try {
|
|
|
- loading.value = true
|
|
|
const res = await getFoldTree()
|
|
|
if (res.success) {
|
|
|
fileTree.value = [res.data]
|
|
|
- defaultExpandedKeys.value = id ? [id] : [fileTree.value[0].id]
|
|
|
+ defaultExpandedKeys.value = id ? [id] : fileTree.value.length > 0 ? [fileTree.value[0].id] : []
|
|
|
} else {
|
|
|
message.error(res.message)
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ message.error('获取文件夹树失败')
|
|
|
+ console.error(error)
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const handleNodeClick = (selectedKeys, { node }) => {
|
|
|
- targetPath.value = node.filePath || '/'
|
|
|
+ /**
|
|
|
+ * 目录树节点点击回调函数
|
|
|
+ * @param {Array} selectedKeys 当前选中的节点的 key
|
|
|
+ * @param {object} e event 对象,包含 {selected: bool, selectedNodes, node, event}
|
|
|
+ */
|
|
|
+ const handleNodeClick = (selectedKeys, e) => {
|
|
|
+ if (e.node && e.node.dataRef) {
|
|
|
+ targetPath.value = e.node.dataRef.filePath ? e.node.dataRef.filePath : '/'
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 新建文件夹按钮点击事件
|
|
|
+ * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件夹树
|
|
|
+ */
|
|
|
const handleAddFolderBtnClick = async (data) => {
|
|
|
- const result = await proxy.$openDialog.addFolder({
|
|
|
- filePath: data.filePath || '/'
|
|
|
- })
|
|
|
- if (result === 'confirm') {
|
|
|
+ try {
|
|
|
+ await proxy.$openDialog.addFolder({
|
|
|
+ filePath: data.filePath || '/'
|
|
|
+ })
|
|
|
initFileTree(data.id)
|
|
|
+ } catch (error) {
|
|
|
+ console.log('Add folder cancelled or failed', error)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 确定按钮点击事件
|
|
|
+ * @description 调用保存分享文件接口
|
|
|
+ */
|
|
|
const handleDialogSure = async () => {
|
|
|
+ sureBtnLoading.value = true
|
|
|
try {
|
|
|
- sureBtnLoading.value = true
|
|
|
const res = await saveShareFile({
|
|
|
filePath: targetPath.value,
|
|
|
userFileIds: props.userFileIds,
|
|
|
@@ -108,19 +171,24 @@
|
|
|
})
|
|
|
|
|
|
if (res.success) {
|
|
|
- visible.value = false
|
|
|
message.success('保存成功')
|
|
|
+ internalVisible.value = false
|
|
|
+ emit('update:visible', false)
|
|
|
props.callback('confirm')
|
|
|
} else {
|
|
|
message.error(res.message)
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ message.error('保存文件失败')
|
|
|
+ console.error(error)
|
|
|
} finally {
|
|
|
sureBtnLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 暴露给模板或者父组件(如果需要ref访问)
|
|
|
defineExpose({
|
|
|
- visible
|
|
|
+ visible: internalVisible // 暴露 internalVisible
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -128,50 +196,105 @@
|
|
|
@import '@/style/myResource/varibles.less';
|
|
|
@import '@/style/myResource/mixins.less';
|
|
|
|
|
|
- .dialog-content {
|
|
|
- height: 300px;
|
|
|
- overflow: auto;
|
|
|
+ // 使用 :deep 替代 >>>
|
|
|
+ :deep(.ant-modal) {
|
|
|
+ .ant-modal-header {
|
|
|
+ display: flex; // antd 默认就是 flex,可能不需要
|
|
|
+ }
|
|
|
|
|
|
- .target-path {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16px;
|
|
|
+ .ant-modal-body {
|
|
|
+ padding: 10px 30px;
|
|
|
|
|
|
- .label {
|
|
|
- width: 80px;
|
|
|
- }
|
|
|
+ .dialog-div-custom {
|
|
|
+ // 修改类名以避免与 antd 内部类名冲突
|
|
|
+ height: 300px;
|
|
|
+ overflow: auto;
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+ }
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background: #ccc; // 滚动条颜色
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: #f1f1f1; // 轨道颜色
|
|
|
+ }
|
|
|
|
|
|
- .ant-input {
|
|
|
- flex: 1;
|
|
|
- }
|
|
|
- }
|
|
|
+ .target-path {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10px; // 增加一些间距
|
|
|
|
|
|
- .custom-tree-node {
|
|
|
- width: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
+ .label {
|
|
|
+ width: 80px;
|
|
|
+ flex-shrink: 0; // 防止被压缩
|
|
|
+ }
|
|
|
|
|
|
- .add-folder-btn {
|
|
|
- display: none;
|
|
|
- }
|
|
|
+ .content {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ant-tree {
|
|
|
+ .ant-tree-treenode {
|
|
|
+ width: 100%;
|
|
|
+ // antd v4+ 使用 .ant-tree-treenode
|
|
|
+ .ant-tree-node-content-wrapper {
|
|
|
+ height: 34px;
|
|
|
+ font-size: 16px;
|
|
|
+ display: flex; // 确保自定义内容能正确布局
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ &:hover {
|
|
|
+ .add-folder-btn {
|
|
|
+ display: inline-block; // 或者 block,取决于布局需求
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &:hover .add-folder-btn {
|
|
|
- display: inline-block;
|
|
|
+ .ant-tree-switcher .ant-tree-switcher-icon svg {
|
|
|
+ font-size: 18px; // 调整展开/折叠图标大小
|
|
|
+ }
|
|
|
+
|
|
|
+ .custom-tree-node {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ font-size: 14px;
|
|
|
+ padding-right: 8px;
|
|
|
+
|
|
|
+ .add-folder-btn {
|
|
|
+ color: #000000;
|
|
|
+ display: none; // 默认隐藏
|
|
|
+ margin-left: 8px; // 给按钮一些左边距
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ :deep(.ant-tree .ant-tree-treenode .custom-tree-node) {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
|
|
|
- .ant-btn-primary {
|
|
|
- &,
|
|
|
- &:hover,
|
|
|
- &:focus {
|
|
|
- background-color: @primary-color;
|
|
|
- border-color: @primary-color;
|
|
|
- }
|
|
|
+ :deep(.ant-tree .ant-tree-treenode:hover .custom-tree-node .add-folder-btn) {
|
|
|
+ color: #29175b !important;
|
|
|
+ display: inline-block !important;
|
|
|
}
|
|
|
|
|
|
- .ant-tree-node-selected {
|
|
|
- // background-color: .fade(@primary-color, 10%) !important;
|
|
|
+ :deep(.ant-tree .ant-tree-treenode .custom-tree-node .add-folder-btn) {
|
|
|
+ color: #000000;
|
|
|
+ display: none !important;
|
|
|
+ margin-left: 8px;
|
|
|
+ }
|
|
|
+ :deep(.ant-tree) {
|
|
|
+ .ant-tree-treenode,
|
|
|
+ .ant-tree-node-content-wrapper,
|
|
|
+ .ant-tree-title {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|