接口调试
This commit is contained in:
parent
b18f424714
commit
ae56e3cabc
|
|
@ -44,3 +44,74 @@ export function uploadFileTagAPI(data = {}) {
|
|||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除文档中心
|
||||
export function deleteDocCenterAPI(data = {}) {
|
||||
return request({
|
||||
url: '/screen/document/delete',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 移动文档/文件
|
||||
export function moveDocCenterAPI(data = {}) {
|
||||
return request({
|
||||
url: '/screen/document/move',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 复制文档/文件
|
||||
export function copyDocCenterAPI(data = {}) {
|
||||
return request({
|
||||
url: '/screen/document/copy',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 共享-获取部门树
|
||||
export function getShareDeptTreeAPI() {
|
||||
return request({
|
||||
url: '/system/user/deptTree',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// 共享-获取角色列表
|
||||
export function getShareRoleListAPI(params = { pageSize: 1000, pageNum: 1 }) {
|
||||
return request({
|
||||
url: '/system/role/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// 共享-根据部门获取人员列表
|
||||
export function getShareUserListAPI(params = {}) {
|
||||
return request({
|
||||
url: '/system/user/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// 共享-根据角色获取人员列表
|
||||
export function getShareUserListByRoleAPI(params = {}) {
|
||||
return request({
|
||||
url: '/system/role/authUser/allocatedList',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// 共享-提交授权
|
||||
export function saveSharePermissionAPI(data = {}) {
|
||||
return request({
|
||||
url: '/screen/document/share',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,33 +84,20 @@
|
|||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
:type="getTypeTagType(scope.row.type)"
|
||||
:type="getTypeTagType(scope.row.fileType)"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.type }}
|
||||
{{ scope.row.fileType == 0 ? '文件夹' : '文件' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="tags"
|
||||
prop="labName"
|
||||
label="标签"
|
||||
width="150"
|
||||
align="center"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<div class="tags-cell">
|
||||
<el-tag
|
||||
v-for="tag in scope.row.tags"
|
||||
:key="tag"
|
||||
size="mini"
|
||||
type="info"
|
||||
class="tag-item"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
|
|
@ -189,6 +176,7 @@
|
|||
<component
|
||||
ref="componentRef"
|
||||
:selectedNode="selectedNode"
|
||||
v-bind="dialogConfig.outerComponentProps"
|
||||
:is="dialogConfig.outerComponent"
|
||||
/>
|
||||
|
||||
|
|
@ -230,7 +218,10 @@ import Move from './tableCom/move.vue'
|
|||
import AddCopy from './tableCom/addCopy.vue'
|
||||
import TagFilter from './tableCom/tagFilter.vue'
|
||||
|
||||
import { getDocCenterRightListAPI } from '@/api/publicService/docCenter'
|
||||
import {
|
||||
getDocCenterRightListAPI,
|
||||
deleteDocCenterAPI,
|
||||
} from '@/api/publicService/docCenter'
|
||||
export default {
|
||||
name: 'RightTable',
|
||||
props: {
|
||||
|
|
@ -267,6 +258,7 @@ export default {
|
|||
searchValue: '',
|
||||
parentId: '',
|
||||
type: '',
|
||||
labelIds: '',
|
||||
},
|
||||
tableData: [],
|
||||
|
||||
|
|
@ -327,6 +319,7 @@ export default {
|
|||
outerVisible: false,
|
||||
outerWidth: '50%',
|
||||
outerComponent: null,
|
||||
outerComponentProps: {},
|
||||
},
|
||||
// 用于下载的文件列表
|
||||
selectedFilesForDownload: [],
|
||||
|
|
@ -376,7 +369,7 @@ export default {
|
|||
|
||||
// 获取类型标签样式
|
||||
getTypeTagType(type) {
|
||||
if (type === '文件夹') {
|
||||
if (type == 0) {
|
||||
return 'warning'
|
||||
} else if (type === 'docx') {
|
||||
return 'primary'
|
||||
|
|
@ -409,6 +402,7 @@ export default {
|
|||
this.dialogConfig.outerTitle = '新建文档夹'
|
||||
this.dialogConfig.outerVisible = true
|
||||
this.dialogConfig.outerComponent = 'AddWord'
|
||||
this.dialogConfig.outerComponentProps = {}
|
||||
},
|
||||
|
||||
// 上传
|
||||
|
|
@ -417,6 +411,7 @@ export default {
|
|||
this.dialogConfig.outerTitle = '上传文件'
|
||||
this.dialogConfig.outerVisible = true
|
||||
this.dialogConfig.outerComponent = 'Upload'
|
||||
this.dialogConfig.outerComponentProps = {}
|
||||
},
|
||||
|
||||
// 移动
|
||||
|
|
@ -425,6 +420,7 @@ export default {
|
|||
this.dialogConfig.outerTitle = '移动'
|
||||
this.dialogConfig.outerVisible = true
|
||||
this.dialogConfig.outerComponent = 'Move'
|
||||
this.dialogConfig.outerComponentProps = {}
|
||||
},
|
||||
|
||||
// 删除
|
||||
|
|
@ -444,8 +440,20 @@ export default {
|
|||
type: 'warning',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('删除功能待实现')
|
||||
.then(async () => {
|
||||
// this.$modal.msgSuccess('删除功能待实现')
|
||||
|
||||
const res = await deleteDocCenterAPI({
|
||||
id: this.selectedRows.map((row) => row.id).join(','),
|
||||
parentId: this.selectedNode?.id,
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.getTableList()
|
||||
} else {
|
||||
this.$modal.msgError(res.message)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$modal.msgInfo('已取消删除')
|
||||
|
|
@ -481,6 +489,10 @@ export default {
|
|||
}))
|
||||
this.dialogConfig.outerTitle = '共享'
|
||||
this.dialogConfig.outerVisible = true
|
||||
this.dialogConfig.outerComponent = 'SharePermissionForm'
|
||||
this.dialogConfig.outerComponentProps = {
|
||||
selectedFiles: this.selectedFilesForShare,
|
||||
}
|
||||
},
|
||||
|
||||
// 添加副本
|
||||
|
|
@ -488,6 +500,8 @@ export default {
|
|||
// this.$emit('add-copy', this.selectedRows)
|
||||
this.dialogConfig.outerTitle = '添加副本'
|
||||
this.dialogConfig.outerVisible = true
|
||||
this.dialogConfig.outerComponent = 'AddCopy'
|
||||
this.dialogConfig.outerComponentProps = {}
|
||||
},
|
||||
|
||||
// 查看
|
||||
|
|
@ -539,7 +553,7 @@ export default {
|
|||
const res = await this.$refs.componentRef.submit()
|
||||
console.log(res, '新增结果')
|
||||
// this.$modal.msgSuccess('操作成功')
|
||||
// this.dialogConfig.outerVisible = false
|
||||
this.dialogConfig.outerVisible = false
|
||||
// 刷新列表
|
||||
this.getTableList()
|
||||
|
||||
|
|
@ -577,16 +591,20 @@ export default {
|
|||
},
|
||||
|
||||
// 处理标签查询
|
||||
handleTagQuery(selectedTags) {
|
||||
console.log('选中的标签:', selectedTags)
|
||||
// 这里可以根据选中的标签进行数据筛选
|
||||
// this.$modal.msgSuccess('标签筛选功能待实现')
|
||||
handleTagQuery(tagIds) {
|
||||
this.queryParams.pageNum = 1
|
||||
this.queryParams.labelIds = Array.isArray(tagIds)
|
||||
? tagIds.join(',')
|
||||
: ''
|
||||
this.getTableList()
|
||||
this.tagFilterVisible = false
|
||||
},
|
||||
|
||||
// 处理标签重置
|
||||
handleTagReset() {
|
||||
console.log('重置标签筛选')
|
||||
this.queryParams.labelIds = ''
|
||||
this.queryParams.pageNum = 1
|
||||
this.getTableList()
|
||||
this.$modal.msgInfo('已重置标签筛选')
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<div class="tags-container" v-loading="loading">
|
||||
<div
|
||||
v-for="category in filteredCategories"
|
||||
:key="category.typeId"
|
||||
:key="category.key"
|
||||
class="category-section"
|
||||
>
|
||||
<div class="category-title"># {{ category.typeName }} #</div>
|
||||
|
|
@ -124,13 +124,22 @@ export default {
|
|||
// 初始化选中的标签
|
||||
initSelectedTags() {
|
||||
if (this.selectedTags && this.selectedTags.length > 0) {
|
||||
this.selectedTagIds = this.selectedTags.map((tag) => {
|
||||
if (typeof tag === 'object') {
|
||||
// 使用 categoryName_labelName 作为唯一标识
|
||||
return tag.id || `${tag.typeName}_${tag.labelName}`
|
||||
}
|
||||
return tag
|
||||
})
|
||||
this.selectedTagIds = this.selectedTags
|
||||
.map((tag) => {
|
||||
if (typeof tag === 'object') {
|
||||
return (
|
||||
(tag.id ||
|
||||
tag.typeId ||
|
||||
tag.labelId ||
|
||||
tag.value ||
|
||||
tag.labelCode) ??
|
||||
''
|
||||
)
|
||||
}
|
||||
return tag ?? ''
|
||||
})
|
||||
.filter((id) => id !== '')
|
||||
.map((id) => String(id))
|
||||
} else {
|
||||
this.selectedTagIds = []
|
||||
}
|
||||
|
|
@ -150,21 +159,32 @@ export default {
|
|||
|
||||
// 转换为分类数组格式
|
||||
this.categories = Object.keys(tagsData)
|
||||
.map((categoryName) => {
|
||||
.map((categoryName, index) => {
|
||||
const tagList = tagsData[categoryName] || []
|
||||
// 从第一个标签获取分类的 typeId(同一分类下的标签 typeId 相同)
|
||||
const categoryTypeId =
|
||||
tagList.length > 0 ? tagList[0].typeId : null
|
||||
|
||||
return {
|
||||
typeId: categoryTypeId,
|
||||
key: `${categoryName}_${index}`,
|
||||
typeName: categoryName,
|
||||
tags: tagList.map((tag) => ({
|
||||
id: `${categoryName}_${tag.labelName}`,
|
||||
labelName: tag.labelName,
|
||||
typeId: tag.typeId,
|
||||
typeName: categoryName,
|
||||
})),
|
||||
tags: tagList
|
||||
.map((tag) => {
|
||||
const rawId =
|
||||
tag.id ??
|
||||
tag.typeId ??
|
||||
tag.labelId ??
|
||||
tag.value ??
|
||||
tag.labelCode
|
||||
if (!rawId) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
id: String(rawId),
|
||||
labelName:
|
||||
tag.labelName ?? tag.name ?? '',
|
||||
typeId: tag.typeId ?? rawId,
|
||||
typeName: categoryName,
|
||||
raw: tag,
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
}
|
||||
})
|
||||
.filter((category) => category.tags.length > 0)
|
||||
|
|
@ -182,16 +202,17 @@ export default {
|
|||
|
||||
// 判断标签是否被选中
|
||||
isTagSelected(tagId) {
|
||||
return this.selectedTagIds.includes(tagId)
|
||||
return this.selectedTagIds.includes(String(tagId))
|
||||
},
|
||||
|
||||
// 切换标签选中状态
|
||||
toggleTag(tag) {
|
||||
const index = this.selectedTagIds.indexOf(tag.id)
|
||||
const tagId = String(tag.id)
|
||||
const index = this.selectedTagIds.indexOf(tagId)
|
||||
if (index > -1) {
|
||||
this.selectedTagIds.splice(index, 1)
|
||||
} else {
|
||||
this.selectedTagIds.push(tag.id)
|
||||
this.selectedTagIds.push(tagId)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -199,15 +220,15 @@ export default {
|
|||
getSelectedTags() {
|
||||
const selectedTags = []
|
||||
const typeIdSet = new Set() // 用于收集去重后的 typeId
|
||||
|
||||
this.categories.forEach((category) => {
|
||||
category.tags.forEach((tag) => {
|
||||
if (this.selectedTagIds.includes(tag.id)) {
|
||||
if (this.selectedTagIds.includes(String(tag.id))) {
|
||||
selectedTags.push({
|
||||
id: tag.id,
|
||||
id: String(tag.id),
|
||||
labelName: tag.labelName,
|
||||
typeId: tag.typeId,
|
||||
typeName: tag.typeName,
|
||||
raw: tag.raw,
|
||||
})
|
||||
// 收集 typeId
|
||||
if (tag.typeId) {
|
||||
|
|
@ -217,9 +238,14 @@ export default {
|
|||
})
|
||||
})
|
||||
|
||||
const tagIds = selectedTags.map((tag) => tag.id)
|
||||
const tagLabels = selectedTags.map((tag) => tag.labelName)
|
||||
|
||||
// 返回选中的标签和去重后的 typeId 数组
|
||||
return {
|
||||
tags: selectedTags,
|
||||
tagIds,
|
||||
tagLabels,
|
||||
typeIds: Array.from(typeIdSet),
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,247 @@
|
|||
<template>
|
||||
<div> 添加副本 </div>
|
||||
<!-- 添加副本 -->
|
||||
<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>
|
||||
export default {}
|
||||
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></style>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,303 @@
|
|||
<template>
|
||||
<div> 移动 </div>
|
||||
<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>
|
||||
export default {}
|
||||
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></style>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -1,74 +1,220 @@
|
|||
<template>
|
||||
<div class="permission-form">
|
||||
<div class="share-permission-form">
|
||||
<section class="share-section">
|
||||
<div class="section-title">共享文件</div>
|
||||
<div class="file-list" v-if="selectedFiles.length">
|
||||
<el-tag
|
||||
size="mini"
|
||||
effect="plain"
|
||||
v-for="file in selectedFiles"
|
||||
:key="file.id"
|
||||
>
|
||||
{{ file.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-empty
|
||||
v-else
|
||||
description="尚未选择文件"
|
||||
:image-size="80"
|
||||
></el-empty>
|
||||
</section>
|
||||
|
||||
<el-form
|
||||
ref="permissionForm"
|
||||
ref="shareFormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
class="permission-form-content"
|
||||
label-width="90px"
|
||||
class="share-form"
|
||||
>
|
||||
<!-- 授权对象 -->
|
||||
<el-form-item
|
||||
label="授权对象"
|
||||
prop="authorizedObject"
|
||||
class="form-item"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.authorizedObject"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleAuthorizedObjectChange"
|
||||
<el-form-item label="授权对象" prop="shareTargets">
|
||||
<el-input
|
||||
readonly
|
||||
placeholder="请选择授权对象"
|
||||
:value="shareTargetNames"
|
||||
@focus="openUserPicker"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in authorizedObjectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<i
|
||||
slot="suffix"
|
||||
class="el-input__icon el-icon-more"
|
||||
@click.stop="openUserPicker"
|
||||
></i>
|
||||
</el-input>
|
||||
<div class="selected-user-tags" v-if="form.shareTargets.length">
|
||||
<el-tag
|
||||
v-for="user in form.shareTargets"
|
||||
:key="user.id"
|
||||
closable
|
||||
@close="removeSelectedUser(user.userId)"
|
||||
>
|
||||
{{ user.nickName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 权限设置 -->
|
||||
<el-form-item label="权限" prop="permission" class="form-item">
|
||||
<el-radio-group
|
||||
v-model="form.permission"
|
||||
@change="handlePermissionChange"
|
||||
>
|
||||
<div class="permission-options">
|
||||
<el-radio label="view" class="permission-radio">
|
||||
仅可查看
|
||||
</el-radio>
|
||||
<el-radio
|
||||
label="view_download"
|
||||
class="permission-radio"
|
||||
>
|
||||
可查看/下载
|
||||
</el-radio>
|
||||
<el-radio label="edit" class="permission-radio">
|
||||
可编辑
|
||||
</el-radio>
|
||||
<el-radio label="manage" class="permission-radio">
|
||||
可管理
|
||||
</el-radio>
|
||||
</div>
|
||||
<el-form-item label="权限类型" prop="permission">
|
||||
<el-radio-group v-model="form.permission" size="small">
|
||||
<el-radio-button label="1">仅可查看</el-radio-button>
|
||||
<el-radio-button label="2">可查看/下载</el-radio-button>
|
||||
<el-radio-button label="3">可编辑</el-radio-button>
|
||||
<el-radio-button label="4">可管理</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-dialog
|
||||
title="人员选择"
|
||||
append-to-body
|
||||
:visible.sync="userPickerVisible"
|
||||
width="80%"
|
||||
class="user-picker-dialog"
|
||||
>
|
||||
<div class="user-picker">
|
||||
<div class="left-panel">
|
||||
<el-tabs
|
||||
v-model="activeSelectorTab"
|
||||
class="selector-tabs"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<el-tab-pane label="部门" name="dept">
|
||||
<div class="tab-toolbar">
|
||||
<el-input
|
||||
v-model="deptKeyword"
|
||||
size="mini"
|
||||
placeholder="搜索部门"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
/>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
class="toggle-btn"
|
||||
@click="toggleDeptExpand"
|
||||
>
|
||||
{{ deptTreeExpanded ? '折叠' : '展开' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-tree
|
||||
ref="deptTreeRef"
|
||||
class="dept-tree"
|
||||
:data="deptTree"
|
||||
:props="deptTreeProps"
|
||||
node-key="id"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false"
|
||||
:default-expand-all="false"
|
||||
:filter-node-method="filterDeptNode"
|
||||
:current-node-key="activeDeptId"
|
||||
v-loading="deptLoading"
|
||||
@node-click="handleDeptClick"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="角色" name="role">
|
||||
<div class="tab-toolbar">
|
||||
<el-input
|
||||
v-model="roleKeyword"
|
||||
size="mini"
|
||||
placeholder="搜索角色"
|
||||
clearable
|
||||
prefix-icon="el-icon-search"
|
||||
/>
|
||||
</div>
|
||||
<el-scrollbar
|
||||
class="role-list"
|
||||
v-loading="roleLoading"
|
||||
>
|
||||
<div
|
||||
v-for="role in filteredRoleList"
|
||||
:key="role.id"
|
||||
class="role-item"
|
||||
:class="{
|
||||
active: activeRoleId === role.id,
|
||||
}"
|
||||
@click="handleRoleClick(role)"
|
||||
>
|
||||
{{ role.name }}
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="
|
||||
!filteredRoleList.length && !roleLoading
|
||||
"
|
||||
:image-size="60"
|
||||
description="暂无角色"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">
|
||||
<span>人员列表</span>
|
||||
<!-- <el-input
|
||||
size="mini"
|
||||
placeholder="搜索姓名"
|
||||
prefix-icon="el-icon-search"
|
||||
v-model="userKeyword"
|
||||
@keyup.enter.native="fetchUserList"
|
||||
clearable
|
||||
@clear="fetchUserList"
|
||||
/> -->
|
||||
</div>
|
||||
<el-table
|
||||
ref="userTableRef"
|
||||
:data="userList"
|
||||
height="320"
|
||||
border
|
||||
v-loading="userTableLoading"
|
||||
@selection-change="handleUserSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="60" />
|
||||
<el-table-column type="index" label="序号" width="80" />
|
||||
<el-table-column
|
||||
prop="nickName"
|
||||
label="姓名"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-title selected-title">
|
||||
已选人员({{ tempSelectedUsers.length }})
|
||||
</div>
|
||||
<div class="selected-user-tags" v-if="tempSelectedUsers.length">
|
||||
<el-tag
|
||||
v-for="user in tempSelectedUsers"
|
||||
:key="user.id"
|
||||
closable
|
||||
@close="removeTempSelected(user.userId)"
|
||||
>
|
||||
{{ user.nickName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-empty v-else description="暂未选择人员" :image-size="60" />
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="userPickerVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmUserSelection">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getShareDeptTreeAPI,
|
||||
getShareRoleListAPI,
|
||||
getShareUserListAPI,
|
||||
getShareUserListByRoleAPI,
|
||||
saveSharePermissionAPI,
|
||||
} from '@/api/publicService/docCenter'
|
||||
|
||||
export default {
|
||||
name: 'SharePermissionForm',
|
||||
props: {
|
||||
// 是否显示弹框
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 选中的文件列表
|
||||
selectedFiles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
|
|
@ -78,13 +224,15 @@ export default {
|
|||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
authorizedObject: '', // 授权对象
|
||||
permission: 'view', // 权限,默认仅可查看
|
||||
shareTargets: [],
|
||||
permission: '3',
|
||||
},
|
||||
rules: {
|
||||
authorizedObject: [
|
||||
shareTargets: [
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
min: 1,
|
||||
message: '请选择授权对象',
|
||||
trigger: 'change',
|
||||
},
|
||||
|
|
@ -92,136 +240,435 @@ export default {
|
|||
permission: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择权限',
|
||||
message: '请选择权限类型',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
},
|
||||
// 授权对象选项
|
||||
authorizedObjectOptions: [
|
||||
{ value: 'user_001', label: '张三' },
|
||||
{ value: 'user_002', label: '李四' },
|
||||
{ value: 'user_003', label: '王五' },
|
||||
{ value: 'dept_001', label: '技术部' },
|
||||
{ value: 'dept_002', label: '产品部' },
|
||||
{ value: 'role_001', label: '管理员' },
|
||||
{ value: 'role_002', label: '编辑者' },
|
||||
],
|
||||
userPickerVisible: false,
|
||||
deptTree: [],
|
||||
deptTreeProps: {
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
},
|
||||
deptLoading: false,
|
||||
roleList: [],
|
||||
roleLoading: false,
|
||||
activeDeptId: null,
|
||||
activeRoleId: null,
|
||||
userKeyword: '',
|
||||
deptKeyword: '',
|
||||
roleKeyword: '',
|
||||
activeSelectorTab: 'dept',
|
||||
deptTreeExpanded: true,
|
||||
userList: [],
|
||||
userTableLoading: false,
|
||||
tempSelectedUsers: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shareTargetNames() {
|
||||
if (!this.form.shareTargets.length) return ''
|
||||
return this.form.shareTargets
|
||||
.map((item) => item.nickName)
|
||||
.join(',')
|
||||
},
|
||||
filteredRoleList() {
|
||||
if (!this.roleKeyword.trim()) {
|
||||
return this.roleList
|
||||
}
|
||||
const keyword = this.roleKeyword.trim().toLowerCase()
|
||||
return this.roleList.filter((role) =>
|
||||
(role.name || '').toLowerCase().includes(keyword),
|
||||
)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newVal) {
|
||||
if (newVal) {
|
||||
this.resetForm()
|
||||
deptKeyword(val) {
|
||||
if (this.$refs.deptTreeRef) {
|
||||
this.$refs.deptTreeRef.filter(val)
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initStaticData()
|
||||
},
|
||||
methods: {
|
||||
// 重置表单
|
||||
resetForm() {
|
||||
this.form = {
|
||||
authorizedObject: '',
|
||||
permission: 'view',
|
||||
async initStaticData() {
|
||||
this.fetchDeptTree()
|
||||
this.fetchRoleList()
|
||||
},
|
||||
openUserPicker() {
|
||||
this.tempSelectedUsers = [...this.form.shareTargets]
|
||||
this.userPickerVisible = true
|
||||
if (!this.deptTree.length) {
|
||||
this.fetchDeptTree()
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.permissionForm &&
|
||||
this.$refs.permissionForm.clearValidate()
|
||||
if (!this.roleList.length) {
|
||||
this.fetchRoleList()
|
||||
}
|
||||
if (this.activeDeptId && this.activeRoleId) {
|
||||
this.fetchUserList()
|
||||
}
|
||||
},
|
||||
async fetchDeptTree() {
|
||||
this.deptLoading = true
|
||||
try {
|
||||
const res = await getShareDeptTreeAPI()
|
||||
const treeData = res.data || res.rows || []
|
||||
this.deptTree = this.normalizeDeptTree(treeData)
|
||||
if (!this.activeDeptId && this.deptTree.length) {
|
||||
this.activeDeptId = this.deptTree[0].id
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.deptTreeRef && this.deptKeyword) {
|
||||
this.$refs.deptTreeRef.filter(this.deptKeyword)
|
||||
}
|
||||
})
|
||||
if (this.activeDeptId && this.activeRoleId) {
|
||||
this.fetchUserList()
|
||||
}
|
||||
} finally {
|
||||
this.deptLoading = false
|
||||
}
|
||||
},
|
||||
async fetchRoleList() {
|
||||
this.roleLoading = true
|
||||
try {
|
||||
const res = await getShareRoleListAPI()
|
||||
const list = res.rows || res.data || []
|
||||
this.roleList = list.map((item) => ({
|
||||
id: item.roleId ?? item.id,
|
||||
name: item.roleName ?? item.name,
|
||||
}))
|
||||
if (!this.activeRoleId && this.roleList.length) {
|
||||
this.activeRoleId = this.roleList[0].id
|
||||
}
|
||||
if (this.activeDeptId && this.activeRoleId) {
|
||||
this.fetchUserList()
|
||||
}
|
||||
} finally {
|
||||
this.roleLoading = false
|
||||
}
|
||||
},
|
||||
async fetchUserList() {
|
||||
if (!this.activeDeptId && !this.activeRoleId) return
|
||||
this.userTableLoading = true
|
||||
try {
|
||||
// const res = await getShareUserListAPI({
|
||||
// deptId: this.activeDeptId,
|
||||
// roleId: this.activeRoleId,
|
||||
// keyword: this.userKeyword.trim(),
|
||||
// })
|
||||
|
||||
const API =
|
||||
this.activeSelectorTab == 'dept'
|
||||
? getShareUserListAPI
|
||||
: getShareUserListByRoleAPI
|
||||
const params =
|
||||
this.activeSelectorTab == 'dept'
|
||||
? {
|
||||
pageSize: 1000,
|
||||
pageNum: 1,
|
||||
deptId: this.activeDeptId,
|
||||
}
|
||||
: {
|
||||
pageSize: 1000,
|
||||
pageNum: 1,
|
||||
roleId: this.activeRoleId,
|
||||
}
|
||||
const res = await API(params)
|
||||
this.userList = res.rows || []
|
||||
this.syncTableSelection()
|
||||
} finally {
|
||||
this.userTableLoading = false
|
||||
}
|
||||
},
|
||||
handleDeptClick(node) {
|
||||
this.activeDeptId = node.id
|
||||
this.activeSelectorTab = 'dept'
|
||||
this.fetchUserList()
|
||||
},
|
||||
handleRoleClick(role) {
|
||||
this.activeRoleId = role.id
|
||||
this.activeSelectorTab = 'role'
|
||||
this.fetchUserList()
|
||||
},
|
||||
handleUserSelectionChange(selection) {
|
||||
const currentIds = this.userList.map((item) => item.userId)
|
||||
this.tempSelectedUsers = this.tempSelectedUsers.filter((user) => {
|
||||
if (!currentIds.includes(user.userId)) return true
|
||||
return selection.some((item) => item.userId === user.userId)
|
||||
})
|
||||
selection.forEach((item) => {
|
||||
if (
|
||||
!this.tempSelectedUsers.some(
|
||||
(user) => user.userId === item.userId,
|
||||
)
|
||||
) {
|
||||
this.tempSelectedUsers.push(item)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 授权对象变化
|
||||
handleAuthorizedObjectChange(value) {
|
||||
console.log('授权对象变化:', value)
|
||||
removeTempSelected(userId) {
|
||||
this.tempSelectedUsers = this.tempSelectedUsers.filter(
|
||||
(item) => item.userId !== userId,
|
||||
)
|
||||
if (this.$refs.userTableRef) {
|
||||
const target = this.userList.find(
|
||||
(item) => item.userId === userId,
|
||||
)
|
||||
if (target) {
|
||||
this.$refs.userTableRef.toggleRowSelection(target, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmUserSelection() {
|
||||
this.form.shareTargets = [...this.tempSelectedUsers]
|
||||
this.$refs.shareFormRef.clearValidate('shareTargets')
|
||||
this.userPickerVisible = false
|
||||
},
|
||||
removeSelectedUser(userId) {
|
||||
this.form.shareTargets = this.form.shareTargets.filter(
|
||||
(item) => item.userId !== userId,
|
||||
)
|
||||
this.tempSelectedUsers = this.tempSelectedUsers.filter(
|
||||
(item) => item.userId !== userId,
|
||||
)
|
||||
},
|
||||
syncTableSelection() {
|
||||
this.$nextTick(() => {
|
||||
if (!this.$refs.userTableRef) return
|
||||
this.$refs.userTableRef.clearSelection()
|
||||
this.userList.forEach((user) => {
|
||||
if (
|
||||
this.tempSelectedUsers.some(
|
||||
(item) => item.userId === user.userId,
|
||||
)
|
||||
) {
|
||||
this.$refs.userTableRef.toggleRowSelection(user, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
filterDeptNode(value, data) {
|
||||
if (!value) return true
|
||||
return (data.label || '').includes(value)
|
||||
},
|
||||
toggleDeptExpand() {
|
||||
if (!this.$refs.deptTreeRef || !this.$refs.deptTreeRef.store) {
|
||||
return
|
||||
}
|
||||
const rootNodes = this.$refs.deptTreeRef.store.root.childNodes || []
|
||||
const allExpanded = this.isAllDeptExpanded(rootNodes)
|
||||
if (allExpanded) {
|
||||
this.collapseNodes(rootNodes)
|
||||
this.deptTreeExpanded = false
|
||||
} else {
|
||||
this.expandNodes(rootNodes)
|
||||
this.deptTreeExpanded = true
|
||||
}
|
||||
},
|
||||
expandNodes(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
node.expanded = true
|
||||
if (node.childNodes && node.childNodes.length) {
|
||||
this.expandNodes(node.childNodes)
|
||||
}
|
||||
})
|
||||
},
|
||||
collapseNodes(nodes) {
|
||||
nodes.forEach((node) => {
|
||||
node.expanded = false
|
||||
if (node.childNodes && node.childNodes.length) {
|
||||
this.collapseNodes(node.childNodes)
|
||||
}
|
||||
})
|
||||
},
|
||||
isAllDeptExpanded(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.childNodes && node.childNodes.length) {
|
||||
if (
|
||||
!node.expanded ||
|
||||
!this.isAllDeptExpanded(node.childNodes)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
normalizeDeptTree(list = []) {
|
||||
return list.map((item) => ({
|
||||
id: item.id ?? item.deptId,
|
||||
label: item.label ?? item.name ?? item.deptName,
|
||||
children: item.children
|
||||
? this.normalizeDeptTree(item.children)
|
||||
: [],
|
||||
}))
|
||||
},
|
||||
async submit() {
|
||||
await new Promise((resolve, reject) => {
|
||||
this.$refs.shareFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error('表单验证失败'))
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!this.selectedFiles.length) {
|
||||
throw new Error('未选择需要共享的文件')
|
||||
}
|
||||
const payload = {
|
||||
id: this.selectedFiles.map((file) => file.id).join(','),
|
||||
userIds: this.form.shareTargets
|
||||
.map((item) => item.userId)
|
||||
.join(','),
|
||||
auth: this.form.permission,
|
||||
}
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await saveSharePermissionAPI(payload)
|
||||
if (res.code !== 200) {
|
||||
throw new Error(res.msg || '共享失败')
|
||||
}
|
||||
this.$message.success('共享成功')
|
||||
return res
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 权限变化
|
||||
handlePermissionChange(value) {
|
||||
console.log('权限变化:', value)
|
||||
handleTabClick(tab) {
|
||||
// this.activeSelectorTab = tab.name
|
||||
// this.fetchUserList()
|
||||
// console.log(tab)
|
||||
this.activeDeptId = null
|
||||
this.activeRoleId = null
|
||||
this.userList = []
|
||||
this.syncTableSelection()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.permission-form {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
|
||||
.permission-form-content {
|
||||
.form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
line-height: normal;
|
||||
}
|
||||
.share-permission-form {
|
||||
.share-section {
|
||||
margin-bottom: 16px;
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.permission-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
|
||||
.permission-radio {
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
:deep(.el-radio__input.is-checked + .el-radio__label) {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-radio__input.is-checked .el-radio__inner) {
|
||||
background-color: #409eff;
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
:deep(.el-radio__label) {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
.share-form {
|
||||
background: #f9fbff;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
.selected-user-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.user-picker {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
.left-panel {
|
||||
width: 320px;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
padding-right: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
flex-direction: column;
|
||||
}
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
min-width: 80px;
|
||||
.selector-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
::v-deep .el-tabs__header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
::v-deep .el-tabs__content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
::v-deep .el-tab-pane {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
padding: 0;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.dept-tree {
|
||||
flex: 1;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.role-list {
|
||||
flex: 1;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
.role-item {
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
&.active {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.permission-form {
|
||||
.permission-options {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.selected-title {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.user-picker-dialog {
|
||||
::v-deep .el-dialog__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,186 +1,177 @@
|
|||
<template>
|
||||
<div class="tag-filter-container">
|
||||
<!-- 需求分类 -->
|
||||
<div class="filter-section">
|
||||
<div class="section-title">需求</div>
|
||||
<div class="tag-buttons">
|
||||
<el-button
|
||||
v-for="(tag, index) in requirementTags"
|
||||
:key="index"
|
||||
:class="['tag-button', { active: tag.selected }]"
|
||||
@click="toggleTag('requirement', index)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工程分类 -->
|
||||
<div class="filter-section">
|
||||
<div class="section-title">工程</div>
|
||||
<div class="tag-buttons">
|
||||
<el-button
|
||||
v-for="(tag, index) in engineeringTags"
|
||||
:key="index"
|
||||
:class="['tag-button', { active: tag.selected }]"
|
||||
@click="toggleTag('engineering', index)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品分类 -->
|
||||
<div class="filter-section">
|
||||
<div class="section-title">产品</div>
|
||||
<div class="tag-buttons">
|
||||
<el-button
|
||||
v-for="(tag, index) in productTags"
|
||||
:key="index"
|
||||
:class="['tag-button', { active: tag.selected }]"
|
||||
@click="toggleTag('product', index)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 营销分类 -->
|
||||
<div class="filter-section">
|
||||
<div class="section-title">营销</div>
|
||||
<div class="tag-buttons">
|
||||
<el-button
|
||||
v-for="(tag, index) in marketingTags"
|
||||
:key="index"
|
||||
:class="['tag-button', { active: tag.selected }]"
|
||||
@click="toggleTag('marketing', index)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" class="query-button" @click="handleQuery">
|
||||
<i class="el-icon-search"></i>
|
||||
查询
|
||||
<div class="search-container">
|
||||
<el-input
|
||||
v-model.trim="searchKeyword"
|
||||
clearable
|
||||
placeholder="输入搜索标签名称"
|
||||
class="search-input"
|
||||
@keyup.enter.native="handleSearch"
|
||||
>
|
||||
<i
|
||||
slot="prefix"
|
||||
class="el-input__icon el-icon-search"
|
||||
@click="handleSearch"
|
||||
></i>
|
||||
</el-input>
|
||||
<el-button type="primary" size="mini" @click="handleSearch">
|
||||
搜索
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="tags-container" v-loading="loading">
|
||||
<template v-if="filteredCategories.length">
|
||||
<div
|
||||
class="filter-section"
|
||||
v-for="category in filteredCategories"
|
||||
:key="category.key"
|
||||
>
|
||||
<div class="section-title"># {{ category.typeName }} #</div>
|
||||
<div class="tag-buttons">
|
||||
<div
|
||||
v-for="tag in category.tags"
|
||||
:key="tag.id"
|
||||
:class="[
|
||||
'tag-button',
|
||||
{ active: isTagSelected(tag.id) },
|
||||
]"
|
||||
@click="toggleTag(tag)"
|
||||
>
|
||||
{{ tag.labelName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="allTagsLoaded" class="load-status">全部加载完毕</div>
|
||||
</template>
|
||||
<el-empty
|
||||
v-else-if="!loading"
|
||||
description="暂无匹配标签"
|
||||
:image-size="80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<el-button class="reset-button" @click="handleReset">
|
||||
<i class="el-icon-refresh"></i>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button type="primary" class="query-button" @click="handleQuery">
|
||||
<i class="el-icon-search"></i>
|
||||
查询
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDocsTagsListAPI } from '@/api/publicService/docCenter'
|
||||
|
||||
export default {
|
||||
name: 'TagFilter',
|
||||
data() {
|
||||
return {
|
||||
// 需求标签
|
||||
requirementTags: [
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
],
|
||||
// 工程标签
|
||||
engineeringTags: [
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: '线路工程', selected: false },
|
||||
{ name: '调试工程', selected: false },
|
||||
{ name: '大修技改', selected: false },
|
||||
{ name: '其他工程', selected: false },
|
||||
{ name: '调试工程', selected: false },
|
||||
{ name: '设计工程', selected: false },
|
||||
{ name: '运维工程', selected: false },
|
||||
{ name: '监理工程', selected: false },
|
||||
],
|
||||
// 产品标签
|
||||
productTags: [
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
],
|
||||
// 营销标签
|
||||
marketingTags: [
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
{ name: 'XXXXXX', selected: false },
|
||||
],
|
||||
searchKeyword: '',
|
||||
loading: false,
|
||||
allTagsLoaded: false,
|
||||
categories: [],
|
||||
selectedTagIds: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredCategories() {
|
||||
if (!this.searchKeyword) {
|
||||
return this.categories
|
||||
}
|
||||
const keyword = this.searchKeyword.toLowerCase()
|
||||
return this.categories
|
||||
.map((category) => {
|
||||
const filteredTags = category.tags.filter((tag) =>
|
||||
(tag.labelName || '').toLowerCase().includes(keyword),
|
||||
)
|
||||
return {
|
||||
...category,
|
||||
tags: filteredTags,
|
||||
}
|
||||
})
|
||||
.filter((category) => category.tags.length > 0)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadTags()
|
||||
},
|
||||
methods: {
|
||||
// 切换标签选中状态
|
||||
toggleTag(category, index) {
|
||||
const tagList = this.getTagListByCategory(category)
|
||||
if (tagList && tagList[index]) {
|
||||
tagList[index].selected = !tagList[index].selected
|
||||
async loadTags() {
|
||||
this.loading = true
|
||||
this.allTagsLoaded = false
|
||||
try {
|
||||
const tagsRes = await getDocsTagsListAPI({
|
||||
labelName: this.searchKeyword,
|
||||
})
|
||||
const tagsData = tagsRes?.data || tagsRes || {}
|
||||
this.categories = Object.keys(tagsData)
|
||||
.map((categoryName, index) => {
|
||||
const tagList = tagsData[categoryName] || []
|
||||
return {
|
||||
key: `${categoryName}_${index}`,
|
||||
typeName: categoryName,
|
||||
tags: tagList
|
||||
.map((tag) => {
|
||||
const rawId =
|
||||
tag.id ??
|
||||
tag.typeId ??
|
||||
tag.labelId ??
|
||||
tag.value ??
|
||||
tag.labelCode
|
||||
if (!rawId) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
id: String(rawId),
|
||||
labelName:
|
||||
tag.labelName ?? tag.name ?? '',
|
||||
typeId: tag.typeId ?? rawId,
|
||||
typeName: categoryName,
|
||||
raw: tag,
|
||||
}
|
||||
})
|
||||
.filter(Boolean),
|
||||
}
|
||||
})
|
||||
.filter((category) => category.tags.length > 0)
|
||||
this.allTagsLoaded = true
|
||||
} catch (error) {
|
||||
console.error('加载标签失败:', error)
|
||||
this.$message.error('加载标签失败,请重试')
|
||||
this.categories = []
|
||||
this.allTagsLoaded = true
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 根据分类获取标签列表
|
||||
getTagListByCategory(category) {
|
||||
switch (category) {
|
||||
case 'requirement':
|
||||
return this.requirementTags
|
||||
case 'engineering':
|
||||
return this.engineeringTags
|
||||
case 'product':
|
||||
return this.productTags
|
||||
case 'marketing':
|
||||
return this.marketingTags
|
||||
default:
|
||||
return null
|
||||
isTagSelected(tagId) {
|
||||
return this.selectedTagIds.includes(String(tagId))
|
||||
},
|
||||
toggleTag(tag) {
|
||||
const tagId = String(tag.id)
|
||||
const index = this.selectedTagIds.indexOf(tagId)
|
||||
if (index > -1) {
|
||||
this.selectedTagIds.splice(index, 1)
|
||||
} else {
|
||||
this.selectedTagIds.push(tagId)
|
||||
}
|
||||
},
|
||||
|
||||
// 查询
|
||||
handleSearch() {
|
||||
this.loadTags()
|
||||
},
|
||||
handleQuery() {
|
||||
const selectedTags = this.getSelectedTags()
|
||||
this.$emit('query', selectedTags)
|
||||
this.$emit('query', [...this.selectedTagIds])
|
||||
},
|
||||
|
||||
// 重置
|
||||
handleReset() {
|
||||
this.resetAllTags()
|
||||
this.searchKeyword = ''
|
||||
this.selectedTagIds = []
|
||||
this.loadTags()
|
||||
this.$emit('reset')
|
||||
},
|
||||
|
||||
// 获取所有选中的标签
|
||||
getSelectedTags() {
|
||||
const selectedTags = {
|
||||
requirement: this.requirementTags.filter((tag) => tag.selected),
|
||||
engineering: this.engineeringTags.filter((tag) => tag.selected),
|
||||
product: this.productTags.filter((tag) => tag.selected),
|
||||
marketing: this.marketingTags.filter((tag) => tag.selected),
|
||||
}
|
||||
return selectedTags
|
||||
},
|
||||
|
||||
// 重置所有标签
|
||||
resetAllTags() {
|
||||
this.requirementTags.forEach((tag) => {
|
||||
tag.selected = false
|
||||
})
|
||||
this.engineeringTags.forEach((tag) => {
|
||||
tag.selected = false
|
||||
})
|
||||
this.productTags.forEach((tag) => {
|
||||
tag.selected = false
|
||||
})
|
||||
this.marketingTags.forEach((tag) => {
|
||||
tag.selected = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -191,14 +182,29 @@ export default {
|
|||
background: #fff;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
|
|
@ -213,108 +219,94 @@ export default {
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tag-button {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
.tag-button {
|
||||
min-width: 80px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #dcdfe6;
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.load-status {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
padding: 12px 0 4px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
|
||||
.query-button {
|
||||
.query-button,
|
||||
.reset-button {
|
||||
height: 36px;
|
||||
padding: 0 24px;
|
||||
border-radius: 18px;
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.query-button {
|
||||
background: #409eff;
|
||||
border-color: #409eff;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #66b1ff;
|
||||
border-color: #66b1ff;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
height: 36px;
|
||||
padding: 0 24px;
|
||||
border-radius: 18px;
|
||||
background: #f5f7fa;
|
||||
border-color: #dcdfe6;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&:hover {
|
||||
background: #ecf5ff;
|
||||
border-color: #c6e2ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.tag-filter-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tag-buttons {
|
||||
.tag-button {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.query-button,
|
||||
.reset-button {
|
||||
width: 120px;
|
||||
}
|
||||
.tag-button {
|
||||
min-width: 70px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
action="#"
|
||||
:auto-upload="false"
|
||||
:file-list="fileList"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleRemove"
|
||||
:on-exceed="handleExceed"
|
||||
|
|
@ -87,61 +86,45 @@ export default {
|
|||
TagSelector,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
duplicateHandle: '1', // 重复处理方式:1-跳过,2-覆盖
|
||||
selectedTag: '', // 选中的文件标签ID(兼容旧代码)
|
||||
selectedTags: [], // 选中的标签对象数组
|
||||
selectedTagIds: [], // 选中的标签ID数组
|
||||
selectedTagLabels: [], // 选中的标签名称数组
|
||||
selectedTypeIds: [], // 选中的去重后的 typeId 数组
|
||||
tagSelectorVisible: false, // 标签选择弹框显示状态
|
||||
fileList: [], // 文件列表
|
||||
uploading: false, // 上传状态
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible: {
|
||||
handler(val) {
|
||||
this.dialogVisible = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 选中的标签显示文本
|
||||
selectedTagText() {
|
||||
if (this.selectedTags.length === 0) {
|
||||
if (this.selectedTagLabels.length === 0) {
|
||||
return ''
|
||||
}
|
||||
return this.selectedTags.map((tag) => tag.labelName).join(',')
|
||||
return this.selectedTagLabels.join(',')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 关闭对话框
|
||||
handleClose() {
|
||||
this.dialogVisible = false
|
||||
this.$emit('update:visible', false)
|
||||
this.resetForm()
|
||||
},
|
||||
|
||||
// 重置表单
|
||||
resetForm() {
|
||||
this.duplicateHandle = '1'
|
||||
this.selectedTag = ''
|
||||
this.selectedTags = []
|
||||
this.selectedTagIds = []
|
||||
this.selectedTagLabels = []
|
||||
this.selectedTypeIds = []
|
||||
this.fileList = []
|
||||
this.uploading = false
|
||||
this.$refs.upload && this.$refs.upload.clearFiles()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.upload && this.$refs.upload.clearFiles()
|
||||
})
|
||||
},
|
||||
|
||||
// 打开标签选择弹框
|
||||
|
|
@ -153,38 +136,56 @@ export default {
|
|||
handleTagConfirm(result) {
|
||||
// result 格式: { tags: [], typeIds: [] }
|
||||
this.selectedTags = result?.tags || []
|
||||
this.selectedTagIds =
|
||||
result?.tagIds ||
|
||||
this.selectedTags.map((tag) => String(tag.id ?? ''))
|
||||
this.selectedTagLabels =
|
||||
result?.tagLabels ||
|
||||
this.selectedTags.map((tag) => tag.labelName || '')
|
||||
this.selectedTypeIds = result?.typeIds || []
|
||||
// 兼容旧代码,取第一个标签的ID
|
||||
if (this.selectedTags.length > 0) {
|
||||
this.selectedTag = this.selectedTags[0].id
|
||||
} else {
|
||||
this.selectedTag = ''
|
||||
}
|
||||
},
|
||||
|
||||
// 上传前校验
|
||||
handleBeforeUpload(file) {
|
||||
// 检查文件格式
|
||||
// const allowedTypes = ['rar', 'zip']
|
||||
// const fileExtension = file.name.split('.').pop().toLowerCase()
|
||||
// 校验文件合法性
|
||||
validateFile(file, options = {}) {
|
||||
const { showMessage = true } = options
|
||||
const rawFile = file?.raw || file
|
||||
if (!rawFile) {
|
||||
if (showMessage) {
|
||||
this.$modal.msgError('文件信息异常,请重新选择文件!')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// if (!allowedTypes.includes(fileExtension)) {
|
||||
// this.$modal.msgError('只支持上传rar/zip格式文件!')
|
||||
// return false
|
||||
// }
|
||||
const fileName = rawFile.name || file.name || ''
|
||||
const fileSize = rawFile.size || file.size || 0
|
||||
|
||||
// 检查文件格式
|
||||
const allowedTypes = ['rar', 'zip']
|
||||
const fileExtension = fileName.split('.').pop().toLowerCase()
|
||||
|
||||
if (!allowedTypes.includes(fileExtension)) {
|
||||
if (showMessage) {
|
||||
this.$modal.msgError('只支持上传rar/zip格式文件!')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查文件大小 (500kb = 0.5MB)
|
||||
const maxSize = 0.5
|
||||
const isLtMaxSize = file.size / 1024 / 1024 < maxSize
|
||||
const isLtMaxSize = fileSize / 1024 / 1024 < maxSize
|
||||
|
||||
if (!isLtMaxSize) {
|
||||
this.$modal.msgError(`文件大小不能超过 ${maxSize}MB!`)
|
||||
if (showMessage) {
|
||||
this.$modal.msgError(`文件大小不能超过 ${maxSize}MB!`)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查文件名是否包含特殊字符
|
||||
if (file.name.includes(',')) {
|
||||
this.$modal.msgError('文件名不能包含英文逗号!')
|
||||
if (fileName.includes(',')) {
|
||||
if (showMessage) {
|
||||
this.$modal.msgError('文件名不能包含英文逗号!')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +194,10 @@ export default {
|
|||
|
||||
// 文件变化
|
||||
handleFileChange(file, fileList) {
|
||||
// if (!this.validateFile(file, { showMessage: true })) {
|
||||
// this.fileList = fileList.filter((item) => item.uid !== file.uid)
|
||||
// return
|
||||
// }
|
||||
this.fileList = fileList
|
||||
},
|
||||
|
||||
|
|
@ -215,14 +220,21 @@ export default {
|
|||
async handleConfirm() {
|
||||
if (this.fileList.length === 0) {
|
||||
this.$modal.msgWarning('请选择要上传的文件!')
|
||||
return
|
||||
throw new Error('表单验证失败')
|
||||
}
|
||||
|
||||
if (this.selectedTags.length === 0) {
|
||||
this.$modal.msgWarning('请选择文件标签!')
|
||||
return
|
||||
throw new Error('表单验证失败')
|
||||
}
|
||||
|
||||
// const hasInvalidFile = this.fileList.some(
|
||||
// (item) => !this.validateFile(item, { showMessage: true }),
|
||||
// )
|
||||
// if (hasInvalidFile) {
|
||||
// throw new Error('表单验证失败')
|
||||
// }
|
||||
|
||||
this.uploading = true
|
||||
|
||||
try {
|
||||
|
|
@ -233,9 +245,12 @@ export default {
|
|||
folderId: this.selectedNode?.id,
|
||||
type: this.selectedNode?.type,
|
||||
repeatType: this.duplicateHandle,
|
||||
parentId: this.selectedNode?.parentId,
|
||||
labels: this.selectedTags.map((tag) => tag.id).join(','),
|
||||
typeIds: this.selectedTypeIds.join(','), // 去重后的 typeId 数组,用逗号连接
|
||||
parentId: this.selectedNode?.id,
|
||||
parentIds: this.selectedNode?.parentIds,
|
||||
labelIds: this.selectedTagIds.join(','), // 标签ID
|
||||
labels: this.selectedTagLabels.join(','), // 标签名称
|
||||
level: this.selectedNode?.level * 1 + 1,
|
||||
// typeIds: this.selectedTypeIds.join(','), // 去重后的 typeId 数组,用逗号连接
|
||||
}
|
||||
|
||||
// 添加文件
|
||||
|
|
@ -250,7 +265,24 @@ export default {
|
|||
|
||||
// 调用上传接口
|
||||
const response = await uploadFileTagAPI(formData)
|
||||
console.log(response, '上传结果')
|
||||
this.$modal.msgSuccess('文件上传成功!')
|
||||
|
||||
const payload = {
|
||||
success: true,
|
||||
response,
|
||||
tagIds: [...this.selectedTagIds],
|
||||
tagLabels: [...this.selectedTagLabels],
|
||||
tags: this.selectedTags.map((tag) => ({
|
||||
id: String(tag.id ?? ''),
|
||||
labelName: tag.labelName,
|
||||
typeId: tag.typeId,
|
||||
typeName: tag.typeName,
|
||||
})),
|
||||
}
|
||||
|
||||
this.$emit('confirm', payload)
|
||||
this.resetForm()
|
||||
return payload
|
||||
} catch (error) {
|
||||
this.$modal.msgError('文件上传失败,请重试!')
|
||||
throw error
|
||||
|
|
|
|||
Loading…
Reference in New Issue