gaoluyang
2025-07-01 049dd638c4de83fd8415c258c29d9ef71fd58141
src/views/archiveManagement/index.vue
@@ -4,22 +4,24 @@
      <div class="left-content">
        <div class="tree-header">
          <h3>文档管理</h3>
          <el-button type="primary" size="small" @click="append('')" icon="Plus"
            >新增</el-button
          >
          <el-button icon="Plus" size="small" type="primary" @click="append('')"
            >新增
          </el-button>
        </div>
        <!-- 搜索框 -->
        <div class="search-box">
          <el-input
            v-model="filterText"
            clearable
            placeholder="输入关键字进行搜索"
            size="small"
            clearable
            @input="handleFilter"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
              <el-icon>
                <Search />
              </el-icon>
            </template>
          </el-input>
        </div>
@@ -28,16 +30,19 @@
          <el-tree
            ref="treeRef"
            :data="treeData"
            :props="props"
            :filter-node-method="filterNode"
            :expand-on-click-node="false"
            :default-expand-all="false"
            :expand-on-click-node="false"
            :filter-node-method="filterNode"
            :props="props"
            class="custom-tree"
            node-key="id"
            @node-click="handleNodeClick"
            class="custom-tree"
          >
            <template #default="{ node, data }">
              <div class="tree-node-content" @dblclick="headerDbClick(data)">
              <div
                class="tree-node-content"
                @dblclick="headerDbClick(node, data)"
              >
                <div class="node-icon">
                  <el-icon
                    v-if="!node.isLeaf"
@@ -57,31 +62,29 @@
                  <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
                    class="tree-input"
                    placeholder="请输入节点名称"
                    size="small"
                    @blur="(event) => handleInputBlur(event, data, node)"
                    @keyup.enter="(event) => handleInputBlur(event, data, node)"
                  />
                </div>
                <div class="node-actions" v-show="!data.isEdit">
                <div v-show="!data.isEdit" class="node-actions">
                  <el-button
                    icon="Plus"
                    link
                    size="small"
                    @click.stop="append(data)"
                    icon="Plus"
                    title="新增子节点"
                    @click.stop="append(data)"
                  ></el-button>
                  <el-button
                    icon="Delete"
                    link
                    size="small"
                    @click.stop="remove(node, data)"
                    icon="Delete"
                    title="删除"
                    @click.stop="remove(node, data)"
                  ></el-button>
                </div>
              </div>
@@ -92,67 +95,168 @@
    </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-col :span="10">
          <div>
            <el-input
              style="float: left; width: 50%"
              v-model="searchText"
              placeholder="请输入关键字查询文件"
              clearable
              @input="handleSearch"
              @clear="handleSearch"
            >
              <template #prefix>
                <el-icon>
                  <Search />
                </el-icon>
              </template>
              <template #suffix>
                <el-button @click="handleSearch" link style="border: none">
                  <span>搜索</span>
                </el-button>
              </template>
            </el-input>
          </div>
        </el-col>
        <el-col :offset="10" :span="2">
          <el-button :icon="Delete" type="danger" @click="delHandler"
            >删除</el-button
          >
        </el-col>
        <el-col :span="2">
          <el-button
            :disabled="!tableSwitch"
            :icon="Plus"
            type="primary"
            @click="add"
            >新增
          </el-button>
        </el-col>
      </el-row>
      <ETable
        :loading="loading"
        :table-data="tableData"
        :columns="columns"
        @selection-change="handleSelectionChange"
        @edit="handleEdit"
        :show-selection="true"
        :border="true"
        :columns="columns"
        :loading="loading"
        :maxHeight="1200"
        :show-selection="true"
        :table-data="tableData"
        @edit="handleEdit"
        @selection-change="handleSelectionChange"
      >
      </ETable>
      <Pagination
        :layout="'total, prev, pager, next, jumper'"
        :limit="queryParams.pageSize"
        :page="queryParams.current"
        :show-total="true"
        :total="total"
        :page-size="10"
        :page-count="Math.ceil(total / 10)"
        @page-change="currentPageChange"
        @pagination="handlePageChange"
      ></Pagination>
    </div>
    <archiveDialog
      ref="archiveDialogs"
      v-model:centerDialogVisible="dialogVisible"
      :row="row"
      @centerDialogVisible="centerDialogVisible"
      @submitForm="submitForm"
    >
    </archiveDialog>
  </el-card>
</template>
<script setup>
import { onMounted, ref, nextTick } from "vue";
import { nextTick, onMounted, reactive, ref } from "vue";
import ETable from "@/components/Table/ETable.vue";
import { ElButton, ElInput, ElIcon } from "element-plus";
import {
  ElButton,
  ElIcon,
  ElInput,
  ElMessage,
  ElMessageBox,
} from "element-plus";
import archiveDialog from "./mould/archiveDialog.vue";
import Pagination from "@/components/Pagination/index.vue";
import {
  Delete,
  Document,
  Folder,
  Plus,
  Search,
  Folder,
  Document,
  Delete,
} from "@element-plus/icons-vue";
import {
  getTree,
  addOrEditTree,
  delArchive,
  delTree,
  getArchiveList,
  addOrEditArchive,
  delArchive,
  getTree,
} from "@/api/archiveManagement";
// ===== 响应式状态管理 =====
const searchText = ref("");
const dialogVisible = ref(false);
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 inputRefs = ref(new Map());
const filterText = ref("");
const treeRef = ref();
const total = ref(0);
const row = ref({});
const selectedRows = reactive([]);
const rowClickData = ref({});
const tableSwitch = ref(false);
const archiveDialogs = ref(null);
// ===== 配置常量 =====
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 queryParams = reactive({
  searchAll: "",
  current: 1,
  pageSize: 10,
  treeId: null,
});
const props = {
  label: "name",
  children: "children",
  isLeaf: "leaf",
};
// 搜索过滤功能
// ===== 工具函数 =====
const handleError = (error, defaultMsg = "操作失败,请稍后重试") => {
  console.error(error);
  ElMessage.error(defaultMsg);
};
const showSuccess = (msg = "操作成功") => {
  ElMessage.success(msg);
};
// 搜索查询函数
const handleSearch = () => {
  queryParams.searchAll = searchText.value;
  queryParams.current = 1; // 重置到第一页
  getArchiveListData();
};
const showConfirm = (message, title = "确认操作") => {
  return ElMessageBox.confirm(message, title, {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  });
};
// ===== 基础功能函数 =====
const handleSelectionChange = (selection) => {
  selectedRows.splice(0, selectedRows.length, ...selection);
};
const handleFilter = () => {
  treeRef.value?.filter(filterText.value);
};
@@ -162,22 +266,176 @@
  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 centerDialogVisible = (val) => {
  dialogVisible.value = val;
};
const currentPageChange = (id) => {
  console.log(id);
// ===== 数据获取函数 =====
const getList = async () => {
  try {
    const res = await getTree();
    treeData.value =
      res.code === 200 ? res.data?.records || res.data || [] : [];
  } catch (error) {
    handleError(error, "获取树结构数据失败");
    treeData.value = [];
  }
};
// 双击编辑节点
const headerDbClick = (comeTreeData) => {
  comeTreeData.isEdit = true;
  newName.value = comeTreeData.name;
const getArchiveListData = async () => {
  try {
    loading.value = true;
    const res = await getArchiveList({
      treeId: queryParams.treeId,
      current: queryParams.current,
      size: queryParams.pageSize,
    });
    if (res.code !== 200) {
      ElMessage.error("获取数据失败: " + res.message);
      tableData.value = [];
      total.value = 0;
      return;
    }
    tableData.value = res.data?.records || res.data || [];
    total.value = res.data?.total || 0;
    if (res.data?.current) {
      queryParams.current = res.data.current;
    }
  } catch (error) {
    handleError(error, "获取归档数据失败");
    tableData.value = [];
    total.value = 0;
  } finally {
    loading.value = false;
  }
};
// ===== 表单提交处理 =====
const submitForm = async (res) => {
  try {
    if (res?.code === 200) {
      showSuccess();
      dialogVisible.value = false;
      await getArchiveListData();
    } else {
      ElMessage.error("操作失败: " + (res?.message || res?.msg || "未知错误"));
    }
  } catch (error) {
    handleError(error, "提交表单失败");
  }
};
// ===== 节点操作函数 =====
const handleNodeClick = (data) => {
  rowClickData.value = data;
  tableSwitch.value = true;
  queryParams.current = 1;
  queryParams.treeId = data.id;
  getArchiveListData();
};
const handlePageChange = (pagination) => {
  try {
    const { page, limit } = pagination;
    queryParams.current = page;
    if (limit) queryParams.pageSize = limit;
    getArchiveListData();
  } catch (error) {
    handleError(error, "分页操作失败");
  }
};
// ===== 弹窗操作函数 =====
const openDialog = (isEdit = false, rowData = {}) => {
  try {
    row.value = isEdit ? { ...rowData } : {};
    newName.value = "";
    dialogVisible.value = true;
    nextTick(() => {
      if (archiveDialogs.value) {
        const method = isEdit ? "editForm" : "initForm";
        if (typeof archiveDialogs.value[method] === "function") {
          archiveDialogs.value[method](isEdit ? rowData : rowClickData.value);
        }
      }
    });
  } catch (error) {
    handleError(error, `打开${isEdit ? "编辑" : "新增"}界面失败`);
  }
};
const add = () => openDialog(false);
const handleEdit = (rows) => openDialog(true, rows);
// ===== 删除操作函数 =====
const delHandler = async () => {
  if (selectedRows.length === 0) {
    ElMessage.warning("请选择要删除的数据");
    return;
  }
  try {
    await showConfirm(
      `确定要删除选中的 ${selectedRows.length} 条记录吗?`,
      "删除确认"
    );
    const ids = selectedRows.map((row) => row.id);
    const { code, msg } = await delArchive(ids);
    if (code !== 200) {
      ElMessage.error("删除失败: " + msg);
      return;
    }
    showSuccess("删除成功");
    await getArchiveListData();
    selectedRows.splice(0, selectedRows.length);
  } catch (error) {
    if (error !== "cancel") {
      handleError(error, "删除操作失败");
    }
  }
};
const remove = async (node, data) => {
  if (!data?.id) {
    ElMessage.warning("无法删除此节点");
    return;
  }
  try {
    await showConfirm(`确定要删除节点 "${data.name}" 吗?`, "删除确认");
    const { code, msg } = await delTree([data.id]);
    if (code !== 200) {
      ElMessage.error("删除失败: " + msg);
      return;
    }
    showSuccess("删除成功");
    await getList();
  } catch (error) {
    if (error !== "cancel") {
      handleError(error, "删除节点失败");
    }
  }
};
// ===== 树节点编辑函数 =====
const setInputRef = (el, data) => {
  if (el) {
    inputRefs.value.set(data.id || data, el);
  }
};
const headerDbClick = (node, data) => {
  data.isEdit = true;
  newName.value = data.name;
  nextTick(() => {
    const inputEl = inputRefs.value.get(comeTreeData.id || comeTreeData);
    const inputEl = inputRefs.value.get(data.id || data);
    if (inputEl) {
      inputEl.focus();
      inputEl.select();
@@ -185,119 +443,106 @@
  });
};
// 设置输入框引用的方法
const setInputRef = (el, data) => {
  if (el) {
    inputRefs.value.set(data.id || data, el);
    if (data.isEdit) {
      nextTick(() => {
        // el.focus();
        // el.select();
      });
    }
const expandParentNodes = (node) => {
  if (node?.parent?.data) {
    node.parent.expanded = true;
    expandParentNodes(node.parent);
  }
};
// 处理输入框失焦
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;
    if (!comeTreeData.isEdit || event.relatedTarget?.tagName === "BUTTON")
      return;
    comeTreeData.isEdit = false;
    const newValue = newName.value.trim();
    if (comeTreeData.name === newValue) return;
    if (!newValue) {
      newName.value = comeTreeData.name || "新节点";
      ElMessage.warning("节点名称不能为空");
      return;
    }
    await addOrEditTree({
    const parentId = node?.parent?.data?.id || null;
    const result = await addOrEditTree({
      name: newValue,
      parentId: parentId || null, // 如果没有父节点,则为 null
      parentId,
      id: comeTreeData.id || null,
    });
    if (result.code === 200) {
      comeTreeData.name = newValue;
      if (!comeTreeData.id && result.data) {
        comeTreeData.id = result.data.id || result.data;
      }
      showSuccess("保存成功");
      const currentNodeId = comeTreeData.id;
      await getList();
      nextTick(() => {
        if (currentNodeId && treeRef.value) {
          const targetNode = treeRef.value.getNode(currentNodeId);
          if (targetNode) {
            targetNode.expanded = true;
            expandParentNodes(targetNode);
          }
        }
      });
    } else {
      comeTreeData.name = comeTreeData.name || "新节点";
      ElMessage.error("保存失败: " + (result.msg || "未知错误"));
    }
  } catch (error) {
    console.error("存储失败", error);
    handleError(error, "保存节点失败");
    comeTreeData.name = comeTreeData.name || "新节点";
  }
  console.log("保存成功:", newValue);
};
onMounted(async () => {
  await getList();
// ===== 节点新增函数 =====
const createNewNode = (name, isEdit = true) => ({
  name,
  isEdit,
});
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 focusInput = (nodeData, delay = 50) => {
  setTimeout(() => {
    const inputEl = inputRefs.value.get(nodeData.id || nodeData);
    if (inputEl) {
      inputEl.focus();
      inputEl.select();
      inputEl.$el?.scrollIntoView?.({
        behavior: "smooth",
        block: "nearest",
      });
    }
  }, delay);
};
const append = async (data) => {
  if (data === "") {
    // 新增根节点
    console.log("新增根节点");
    const newNode = {
      id: Date.now(),
      name: "新节点",
      isEdit: true,
    };
    const newNode = createNewNode("新节点");
    treeData.value.push(newNode);
    newName.value = "新节点";
    nextTick(() => {
      const inputEl = inputRefs.value.get(newNode.id || newNode);
      if (inputEl) {
        inputEl.focus();
        inputEl.select();
      }
    });
    nextTick(() => focusInput(newNode));
  } else {
    const hasChildren = data.children && data.children.length > 0;
    const hasChildren = data.children;
    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 isExpanded = node?.expanded;
    // 如果有子级且未展开,先展开节点
    if (hasChildren && !isExpanded && treeRef.value?.store?.nodesMap[nodeKey]) {
      treeRef.value.store.nodesMap[nodeKey].expanded = true;
    }
    const newNode = {
      id: Date.now(),
      name: "新子节点",
      isEdit: true,
    };
    const newNode = createNewNode("新子节点");
    if (!data.children) {
      data.children = [];
@@ -305,47 +550,15 @@
    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);
    });
    nextTick(() => focusInput(newNode, 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 = [];
  }
};
// ===== 生命周期 =====
onMounted(getList);
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.custom-tree-node {
  flex: 1;
  display: flex;
@@ -536,11 +749,13 @@
    }
  }
}
.el-card {
  width: calc(100% - 40px);
  height: calc(100vh - 130px);
  margin: 20px;
  box-sizing: border-box;
  .left {
    width: 30%;
    height: calc(100vh - 160px);
@@ -557,14 +772,16 @@
      flex-direction: column;
    }
  }
  .right {
    width: 70%;
    height: calc(100vh - 160px);
    padding: 0px 10px;
    padding: 0 10px;
    float: left;
  }
}
.archive-management-card{
.archive-management-card {
  margin: 0;
}
</style>