This commit is contained in:
BianLzhaoMin 2025-11-18 16:32:27 +08:00
parent 1898d1b730
commit d5adbd8a1c
7 changed files with 839 additions and 357 deletions

View File

@ -115,3 +115,12 @@ export function saveSharePermissionAPI(data = {}) {
data, data,
}) })
} }
// 移动和添加副本时获取的树结构
export function getMoveAndAddCopyTreeAPI(data = {}) {
return request({
url: '/screen/document/getRemoveTree',
method: 'POST',
data,
})
}

View File

@ -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'
} }

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>