| | |
| | | import {Delete, Document, Folder, Plus, Search,} from "@element-plus/icons-vue"; |
| | | import {addOrEditTree, delArchive, delTree, getArchiveList, getTree,} from "@/api/archiveManagement"; |
| | | |
| | | const dialogVisible = ref(false); // 控制归档对话框显示 |
| | | // ===== 响应式状态管理 ===== |
| | | 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 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 selectedRows = reactive([]); // 存储选中行数据 |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.splice(0, selectedRows.length, ...selection); |
| | | }; |
| | | |
| | | const queryParams = reactive({ |
| | | searchAll: "", |
| | | current: 1, |
| | | pageSize: 10, // 固定每页10条 |
| | | treeId: null, // 当前树节点ID |
| | | 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 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); |
| | | }; |
| | | const row = ref({}); // 当前选中行数据 |
| | | |
| | | const filterNode = (value, data) => { |
| | | if (!value) return true; |
| | | return data.name?.toLowerCase().includes(value.toLowerCase()); |
| | | }; |
| | | const submitForm = async (res) => { |
| | | try { |
| | | if (res && res.code === 200) { |
| | | ElMessage.success("操作成功"); |
| | | dialogVisible.value = false; |
| | | // 刷新列表数据 |
| | | await getArchiveListData(); |
| | | } else { |
| | | ElMessage.error("操作失败: " + (res?.message || res?.msg || "未知错误")); |
| | | } |
| | | } catch (error) { |
| | | console.error("提交表单错误:", error); |
| | | ElMessage.error("操作失败,请稍后重试"); |
| | | } |
| | | }; |
| | | |
| | | const centerDialogVisible = (val) => { |
| | | dialogVisible.value = val; |
| | | }; |
| | | const tableSwitch = ref(false); |
| | | // 处理节点点击 |
| | | const handleNodeClick = (data) => { |
| | | rowClickData.value = data; // 存储当前点击的节点数据 |
| | | tableSwitch.value = true; |
| | | // 切换节点时重置到第一页 |
| | | queryParams.current = 1; |
| | | queryParams.treeId = data.id; |
| | | getArchiveListData(); |
| | | }; |
| | | const rowClickData = ref({}); // 存储当前点击的节点数据 |
| | | const archiveDialogs = ref(null); // 表格组件引用 |
| | | // 新增归档 |
| | | const add = () => { |
| | | // ===== 数据获取函数 ===== |
| | | const getList = async () => { |
| | | try { |
| | | row.value = {}; // 清空行数据,确保是新增模式 |
| | | newName.value = ""; // 清空输入框 |
| | | dialogVisible.value = true; |
| | | |
| | | // 确保组件引用存在后再调用方法 |
| | | nextTick(() => { |
| | | if (archiveDialogs.value && typeof archiveDialogs.value.initForm === 'function') { |
| | | archiveDialogs.value.initForm(rowClickData.value); // 重置表单 |
| | | } |
| | | }); |
| | | const res = await getTree(); |
| | | treeData.value = res.code === 200 ? (res.data?.records || res.data || []) : []; |
| | | } catch (error) { |
| | | console.error("新增归档错误:", error); |
| | | ElMessage.error("打开新增界面失败"); |
| | | } |
| | | }; |
| | | // 处理分页变化 |
| | | const handlePageChange = (pagination) => { |
| | | try { |
| | | const { page, limit } = pagination; |
| | | queryParams.current = page; |
| | | if (limit) { |
| | | queryParams.pageSize = limit; |
| | | } |
| | | getArchiveListData(); |
| | | } catch (error) { |
| | | console.error("分页处理错误:", error); |
| | | ElMessage.error("分页操作失败"); |
| | | handleError(error, "获取树结构数据失败"); |
| | | treeData.value = []; |
| | | } |
| | | }; |
| | | |
| | | const getArchiveListData = async () => { |
| | | try { |
| | | loading.value = true; |
| | | let res = await getArchiveList({ |
| | | const res = await getArchiveList({ |
| | | treeId: queryParams.treeId, |
| | | current: queryParams.current, |
| | | size: queryParams.pageSize, |
| | |
| | | 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; |
| | | } |
| | | // pageSize 固定为20,不从后端获取 |
| | | |
| | | } catch (error) { |
| | | ElMessage.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("请选择要删除的数据"); |
| | |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `确定要删除选中的 ${selectedRows.length} 条记录吗?`, |
| | | '删除确认', |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | } |
| | | ); |
| | | |
| | | await showConfirm(`确定要删除选中的 ${selectedRows.length} 条记录吗?`, '删除确认'); |
| | | |
| | | const ids = selectedRows.map((row) => row.id); |
| | | const { code, msg } = await delArchive(ids); |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | ElMessage.success("删除成功"); |
| | | // 删除成功后重新获取数据 |
| | | showSuccess("删除成功"); |
| | | await getArchiveListData(); |
| | | // 清空选中状态 |
| | | selectedRows.splice(0, selectedRows.length); |
| | | |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | console.error("删除归档失败:", error); |
| | | ElMessage.error("删除操作失败,请稍后重试"); |
| | | 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; |
| | |
| | | }); |
| | | }; |
| | | |
| | | // 设置输入框引用的方法 |
| | | 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) => { |
| | | try { |
| | | if (!comeTreeData.isEdit) return; // 如果不是编辑状态,直接返回 |
| | | |
| | | if (event.relatedTarget && event.relatedTarget.tagName === "BUTTON") { |
| | | return; |
| | | } |
| | | if (!comeTreeData.isEdit || (event.relatedTarget?.tagName === "BUTTON")) return; |
| | | |
| | | comeTreeData.isEdit = false; |
| | | const newValue = newName.value.trim(); |
| | | |
| | | if (comeTreeData.name === newValue) { |
| | | return; |
| | | } |
| | | if (comeTreeData.name === newValue) return; |
| | | |
| | | if (newValue === "") { |
| | | if (!newValue) { |
| | | newName.value = comeTreeData.name || "新节点"; |
| | | ElMessage.warning("节点名称不能为空"); |
| | | return; |
| | | } |
| | | |
| | | // 获取父节点的id - 通过 node 参数更准确地获取 |
| | | let parentId = null; |
| | | if (node && node.parent && node.parent.data) { |
| | | parentId = node.parent.data.id; |
| | | } |
| | | 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) { |
| | | |
| | | if (result.code === 200) { |
| | | comeTreeData.name = newValue; |
| | | if (!comeTreeData.id && result.data) { |
| | | comeTreeData.id = result.data.id || result.data; |
| | | } |
| | | ElMessage.success("保存成功"); |
| | | showSuccess("保存成功"); |
| | | |
| | | // 刷新树数据并展开当前节点 |
| | | const currentNodeId = comeTreeData.id; |
| | | await getList(); |
| | | |
| | | // 等待DOM更新后展开节点 |
| | | nextTick(() => { |
| | | if (currentNodeId && treeRef.value) { |
| | | const targetNode = treeRef.value.getNode(currentNodeId); |
| | | if (targetNode) { |
| | | // 展开当前节点 |
| | | targetNode.expanded = true; |
| | | // 如果有父节点,也展开父节点路径 |
| | | expandParentNodes(targetNode); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | } catch (error) { |
| | | console.error("保存节点失败:", error); |
| | | handleError(error, "保存节点失败"); |
| | | comeTreeData.name = comeTreeData.name || "新节点"; |
| | | ElMessage.error("保存失败,请稍后重试"); |
| | | } |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await getList(); |
| | | // ===== 节点新增函数 ===== |
| | | const createNewNode = (name, isEdit = true) => ({ |
| | | name, |
| | | isEdit, |
| | | }); |
| | | const props = { |
| | | label: "name", |
| | | children: "children", // 改为 children 以匹配标准结构 |
| | | isLeaf: "leaf", |
| | | }; |
| | | |
| | | // 展开父节点路径 |
| | | const expandParentNodes = (node) => { |
| | | if (node && node.parent && node.parent.data) { |
| | | // 递归展开所有父节点 |
| | | node.parent.expanded = true; |
| | | expandParentNodes(node.parent); |
| | | } |
| | | }; |
| | | |
| | | // 删除树节点 |
| | | const remove = async (node, data) => { |
| | | if (!data || !data.id) { |
| | | ElMessage.warning("无法删除此节点"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | `确定要删除节点 "${data.name}" 吗?`, |
| | | '删除确认', |
| | | { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning', |
| | | } |
| | | ); |
| | | |
| | | const { code, msg } = await delTree([data.id]); |
| | | |
| | | if (code !== 200) { |
| | | ElMessage.error("删除失败: " + msg); |
| | | return; |
| | | 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", |
| | | }); |
| | | } |
| | | |
| | | ElMessage.success("删除成功"); |
| | | await getList(); |
| | | |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | console.error("删除节点失败:", error); |
| | | ElMessage.error("删除失败,请稍后重试"); |
| | | } |
| | | } |
| | | }, delay); |
| | | }; |
| | | |
| | | const append = async (data) => { |
| | | if (data === "") { |
| | | // 新增根节点 |
| | | const newNode = { |
| | | 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; |
| | | const nodeKey = data.id || data; |
| | | const node = treeRef.value?.getNode(nodeKey); |
| | | const isExpanded = node?.expanded; // 如果有子级且未展开,先展开节点 |
| | | if (hasChildren && !isExpanded) { |
| | | 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 = { |
| | | name: "新子节点", |
| | | isEdit: true, |
| | | }; |
| | | |
| | | |
| | | const newNode = createNewNode("新子节点"); |
| | | |
| | | 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); |
| | | }); |
| | | nextTick(() => focusInput(newNode, delay)); |
| | | } |
| | | }; |
| | | |
| | | // 编辑归档 |
| | | const handleEdit = (rows) => { |
| | | try { |
| | | row.value = { ...rows }; // 使用深拷贝避免引用问题 |
| | | dialogVisible.value = true; |
| | | |
| | | // 确保组件引用存在后再调用方法 |
| | | nextTick(() => { |
| | | if (archiveDialogs.value && typeof archiveDialogs.value.editForm === 'function') { |
| | | archiveDialogs.value.editForm(rows); // 调用编辑方法 |
| | | } |
| | | }); |
| | | } catch (error) { |
| | | console.error("编辑归档错误:", error); |
| | | ElMessage.error("打开编辑界面失败"); |
| | | } |
| | | }; |
| | | |
| | | // 移除懒加载,直接获取数据 |
| | | const getList = async () => { |
| | | try { |
| | | let res = await getTree(); |
| | | if (res.code === 200) { |
| | | treeData.value = res.data?.records || res.data || []; |
| | | } else { |
| | | treeData.value = []; |
| | | } |
| | | } catch (error) { |
| | | treeData.value = []; |
| | | } |
| | | }; |
| | | // ===== 生命周期 ===== |
| | | onMounted(getList); |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | .custom-tree-node { |