This commit is contained in:
parent
1898d1b730
commit
d5adbd8a1c
|
|
@ -115,3 +115,12 @@ export function saveSharePermissionAPI(data = {}) {
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移动和添加副本时获取的树结构
|
||||||
|
export function getMoveAndAddCopyTreeAPI(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/screen/document/getRemoveTree',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
// 获取节点图标
|
// 获取节点图标
|
||||||
getNodeIcon(data) {
|
getNodeIcon(data) {
|
||||||
|
return 'el-icon-folder'
|
||||||
if (data.fileType == 0) {
|
if (data.fileType == 0) {
|
||||||
return 'el-icon-folder'
|
return 'el-icon-folder'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,10 @@
|
||||||
/>
|
/>
|
||||||
<span class="file-name">{{ scope.row.name }}</span>
|
<span class="file-name">{{ scope.row.name }}</span>
|
||||||
|
|
||||||
<el-dropdown trigger="click">
|
<el-dropdown
|
||||||
|
trigger="click"
|
||||||
|
v-if="scope.row.fileType == 1"
|
||||||
|
>
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<i
|
<i
|
||||||
class="el-icon-setting el-icon--right"
|
class="el-icon-setting el-icon--right"
|
||||||
|
|
@ -84,7 +87,7 @@
|
||||||
<el-dropdown-menu slot="dropdown">
|
<el-dropdown-menu slot="dropdown">
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
@click="item.click"
|
@click="item.click(scope.row)"
|
||||||
v-for="item in dropdownList"
|
v-for="item in dropdownList"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
|
|
@ -174,24 +177,6 @@
|
||||||
@closeDialogOuter="handleCloseDialogOuter"
|
@closeDialogOuter="handleCloseDialogOuter"
|
||||||
>
|
>
|
||||||
<template #outerContent>
|
<template #outerContent>
|
||||||
<!-- <DownloadTags
|
|
||||||
v-if="dialogConfig.outerTitle === '批量下载'"
|
|
||||||
:selectedFiles="selectedFilesForDownload"
|
|
||||||
@remove-file="handleRemoveFile"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SharePermissionForm
|
|
||||||
v-if="dialogConfig.outerTitle === '共享'"
|
|
||||||
:selectedFiles="selectedFilesForShare"
|
|
||||||
@cancel="handleCloseShareDialog"
|
|
||||||
@confirm="handleShareConfirm"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AddWord v-if="dialogConfig.outerTitle === '新建文档夹'" />
|
|
||||||
<Upload v-if="dialogConfig.outerTitle === '上传文件'" />
|
|
||||||
<Move v-if="dialogConfig.outerTitle === '移动'" />
|
|
||||||
<AddCopy v-if="dialogConfig.outerTitle === '添加副本'" /> -->
|
|
||||||
|
|
||||||
<component
|
<component
|
||||||
ref="componentRef"
|
ref="componentRef"
|
||||||
:selectedNode="selectedNode"
|
:selectedNode="selectedNode"
|
||||||
|
|
@ -336,13 +321,15 @@ export default {
|
||||||
dropdownList: [
|
dropdownList: [
|
||||||
{
|
{
|
||||||
label: '下载',
|
label: '下载',
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '移动',
|
|
||||||
click: this.handleMove,
|
click: this.handleMove,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '移动',
|
||||||
|
click: this.handleMove_1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '删除',
|
label: '删除',
|
||||||
|
click: this.handleDelete_1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '共享',
|
label: '共享',
|
||||||
|
|
@ -350,10 +337,11 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '重命名',
|
label: '重命名',
|
||||||
|
click: this.handleMove,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '添加副本',
|
label: '添加副本',
|
||||||
click: this.handleAddCopy,
|
click: this.handleAddCopy_1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -461,11 +449,26 @@ export default {
|
||||||
|
|
||||||
// 移动
|
// 移动
|
||||||
handleMove() {
|
handleMove() {
|
||||||
// this.$emit('move', this.selectedRows)
|
if (this.selectedRows.length === 0) {
|
||||||
|
this.$modal.msgWarning('请选择要移动的文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.dialogConfig.outerTitle = '移动'
|
this.dialogConfig.outerTitle = '移动'
|
||||||
this.dialogConfig.outerVisible = true
|
this.dialogConfig.outerVisible = true
|
||||||
this.dialogConfig.outerComponent = 'Move'
|
this.dialogConfig.outerComponent = 'Move'
|
||||||
this.dialogConfig.outerComponentProps = {}
|
this.dialogConfig.outerComponentProps = {
|
||||||
|
selectedFiles: this.selectedRows,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 移动
|
||||||
|
handleMove_1(row) {
|
||||||
|
this.dialogConfig.outerTitle = '移动'
|
||||||
|
this.dialogConfig.outerVisible = true
|
||||||
|
this.dialogConfig.outerComponent = 'Move'
|
||||||
|
this.dialogConfig.outerComponentProps = {
|
||||||
|
selectedFiles: [row],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
|
|
@ -505,6 +508,37 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
async handleDelete_1(row) {
|
||||||
|
this.$confirm(
|
||||||
|
'所选中文档及对应的映射文档将被删除, 且该操作不可恢复, 确认吗?\n该操作会重新配置文档或文档夹的权限, 确认继续吗?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
// this.$modal.msgSuccess('删除功能待实现')
|
||||||
|
|
||||||
|
const res = await deleteDocCenterAPI({
|
||||||
|
id: row.id,
|
||||||
|
parentId: this.selectedNode?.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.$modal.msgSuccess('删除成功')
|
||||||
|
this.getTableList()
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError(res.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.$modal.msgInfo('已取消删除')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 批量下载
|
// 批量下载
|
||||||
handleBatchDownload() {
|
handleBatchDownload() {
|
||||||
if (this.selectedRows.length === 0) {
|
if (this.selectedRows.length === 0) {
|
||||||
|
|
@ -542,11 +576,26 @@ export default {
|
||||||
|
|
||||||
// 添加副本
|
// 添加副本
|
||||||
handleAddCopy() {
|
handleAddCopy() {
|
||||||
// this.$emit('add-copy', this.selectedRows)
|
if (this.selectedRows.length === 0) {
|
||||||
|
this.$modal.msgWarning('请选择要添加副本的文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.dialogConfig.outerTitle = '添加副本'
|
this.dialogConfig.outerTitle = '添加副本'
|
||||||
this.dialogConfig.outerVisible = true
|
this.dialogConfig.outerVisible = true
|
||||||
this.dialogConfig.outerComponent = 'AddCopy'
|
this.dialogConfig.outerComponent = 'AddCopy'
|
||||||
this.dialogConfig.outerComponentProps = {}
|
this.dialogConfig.outerComponentProps = {
|
||||||
|
selectedFiles: this.selectedRows,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 添加副本
|
||||||
|
handleAddCopy_1(row) {
|
||||||
|
this.dialogConfig.outerTitle = '添加副本'
|
||||||
|
this.dialogConfig.outerVisible = true
|
||||||
|
this.dialogConfig.outerComponent = 'AddCopy'
|
||||||
|
this.dialogConfig.outerComponentProps = {
|
||||||
|
selectedFiles: [row],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 查看
|
// 查看
|
||||||
|
|
@ -603,7 +652,7 @@ export default {
|
||||||
this.getTableList()
|
this.getTableList()
|
||||||
|
|
||||||
// 更新左侧树
|
// 更新左侧树
|
||||||
this.$emit('updateLeftTree')
|
// this.$emit('updateLeftTree')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('操作失败:', error)
|
console.error('操作失败:', error)
|
||||||
// 如果是表单验证失败,不显示错误提示(因为 Element UI 已经显示了)
|
// 如果是表单验证失败,不显示错误提示(因为 Element UI 已经显示了)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- 添加副本 -->
|
|
||||||
<div class="move-container">
|
<div class="move-container">
|
||||||
<div class="tree-wrapper" v-loading="loading">
|
<div class="tree-wrapper" v-loading="loading">
|
||||||
<el-tree
|
<el-tree
|
||||||
|
|
@ -11,42 +10,37 @@
|
||||||
:props="treeProps"
|
:props="treeProps"
|
||||||
highlight-current
|
highlight-current
|
||||||
default-expand-all
|
default-expand-all
|
||||||
draggable
|
|
||||||
:allow-drag="allowDrag"
|
|
||||||
:allow-drop="allowDrop"
|
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="false"
|
||||||
@node-drop="handleNodeDrop"
|
@node-click="handleNodeClick"
|
||||||
>
|
>
|
||||||
<span class="tree-node" slot-scope="{ data }">
|
<span class="tree-node" slot-scope="{ data }">
|
||||||
<i :class="getNodeIcon(data)" class="node-icon"></i>
|
<i class="el-icon-folder node-icon"></i>
|
||||||
<span class="node-label" :title="data.name">
|
<span class="node-label" :title="data.name">
|
||||||
{{ data.name }}
|
{{ data.name }}
|
||||||
</span>
|
</span>
|
||||||
<el-tag size="mini" v-if="data.fileType === 0" type="info">
|
|
||||||
文件夹
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
</span>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
<div v-else class="empty-holder">暂无可移动数据</div>
|
<div v-else class="empty-holder">暂无可添加副本的文件夹</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tips">
|
<div v-if="selectedFolder" class="selected-info">
|
||||||
<p class="tips-title">操作提示</p>
|
<span>已选择目标文件夹:</span>
|
||||||
<p>1. 仅文件(fileType ≠ 0)可被拖拽。</p>
|
<el-tag type="primary">{{ selectedFolder.name }}</el-tag>
|
||||||
<p>2. 仅支持拖拽到文件夹节点内。</p>
|
|
||||||
<p>3. 拖拽到目标组后会立即复制副本并同步至后台。</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { copyDocCenterAPI } from '@/api/publicService/docCenter'
|
import {
|
||||||
|
copyDocCenterAPI,
|
||||||
|
getMoveAndAddCopyTreeAPI,
|
||||||
|
} from '@/api/publicService/docCenter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddCopy',
|
name: 'Move',
|
||||||
props: {
|
props: {
|
||||||
selectedNode: {
|
selectedFiles: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: () => ({}),
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -57,128 +51,100 @@ export default {
|
||||||
label: 'name',
|
label: 'name',
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
copyActions: [],
|
selectedFolder: null, // 选中的目标文件夹
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted() {
|
||||||
selectedNode: {
|
this.getTreeData()
|
||||||
handler(newVal) {
|
|
||||||
this.initTree(newVal)
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initTree(node) {
|
// 获取树结构数据
|
||||||
if (!node || !node.id) {
|
async getTreeData() {
|
||||||
this.treeData = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.treeData = [this.cloneNode(node)]
|
|
||||||
this.copyActions = []
|
|
||||||
},
|
|
||||||
|
|
||||||
cloneNode(node) {
|
|
||||||
const childTree = Array.isArray(node.childTree)
|
|
||||||
? node.childTree.map((child) => this.cloneNode(child))
|
|
||||||
: []
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
childTree,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getNodeIcon(data) {
|
|
||||||
// console.log(data.fileType, 'data.fileType')
|
|
||||||
return data.fileType == 0 ? 'el-icon-folder' : 'el-icon-document'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 是否允许拖拽
|
|
||||||
allowDrag(draggingNode) {
|
|
||||||
const data = draggingNode?.data || {}
|
|
||||||
return data.fileType == 1
|
|
||||||
},
|
|
||||||
// 是否允许放下
|
|
||||||
allowDrop(draggingNode, dropNode, type) {
|
|
||||||
if (type !== 'inner') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const dropData = dropNode?.data || {}
|
|
||||||
if (dropData.fileType == 1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragData = draggingNode?.data || {}
|
|
||||||
if (!dragData.id || dragData.id === dropData.id) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
async handleNodeDrop(draggingNode, dropNode) {
|
|
||||||
const source = draggingNode?.data
|
|
||||||
const target = dropNode?.data
|
|
||||||
|
|
||||||
if (!source?.id || !target?.id) {
|
|
||||||
this.$message.error('无法获取拖拽节点信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.copyDocument(
|
|
||||||
source.id,
|
|
||||||
target.id,
|
|
||||||
source.parentId,
|
|
||||||
target.parentIds,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
async copyDocument(documentId, targetParentId, parentId, parentIds) {
|
|
||||||
// 组装参数
|
|
||||||
const params = {
|
|
||||||
id: documentId, // 自身id
|
|
||||||
toId: targetParentId,
|
|
||||||
newParentId: targetParentId, // 目标i
|
|
||||||
parentId: parentId, // 目标父级id
|
|
||||||
parentIds: parentIds,
|
|
||||||
}
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const res = await copyDocCenterAPI(params)
|
const res = await getMoveAndAddCopyTreeAPI()
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
if (res.code === 200) {
|
this.treeData = Array.isArray(res.data) ? res.data : []
|
||||||
this.$message.success('复制成功')
|
|
||||||
this.recordAction(documentId, targetParentId)
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg || res.message || '复制失败')
|
this.$message.error(
|
||||||
|
res.msg || res.message || '获取文件夹树失败',
|
||||||
|
)
|
||||||
|
this.treeData = []
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error(error.message || '复制失败')
|
this.$message.error(error.message || '获取文件夹树失败')
|
||||||
|
this.treeData = []
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
recordAction(documentId, targetParentId) {
|
// 点击树节点
|
||||||
const idx = this.copyActions.findIndex(
|
handleNodeClick(data) {
|
||||||
(item) => item.documentId === documentId,
|
this.selectedFolder = data
|
||||||
)
|
// 设置树节点为选中状态
|
||||||
const payload = { documentId, targetParentId }
|
this.$nextTick(() => {
|
||||||
if (idx > -1) {
|
this.$refs.treeRef.setCurrentKey(data.id)
|
||||||
this.$set(this.copyActions, idx, payload)
|
})
|
||||||
} else {
|
},
|
||||||
this.copyActions.push(payload)
|
|
||||||
|
// 添加副本
|
||||||
|
async moveDocument(fileId, targetParentId, parentId, parentIds) {
|
||||||
|
const params = {
|
||||||
|
id: fileId,
|
||||||
|
toId: targetParentId,
|
||||||
|
newParentId: targetParentId,
|
||||||
|
parentId: parentId,
|
||||||
|
parentIds: parentIds,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await copyDocCenterAPI(params)
|
||||||
|
if (res.code !== 200) {
|
||||||
|
throw new Error(res.msg || res.message || '添加副本失败')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
submit() {
|
// 提交添加副本操作
|
||||||
if (this.copyActions.length === 0) {
|
async submit() {
|
||||||
return Promise.reject(new Error('请先拖拽需要复制的文件'))
|
// 验证选中的文件
|
||||||
|
if (!this.selectedFiles || this.selectedFiles.length === 0) {
|
||||||
|
return Promise.reject(new Error('请先选择要添加副本的文件'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证选中的目标文件夹
|
||||||
|
if (!this.selectedFolder || !this.selectedFolder.id) {
|
||||||
|
return Promise.reject(new Error('请选择要添加副本的目标文件夹'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
// 批量移动文件
|
||||||
|
const movePromises = this.selectedFiles.map((file) =>
|
||||||
|
this.moveDocument(
|
||||||
|
file.id,
|
||||||
|
this.selectedFolder.id,
|
||||||
|
file.parentId,
|
||||||
|
file.parentIds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await Promise.all(movePromises)
|
||||||
|
|
||||||
|
this.$message.success('移动成功')
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
movedCount: this.selectedFiles.length,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error(error.message || '添加副本失败')
|
||||||
|
return Promise.reject(error)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
}
|
}
|
||||||
return Promise.resolve({
|
|
||||||
copied: this.copyActions,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +173,7 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.node-icon {
|
.node-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
@ -214,6 +181,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-label {
|
.node-label {
|
||||||
|
flex: 1;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
@ -230,18 +198,15 @@ export default {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips {
|
.selected-info {
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.8;
|
|
||||||
background: #fdf6ec;
|
|
||||||
border: 1px solid #f5dab1;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border: 1px solid #bae6fd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
.tips-title {
|
align-items: center;
|
||||||
font-weight: 600;
|
gap: 8px;
|
||||||
margin-bottom: 4px;
|
font-size: 14px;
|
||||||
}
|
color: #666;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
<template>
|
||||||
|
<!-- 添加副本 -->
|
||||||
|
<div class="move-container">
|
||||||
|
<div class="tree-wrapper" v-loading="loading">
|
||||||
|
<el-tree
|
||||||
|
v-if="treeData.length"
|
||||||
|
ref="treeRef"
|
||||||
|
node-key="id"
|
||||||
|
class="move-tree"
|
||||||
|
:data="treeData"
|
||||||
|
:props="treeProps"
|
||||||
|
highlight-current
|
||||||
|
default-expand-all
|
||||||
|
draggable
|
||||||
|
:allow-drag="allowDrag"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
@node-drop="handleNodeDrop"
|
||||||
|
>
|
||||||
|
<span class="tree-node" slot-scope="{ data }">
|
||||||
|
<i :class="getNodeIcon(data)" class="node-icon"></i>
|
||||||
|
<span class="node-label" :title="data.name">
|
||||||
|
{{ data.name }}
|
||||||
|
</span>
|
||||||
|
<el-tag size="mini" v-if="data.fileType === 0" type="info">
|
||||||
|
文件夹
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
</el-tree>
|
||||||
|
<div v-else class="empty-holder">暂无可移动数据</div>
|
||||||
|
</div>
|
||||||
|
<div class="tips">
|
||||||
|
<p class="tips-title">操作提示</p>
|
||||||
|
<p>1. 仅文件(fileType ≠ 0)可被拖拽。</p>
|
||||||
|
<p>2. 仅支持拖拽到文件夹节点内。</p>
|
||||||
|
<p>3. 拖拽到目标组后会立即复制副本并同步至后台。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { copyDocCenterAPI } from '@/api/publicService/docCenter'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AddCopy',
|
||||||
|
props: {
|
||||||
|
selectedNode: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
treeData: [],
|
||||||
|
treeProps: {
|
||||||
|
children: 'childTree',
|
||||||
|
label: 'name',
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
copyActions: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedNode: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.initTree(newVal)
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initTree(node) {
|
||||||
|
if (!node || !node.id) {
|
||||||
|
this.treeData = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.treeData = [this.cloneNode(node)]
|
||||||
|
this.copyActions = []
|
||||||
|
},
|
||||||
|
|
||||||
|
cloneNode(node) {
|
||||||
|
const childTree = Array.isArray(node.childTree)
|
||||||
|
? node.childTree.map((child) => this.cloneNode(child))
|
||||||
|
: []
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
childTree,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNodeIcon(data) {
|
||||||
|
// console.log(data.fileType, 'data.fileType')
|
||||||
|
return data.fileType == 0 ? 'el-icon-folder' : 'el-icon-document'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 是否允许拖拽
|
||||||
|
allowDrag(draggingNode) {
|
||||||
|
const data = draggingNode?.data || {}
|
||||||
|
return data.fileType == 1
|
||||||
|
},
|
||||||
|
// 是否允许放下
|
||||||
|
allowDrop(draggingNode, dropNode, type) {
|
||||||
|
if (type !== 'inner') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const dropData = dropNode?.data || {}
|
||||||
|
if (dropData.fileType == 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragData = draggingNode?.data || {}
|
||||||
|
if (!dragData.id || dragData.id === dropData.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleNodeDrop(draggingNode, dropNode) {
|
||||||
|
const source = draggingNode?.data
|
||||||
|
const target = dropNode?.data
|
||||||
|
|
||||||
|
if (!source?.id || !target?.id) {
|
||||||
|
this.$message.error('无法获取拖拽节点信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.copyDocument(
|
||||||
|
source.id,
|
||||||
|
target.id,
|
||||||
|
source.parentId,
|
||||||
|
target.parentIds,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
async copyDocument(documentId, targetParentId, parentId, parentIds) {
|
||||||
|
// 组装参数
|
||||||
|
const params = {
|
||||||
|
id: documentId, // 自身id
|
||||||
|
toId: targetParentId,
|
||||||
|
newParentId: targetParentId, // 目标i
|
||||||
|
parentId: parentId, // 目标父级id
|
||||||
|
parentIds: parentIds,
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await copyDocCenterAPI(params)
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.$message.success('复制成功')
|
||||||
|
this.recordAction(documentId, targetParentId)
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg || res.message || '复制失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error(error.message || '复制失败')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
recordAction(documentId, targetParentId) {
|
||||||
|
const idx = this.copyActions.findIndex(
|
||||||
|
(item) => item.documentId === documentId,
|
||||||
|
)
|
||||||
|
const payload = { documentId, targetParentId }
|
||||||
|
if (idx > -1) {
|
||||||
|
this.$set(this.copyActions, idx, payload)
|
||||||
|
} else {
|
||||||
|
this.copyActions.push(payload)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (this.copyActions.length === 0) {
|
||||||
|
return Promise.reject(new Error('请先拖拽需要复制的文件'))
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
copied: this.copyActions,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.move-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-wrapper {
|
||||||
|
min-height: 320px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-tree {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.node-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-holder {
|
||||||
|
height: 180px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
background: #fdf6ec;
|
||||||
|
border: 1px solid #f5dab1;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.tips-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -10,42 +10,37 @@
|
||||||
:props="treeProps"
|
:props="treeProps"
|
||||||
highlight-current
|
highlight-current
|
||||||
default-expand-all
|
default-expand-all
|
||||||
draggable
|
|
||||||
:allow-drag="allowDrag"
|
|
||||||
:allow-drop="allowDrop"
|
|
||||||
:expand-on-click-node="false"
|
:expand-on-click-node="false"
|
||||||
@node-drop="handleNodeDrop"
|
@node-click="handleNodeClick"
|
||||||
>
|
>
|
||||||
<span class="tree-node" slot-scope="{ data }">
|
<span class="tree-node" slot-scope="{ data }">
|
||||||
<i :class="getNodeIcon(data)" class="node-icon"></i>
|
<i class="el-icon-folder node-icon"></i>
|
||||||
<span class="node-label" :title="data.name">
|
<span class="node-label" :title="data.name">
|
||||||
{{ data.name }}
|
{{ data.name }}
|
||||||
</span>
|
</span>
|
||||||
<el-tag size="mini" v-if="data.fileType === 0" type="info">
|
|
||||||
文件夹
|
|
||||||
</el-tag>
|
|
||||||
</span>
|
</span>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
<div v-else class="empty-holder">暂无可移动数据</div>
|
<div v-else class="empty-holder">暂无可移动的文件夹</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tips">
|
<div v-if="selectedFolder" class="selected-info">
|
||||||
<p class="tips-title">操作提示</p>
|
<span>已选择目标文件夹:</span>
|
||||||
<p>1. 仅文件(fileType ≠ 0)可被拖拽。</p>
|
<el-tag type="primary">{{ selectedFolder.name }}</el-tag>
|
||||||
<p>2. 仅支持拖拽到文件夹节点内。</p>
|
|
||||||
<p>3. 拖拽到目标组后会立即同步至后台。</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { moveDocCenterAPI } from '@/api/publicService/docCenter'
|
import {
|
||||||
|
moveDocCenterAPI,
|
||||||
|
getMoveAndAddCopyTreeAPI,
|
||||||
|
} from '@/api/publicService/docCenter'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Move',
|
name: 'Move',
|
||||||
props: {
|
props: {
|
||||||
selectedNode: {
|
selectedFiles: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: () => ({}),
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -56,186 +51,100 @@ export default {
|
||||||
label: 'name',
|
label: 'name',
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
moveActions: [],
|
selectedFolder: null, // 选中的目标文件夹
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
mounted() {
|
||||||
selectedNode: {
|
this.getTreeData()
|
||||||
handler(newVal) {
|
|
||||||
this.initTree(newVal)
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initTree(node) {
|
// 获取树结构数据
|
||||||
if (!node || !node.id) {
|
async getTreeData() {
|
||||||
this.treeData = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.treeData = [this.cloneNode(node)]
|
|
||||||
this.moveActions = []
|
|
||||||
},
|
|
||||||
|
|
||||||
cloneNode(node) {
|
|
||||||
const childTree = Array.isArray(node.childTree)
|
|
||||||
? node.childTree.map((child) => this.cloneNode(child))
|
|
||||||
: []
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
childTree,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getNodeIcon(data) {
|
|
||||||
return data.fileType == 0 ? 'el-icon-folder' : 'el-icon-document'
|
|
||||||
},
|
|
||||||
|
|
||||||
// 是否允许拖拽
|
|
||||||
allowDrag(draggingNode) {
|
|
||||||
const data = draggingNode?.data || {}
|
|
||||||
return data.fileType == 1
|
|
||||||
},
|
|
||||||
// 是否允许放下
|
|
||||||
allowDrop(draggingNode, dropNode, type) {
|
|
||||||
if (type !== 'inner') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const dropData = dropNode?.data || {}
|
|
||||||
if (dropData.fileType == 1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const dragData = draggingNode?.data || {}
|
|
||||||
if (!dragData.id || dragData.id === dropData.id) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
async handleNodeDrop(draggingNode, dropNode) {
|
|
||||||
const source = draggingNode?.data
|
|
||||||
const target = dropNode?.data
|
|
||||||
|
|
||||||
if (!source?.id || !target?.id) {
|
|
||||||
this.$message.error('无法获取拖拽节点信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.moveDocument(
|
|
||||||
source.id,
|
|
||||||
target.id,
|
|
||||||
source.parentId,
|
|
||||||
target.parentIds,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
async moveDocument(documentId, targetParentId, parentId, parentIds) {
|
|
||||||
// 组装参数
|
|
||||||
const params = {
|
|
||||||
id: documentId, // 自身id
|
|
||||||
newParentId: targetParentId, // 目标id
|
|
||||||
parentId: parentId, // 目标父级id
|
|
||||||
parentIds: parentIds,
|
|
||||||
}
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const res = await moveDocCenterAPI(params)
|
const res = await getMoveAndAddCopyTreeAPI()
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
if (res.code === 200) {
|
this.treeData = Array.isArray(res.data) ? res.data : []
|
||||||
this.$message.success('移动成功')
|
|
||||||
this.recordAction(documentId, targetParentId)
|
|
||||||
this.updateLocalTree(documentId, targetParentId)
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg || res.message || '移动失败')
|
this.$message.error(
|
||||||
|
res.msg || res.message || '获取文件夹树失败',
|
||||||
|
)
|
||||||
|
this.treeData = []
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$message.error(error.message || '移动失败')
|
this.$message.error(error.message || '获取文件夹树失败')
|
||||||
|
this.treeData = []
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
recordAction(documentId, targetParentId) {
|
// 点击树节点
|
||||||
const idx = this.moveActions.findIndex(
|
handleNodeClick(data) {
|
||||||
(item) => item.documentId === documentId,
|
this.selectedFolder = data
|
||||||
)
|
// 设置树节点为选中状态
|
||||||
const payload = { documentId, targetParentId }
|
this.$nextTick(() => {
|
||||||
if (idx > -1) {
|
this.$refs.treeRef.setCurrentKey(data.id)
|
||||||
this.$set(this.moveActions, idx, payload)
|
|
||||||
} else {
|
|
||||||
this.moveActions.push(payload)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateLocalTree(sourceId, targetId) {
|
|
||||||
const { node: sourceNode, parent } = this.findNodeAndParent(
|
|
||||||
this.treeData,
|
|
||||||
sourceId,
|
|
||||||
)
|
|
||||||
if (!sourceNode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent) {
|
|
||||||
const newChildren = (parent.childTree || []).filter(
|
|
||||||
(child) => child.id !== sourceId,
|
|
||||||
)
|
|
||||||
this.$set(parent, 'childTree', newChildren)
|
|
||||||
} else {
|
|
||||||
this.treeData = this.treeData.filter(
|
|
||||||
(item) => item.id !== sourceId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetNode = this.findNode(this.treeData, targetId)
|
|
||||||
if (targetNode) {
|
|
||||||
if (!Array.isArray(targetNode.childTree)) {
|
|
||||||
this.$set(targetNode, 'childTree', [])
|
|
||||||
}
|
|
||||||
targetNode.childTree.push(sourceNode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
findNodeAndParent(nodes, id, parent = null) {
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (node.id === id) {
|
|
||||||
return { node, parent }
|
|
||||||
}
|
|
||||||
const children = node.childTree || []
|
|
||||||
if (children.length > 0) {
|
|
||||||
const result = this.findNodeAndParent(children, id, node)
|
|
||||||
if (result.node) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { node: null, parent: null }
|
|
||||||
},
|
|
||||||
|
|
||||||
findNode(nodes, id) {
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (node.id === id) {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
const childResult = this.findNode(node.childTree || [], id)
|
|
||||||
if (childResult) {
|
|
||||||
return childResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
if (this.moveActions.length === 0) {
|
|
||||||
return Promise.reject(new Error('请先拖拽需要移动的文件'))
|
|
||||||
}
|
|
||||||
return Promise.resolve({
|
|
||||||
moved: this.moveActions,
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 移动文档
|
||||||
|
async moveDocument(fileId, targetParentId, parentId, parentIds) {
|
||||||
|
const params = {
|
||||||
|
id: fileId,
|
||||||
|
newParentId: targetParentId,
|
||||||
|
parentId: parentId,
|
||||||
|
parentIds: parentIds,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await moveDocCenterAPI(params)
|
||||||
|
if (res.code !== 200) {
|
||||||
|
throw new Error(res.msg || res.message || '移动失败')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提交移动操作
|
||||||
|
async submit() {
|
||||||
|
// 验证选中的文件
|
||||||
|
if (!this.selectedFiles || this.selectedFiles.length === 0) {
|
||||||
|
return Promise.reject(new Error('请先选择要移动的文件'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证选中的目标文件夹
|
||||||
|
if (!this.selectedFolder || !this.selectedFolder.id) {
|
||||||
|
return Promise.reject(new Error('请选择目标文件夹'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
// 批量移动文件
|
||||||
|
const movePromises = this.selectedFiles.map((file) =>
|
||||||
|
this.moveDocument(
|
||||||
|
file.id,
|
||||||
|
this.selectedFolder.id,
|
||||||
|
file.parentId,
|
||||||
|
file.parentIds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await Promise.all(movePromises)
|
||||||
|
|
||||||
|
this.$message.success('移动成功')
|
||||||
|
return Promise.resolve({
|
||||||
|
success: true,
|
||||||
|
movedCount: this.selectedFiles.length,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error(error.message || '移动失败')
|
||||||
|
return Promise.reject(error)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -263,6 +172,7 @@ export default {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
.node-icon {
|
.node-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
@ -270,6 +180,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-label {
|
.node-label {
|
||||||
|
flex: 1;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
@ -286,18 +197,15 @@ export default {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tips {
|
.selected-info {
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.8;
|
|
||||||
background: #fdf6ec;
|
|
||||||
border: 1px solid #f5dab1;
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border: 1px solid #bae6fd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
.tips-title {
|
align-items: center;
|
||||||
font-weight: 600;
|
gap: 8px;
|
||||||
margin-bottom: 4px;
|
font-size: 14px;
|
||||||
}
|
color: #666;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
<template>
|
||||||
|
<div class="move-container">
|
||||||
|
<div class="tree-wrapper" v-loading="loading">
|
||||||
|
<el-tree
|
||||||
|
v-if="treeData.length"
|
||||||
|
ref="treeRef"
|
||||||
|
node-key="id"
|
||||||
|
class="move-tree"
|
||||||
|
:data="treeData"
|
||||||
|
:props="treeProps"
|
||||||
|
highlight-current
|
||||||
|
default-expand-all
|
||||||
|
draggable
|
||||||
|
:allow-drag="allowDrag"
|
||||||
|
:allow-drop="allowDrop"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
@node-drop="handleNodeDrop"
|
||||||
|
>
|
||||||
|
<span class="tree-node" slot-scope="{ data }">
|
||||||
|
<i :class="getNodeIcon(data)" class="node-icon"></i>
|
||||||
|
<span class="node-label" :title="data.name">
|
||||||
|
{{ data.name }}
|
||||||
|
</span>
|
||||||
|
<el-tag size="mini" v-if="data.fileType === 0" type="info">
|
||||||
|
文件夹
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
</el-tree>
|
||||||
|
<div v-else class="empty-holder">暂无可移动数据</div>
|
||||||
|
</div>
|
||||||
|
<div class="tips">
|
||||||
|
<p class="tips-title">操作提示</p>
|
||||||
|
<p>1. 仅文件(fileType ≠ 0)可被拖拽。</p>
|
||||||
|
<p>2. 仅支持拖拽到文件夹节点内。</p>
|
||||||
|
<p>3. 拖拽到目标组后会立即同步至后台。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { moveDocCenterAPI } from '@/api/publicService/docCenter'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Move',
|
||||||
|
props: {
|
||||||
|
selectedNode: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
treeData: [],
|
||||||
|
treeProps: {
|
||||||
|
children: 'childTree',
|
||||||
|
label: 'name',
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
moveActions: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedNode: {
|
||||||
|
handler(newVal) {
|
||||||
|
this.initTree(newVal)
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initTree(node) {
|
||||||
|
if (!node || !node.id) {
|
||||||
|
this.treeData = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.treeData = [this.cloneNode(node)]
|
||||||
|
this.moveActions = []
|
||||||
|
},
|
||||||
|
|
||||||
|
cloneNode(node) {
|
||||||
|
const childTree = Array.isArray(node.childTree)
|
||||||
|
? node.childTree.map((child) => this.cloneNode(child))
|
||||||
|
: []
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
childTree,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getNodeIcon(data) {
|
||||||
|
return data.fileType == 0 ? 'el-icon-folder' : 'el-icon-document'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 是否允许拖拽
|
||||||
|
allowDrag(draggingNode) {
|
||||||
|
const data = draggingNode?.data || {}
|
||||||
|
return data.fileType == 1
|
||||||
|
},
|
||||||
|
// 是否允许放下
|
||||||
|
allowDrop(draggingNode, dropNode, type) {
|
||||||
|
if (type !== 'inner') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const dropData = dropNode?.data || {}
|
||||||
|
if (dropData.fileType == 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragData = draggingNode?.data || {}
|
||||||
|
if (!dragData.id || dragData.id === dropData.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleNodeDrop(draggingNode, dropNode) {
|
||||||
|
const source = draggingNode?.data
|
||||||
|
const target = dropNode?.data
|
||||||
|
|
||||||
|
if (!source?.id || !target?.id) {
|
||||||
|
this.$message.error('无法获取拖拽节点信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.moveDocument(
|
||||||
|
source.id,
|
||||||
|
target.id,
|
||||||
|
source.parentId,
|
||||||
|
target.parentIds,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
async moveDocument(documentId, targetParentId, parentId, parentIds) {
|
||||||
|
// 组装参数
|
||||||
|
const params = {
|
||||||
|
id: documentId, // 自身id
|
||||||
|
newParentId: targetParentId, // 目标id
|
||||||
|
parentId: parentId, // 目标父级id
|
||||||
|
parentIds: parentIds,
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const res = await moveDocCenterAPI(params)
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.$message.success('移动成功')
|
||||||
|
this.recordAction(documentId, targetParentId)
|
||||||
|
this.updateLocalTree(documentId, targetParentId)
|
||||||
|
} else {
|
||||||
|
this.$message.error(res.msg || res.message || '移动失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error(error.message || '移动失败')
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
recordAction(documentId, targetParentId) {
|
||||||
|
const idx = this.moveActions.findIndex(
|
||||||
|
(item) => item.documentId === documentId,
|
||||||
|
)
|
||||||
|
const payload = { documentId, targetParentId }
|
||||||
|
if (idx > -1) {
|
||||||
|
this.$set(this.moveActions, idx, payload)
|
||||||
|
} else {
|
||||||
|
this.moveActions.push(payload)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLocalTree(sourceId, targetId) {
|
||||||
|
const { node: sourceNode, parent } = this.findNodeAndParent(
|
||||||
|
this.treeData,
|
||||||
|
sourceId,
|
||||||
|
)
|
||||||
|
if (!sourceNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const newChildren = (parent.childTree || []).filter(
|
||||||
|
(child) => child.id !== sourceId,
|
||||||
|
)
|
||||||
|
this.$set(parent, 'childTree', newChildren)
|
||||||
|
} else {
|
||||||
|
this.treeData = this.treeData.filter(
|
||||||
|
(item) => item.id !== sourceId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNode = this.findNode(this.treeData, targetId)
|
||||||
|
if (targetNode) {
|
||||||
|
if (!Array.isArray(targetNode.childTree)) {
|
||||||
|
this.$set(targetNode, 'childTree', [])
|
||||||
|
}
|
||||||
|
targetNode.childTree.push(sourceNode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
findNodeAndParent(nodes, id, parent = null) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === id) {
|
||||||
|
return { node, parent }
|
||||||
|
}
|
||||||
|
const children = node.childTree || []
|
||||||
|
if (children.length > 0) {
|
||||||
|
const result = this.findNodeAndParent(children, id, node)
|
||||||
|
if (result.node) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { node: null, parent: null }
|
||||||
|
},
|
||||||
|
|
||||||
|
findNode(nodes, id) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === id) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
const childResult = this.findNode(node.childTree || [], id)
|
||||||
|
if (childResult) {
|
||||||
|
return childResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
if (this.moveActions.length === 0) {
|
||||||
|
return Promise.reject(new Error('请先拖拽需要移动的文件'))
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
moved: this.moveActions,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.move-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-wrapper {
|
||||||
|
min-height: 320px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.move-tree {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.node-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-holder {
|
||||||
|
height: 180px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
background: #fdf6ec;
|
||||||
|
border: 1px solid #f5dab1;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.tips-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue