<template>
|
<el-card class="archive-management-card">
|
<div class="left">
|
<div class="left-content">
|
<div class="tree-header">
|
<h3>文档管理</h3>
|
<el-button type="primary" size="small" @click="append('')" icon="Plus"
|
>新增</el-button
|
>
|
</div>
|
|
<!-- 搜索框 -->
|
<div class="search-box">
|
<el-input
|
v-model="filterText"
|
placeholder="输入关键字进行搜索"
|
size="small"
|
clearable
|
@input="handleFilter"
|
>
|
<template #prefix>
|
<el-icon><Search /></el-icon>
|
</template>
|
</el-input>
|
</div>
|
|
<div class="tree-container">
|
<el-tree
|
ref="treeRef"
|
:data="treeData"
|
:props="props"
|
:filter-node-method="filterNode"
|
:expand-on-click-node="false"
|
:default-expand-all="false"
|
node-key="id"
|
@node-click="handleNodeClick"
|
class="custom-tree"
|
>
|
<template #default="{ node, data }">
|
<div class="tree-node-content" @dblclick="headerDbClick(data)">
|
<div class="node-icon">
|
<el-icon
|
v-if="!node.isLeaf"
|
:class="{ expanded: node.expanded }"
|
>
|
<Folder />
|
</el-icon>
|
<el-icon v-else>
|
<Document />
|
</el-icon>
|
</div>
|
|
<div class="node-label">
|
<span v-if="!data.isEdit" class="label-text">{{
|
node.label
|
}}</span>
|
<el-input
|
v-else
|
:ref="(el) => setInputRef(el, data)"
|
placeholder="请输入节点名称"
|
v-model="newName"
|
@blur="($event) => handleInputBlur($event, data, node)"
|
@keyup.enter="
|
($event) => handleInputBlur($event, data, node)
|
"
|
size="small"
|
class="tree-input"
|
autofocus
|
/>
|
</div>
|
<div class="node-actions" v-show="!data.isEdit">
|
<el-button
|
link
|
size="small"
|
@click.stop="append(data)"
|
icon="Plus"
|
title="新增子节点"
|
></el-button>
|
<el-button
|
link
|
size="small"
|
@click.stop="remove(node, data)"
|
icon="Delete"
|
title="删除"
|
></el-button>
|
</div>
|
</div>
|
</template>
|
</el-tree>
|
</div>
|
</div>
|
</div>
|
<div class="right">
|
<el-row :gutter="24">
|
<el-col :span="2" :offset="20"><el-button :icon="Delete" type="danger">删除</el-button></el-col>
|
<el-col :span="2"><el-button :icon="Plus" type="primary">新增</el-button></el-col>
|
</el-row>
|
<ETable
|
:loading="loading"
|
:table-data="tableData"
|
:columns="columns"
|
@selection-change="handleSelectionChange"
|
@edit="handleEdit"
|
:show-selection="true"
|
:border="true"
|
>
|
</ETable>
|
<Pagination
|
:total="total"
|
:page-size="10"
|
:page-count="Math.ceil(total / 10)"
|
@page-change="currentPageChange"
|
></Pagination>
|
</div>
|
</el-card>
|
</template>
|
<script setup>
|
import { onMounted, ref, nextTick } from "vue";
|
import ETable from "@/components/Table/ETable.vue";
|
import { ElButton, ElInput, ElIcon } from "element-plus";
|
import Pagination from "@/components/Pagination/index.vue";
|
import {
|
Plus,
|
Search,
|
Folder,
|
Document,
|
Delete,
|
} from "@element-plus/icons-vue";
|
import {
|
getTree,
|
addOrEditTree,
|
delTree,
|
getArchiveList,
|
addOrEditArchive,
|
delArchive,
|
} from "@/api/archiveManagement";
|
const loading = ref(false);
|
const tableData = ref([]);
|
const treeData = ref([]);
|
const newName = ref("");
|
const inputRefs = ref(new Map()); // 存储输入框引用
|
const filterText = ref(""); // 搜索关键字
|
const treeRef = ref(); // 树组件引用
|
const total = ref(0); // 总记录数
|
const current = ref(1); // 当前页码
|
const columns = [
|
{ prop: "name", label: "名称", minWidth: 180 },
|
{ prop: "type", label: "类型", minWidth: 120 },
|
{ prop: "status", label: "状态", minWidth: 100 },
|
];
|
const handleSelectionChange = (selection) => {
|
console.log("Selected rows:", selection);
|
};
|
|
// 搜索过滤功能
|
const handleFilter = () => {
|
treeRef.value?.filter(filterText.value);
|
};
|
|
const filterNode = (value, data) => {
|
if (!value) return true;
|
return data.name?.toLowerCase().includes(value.toLowerCase());
|
};
|
|
// 处理节点点击
|
const handleNodeClick = async (data) => {
|
console.log("点击节点:", data);
|
let res = await getArchiveList(data.id);
|
tableData.value = res.data?.records || res.data || [];
|
console.log(data)
|
};
|
const currentPageChange = (id) => {
|
console.log(id);
|
};
|
// 双击编辑节点
|
const headerDbClick = (comeTreeData) => {
|
comeTreeData.isEdit = true;
|
newName.value = comeTreeData.name;
|
nextTick(() => {
|
const inputEl = inputRefs.value.get(comeTreeData.id || comeTreeData);
|
if (inputEl) {
|
inputEl.focus();
|
inputEl.select();
|
}
|
});
|
};
|
|
// 设置输入框引用的方法
|
const setInputRef = (el, data) => {
|
if (el) {
|
inputRefs.value.set(data.id || data, el);
|
if (data.isEdit) {
|
nextTick(() => {
|
// el.focus();
|
// el.select();
|
});
|
}
|
}
|
};
|
|
// 处理输入框失焦
|
const handleInputBlur = async (event, comeTreeData, node) => {
|
if (!comeTreeData.isEdit) return; // 如果不是编辑状态,直接返回
|
if (event.relatedTarget && event.relatedTarget.tagName === "BUTTON") {
|
return;
|
}
|
comeTreeData.isEdit = false;
|
const newValue = newName.value.trim();
|
if (comeTreeData.name === newValue) {
|
console.log("没有修改内容");
|
return;
|
}
|
if (newValue === "") {
|
console.warn("输入不能为空");
|
newName.value = comeTreeData.name || "新节点";
|
return;
|
}
|
try {
|
comeTreeData.name = newValue;
|
// 获取父节点的id - 通过 node 参数更准确地获取
|
let parentId = null;
|
if (node && node.parent && node.parent.data) {
|
parentId = node.parent.data.id;
|
}
|
await addOrEditTree({
|
name: newValue,
|
parentId: parentId || null, // 如果没有父节点,则为 null
|
});
|
} catch (error) {
|
console.error("存储失败", error);
|
comeTreeData.name = comeTreeData.name || "新节点";
|
}
|
console.log("保存成功:", newValue);
|
};
|
|
onMounted(async () => {
|
await getList();
|
});
|
const props = {
|
label: "name",
|
children: "children", // 改为 children 以匹配标准结构
|
isLeaf: "leaf",
|
};
|
|
const remove = async (node, data) => {
|
console.log("删除节点:", data);
|
if (!data || !data.id) {
|
console.warn("无法删除未定义或无效的节点");
|
return;
|
}
|
console.log("删除节点 ID:", data.id);
|
let { code, msg } = await delTree([data.id]);
|
if (code !== 200) {
|
ElMessage.warning("删除失败, " + msg);
|
} else {
|
ElMessage.success("删除成功");
|
}
|
await getList();
|
};
|
|
const append = async (data) => {
|
if (data === "") {
|
// 新增根节点
|
console.log("新增根节点");
|
const newNode = {
|
id: Date.now(),
|
name: "新节点",
|
isEdit: true,
|
};
|
treeData.value.push(newNode);
|
newName.value = "新节点";
|
|
nextTick(() => {
|
const inputEl = inputRefs.value.get(newNode.id || newNode);
|
if (inputEl) {
|
inputEl.focus();
|
inputEl.select();
|
}
|
});
|
} else {
|
const hasChildren = data.children && data.children.length > 0;
|
const nodeKey = data.id || data;
|
const node = treeRef.value?.getNode(nodeKey);
|
const isExpanded = node?.expanded; // 如果有子级且未展开,先展开节点
|
if (hasChildren && !isExpanded) {
|
console.log(treeRef.value, "展开节点", nodeKey);
|
if (
|
treeRef.value &&
|
treeRef.value.store &&
|
treeRef.value.store.nodesMap[nodeKey]
|
) {
|
treeRef.value.store.nodesMap[nodeKey].expanded = true;
|
}
|
}
|
|
const newNode = {
|
id: Date.now(),
|
name: "新子节点",
|
isEdit: true,
|
};
|
|
if (!data.children) {
|
data.children = [];
|
}
|
data.children.push(newNode);
|
newName.value = "新子节点";
|
|
// 根据是否需要展开来决定延迟时间
|
const delay = hasChildren && !isExpanded ? 200 : 50;
|
|
// 等待DOM更新完成后再聚焦
|
nextTick(() => {
|
setTimeout(() => {
|
const inputEl = inputRefs.value.get(newNode.id || newNode);
|
if (inputEl) {
|
inputEl.focus();
|
inputEl.select();
|
|
// 滚动到新增的节点位置
|
inputEl.$el?.scrollIntoView?.({
|
behavior: "smooth",
|
block: "nearest",
|
});
|
}
|
}, delay);
|
});
|
}
|
};
|
|
const handleEdit = () => {};
|
|
// 移除懒加载,直接获取数据
|
const getList = async () => {
|
try {
|
let res = await getTree();
|
if (res.code === 200) {
|
treeData.value = res.data?.records || res.data || [];
|
} else {
|
console.error("Failed to fetch tree data:", res.message);
|
treeData.value = [];
|
}
|
} catch (error) {
|
console.error("获取树形数据失败:", error);
|
treeData.value = [];
|
}
|
};
|
</script>
|
<style scoped lang="scss">
|
.custom-tree-node {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
font-size: 14px;
|
padding-right: 8px;
|
}
|
|
// 树形菜单样式
|
.tree-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 16px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #e4e7ed;
|
|
h3 {
|
margin: 0;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
}
|
|
.search-box {
|
margin-bottom: 16px;
|
|
.el-input {
|
border-radius: 6px;
|
|
:deep(.el-input__wrapper) {
|
border-radius: 6px;
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
&:hover {
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.15);
|
}
|
|
&.is-focus {
|
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
}
|
}
|
}
|
}
|
|
.tree-container {
|
flex: 1;
|
overflow-y: auto;
|
border: 1px solid #dcdfe6;
|
border-radius: 8px;
|
background: #fff;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
.custom-tree {
|
padding: 8px;
|
background: transparent;
|
|
:deep(.el-tree-node) {
|
.el-tree-node__content {
|
height: 36px;
|
padding: 0 8px;
|
border-radius: 6px;
|
margin: 2px 0;
|
transition: all 0.2s ease;
|
|
&:hover {
|
background-color: #f0f9ff;
|
}
|
|
&.is-current {
|
background-color: #e6f7ff;
|
border: 1px solid #91d5ff;
|
}
|
}
|
|
.el-tree-node__expand-icon {
|
color: #606266;
|
font-size: 14px;
|
padding: 6px;
|
|
&.expanded {
|
transform: rotate(90deg);
|
}
|
|
&.is-leaf {
|
color: transparent;
|
}
|
}
|
}
|
}
|
}
|
|
.tree-node-content {
|
display: flex;
|
align-items: center;
|
width: 100%;
|
padding: 4px 0;
|
|
.node-icon {
|
margin-right: 8px;
|
color: #faad14;
|
display: flex;
|
align-items: center;
|
|
.el-icon {
|
font-size: 16px;
|
|
&.expanded {
|
color: #1890ff;
|
}
|
}
|
}
|
|
.node-label {
|
flex: 1;
|
min-width: 0;
|
|
.label-text {
|
font-size: 14px;
|
color: #303133;
|
cursor: pointer;
|
display: block;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
|
&:hover {
|
color: #1890ff;
|
}
|
}
|
}
|
|
.node-actions {
|
opacity: 0;
|
transition: opacity 0.2s ease;
|
display: flex;
|
|
.el-button {
|
padding: 4px;
|
margin-left: 4px;
|
color: #909399;
|
min-height: auto;
|
|
&:hover {
|
color: #1890ff;
|
background-color: #f0f9ff;
|
}
|
|
&.el-button--text:hover {
|
background-color: #f0f9ff;
|
}
|
}
|
}
|
|
&:hover .node-actions {
|
opacity: 1;
|
}
|
}
|
|
// 输入框样式美化
|
.tree-input {
|
flex: 1;
|
|
:deep(.el-input__wrapper) {
|
border-radius: 4px;
|
border: 1px solid #d9d9d9;
|
transition: all 0.2s ease;
|
|
&:hover {
|
border-color: #40a9ff;
|
}
|
|
&.is-focus {
|
border-color: #1890ff;
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
}
|
}
|
|
:deep(.el-input__inner) {
|
padding: 4px 8px;
|
font-size: 14px;
|
color: #303133;
|
|
&::placeholder {
|
color: #bfbfbf;
|
}
|
}
|
}
|
.el-card {
|
width: calc(100% - 40px);
|
height: calc(100vh - 130px);
|
margin: 20px;
|
box-sizing: border-box;
|
.left {
|
width: 30%;
|
height: calc(100vh - 160px);
|
background-color: #fafafa;
|
padding: 16px;
|
float: left;
|
box-sizing: border-box;
|
border-radius: 8px;
|
|
.left-content {
|
width: 100%;
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
}
|
}
|
.right {
|
width: 70%;
|
height: calc(100vh - 160px);
|
padding: 0px 10px;
|
float: left;
|
}
|
}
|
.archive-management-card{
|
margin: 0;
|
}
|
</style>
|