vue3+ts项目antd-vue中实现树节点添加,编辑,删除等操作

vue3+ts项目antd-vue中实现树节点添加,编辑,删除等操作

对vue3,ts和antd-vue的使用

功能详解:

通过弹框来维护树结构,可以点击根节点、子节点、同级节点、删除 按钮来维护树结构
点击根节点按钮:可以在外层根节点添加一个节点
点击子节点按钮:可以在当前节点下添加一个子节点
点击同级结点按钮:可以在当前节点按钮同级添加一个同级节点
点击删除按钮:可以删除目前选中的节点
点击具体节点右侧表单部分会回显节点名称、组织机构、排序等信息
点击保存可将当前修改节点的信息同步到左侧树上

具体页面如下:

在这里插入图片描述

代码实现部分

import { isArray } from 'util';
<script setup lang="ts">
import { ref, reactive, watch, Ref, onMounted, createVNode } from "vue";
import CPage from 'remote_app1/CPage';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
import { typeRequestApi, postRequestApi, getRequestApi } from 'remote_app1/request.ts'
import { useRoute } from 'vue-router'
import rest from "./index.json"
import { message, TreeSelectProps, Modal } from 'ant-design-vue'
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { findKeyName, randomUUID } from "remote_app1/common.ts"
const onSelectedNodes = ref<string[]>([]);
const newNodes = ref<string[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
const route = useRoute()
/** 定义数据源 */
const c_rest = reactive(rest);

let organiseData = ref<TreeSelectProps['treeData']>([]);
/**
 * 下发页面数据配置
 */
interface winFace {
  [propName: string]: any
}
const win: winFace = window;
const p_config = reactive<any>({
  requestApi: win['requestApi'],
  p_source: reactive(rest),
  theme: win['theme'],
  route: route
})

let rowData: any = {}

// 新增按钮
let addBtn = findKeyName("c_text", "维护树", c_rest);
const model3 = findKeyName("c_id", "model_id3", c_rest);
const form3 = model3.c_children;
addBtn.c_callback = function (row: any) {
  rowData = row
  getTreeList()

  setTimeout(() => {
    modelKey.value = true;
  }, 500);
}

// 新增弹框显示/隐藏
let modelKey = ref(false);
const addUserModel: any = reactive({
  title: "维护树",
  width: "800px",
  loading: false,
  okText: "保存",
  cancelText: "取消",
  handleOk: function () {
    const newNode: any = {}
    newNode.nodeName = nodeMsg.value.nodeName
    newNode.nodeId = nodeMsg.value.nodeId
    newNode.parentId = newNodes.value.parentId
    newNode.treeTypeId = rowData.treeTypeId
    newNode.organiseId = nodeMsg.value.organiseId
    newNode.nodeOrderNo = nodeMsg.value.nodeOrderNo
    newNode['nodeId'] = nodeMsg.value.nodeId
    newNode['nodeName'] = nodeMsg.value.nodeName
    newNode['children'] = [];

    // addUserModel.loading = true;
    saveNodeInfo(newNode, "edit")
  }
})
let treeData = ref<any>([]);

// 获取组织树列表
function getTreeList() {
  let organiseId = rowData.organiseId ? rowData.organiseId : ""
  typeRequestApi(
    p_config.requestApi,
    "/XXXX/selectAll",
    { treeTypeId: rowData.treeTypeId, organiseId: organiseId },
    "get"
  ).then((res: any) => {
    treeData.value = res.data.result;
  });
};
const searchValue = ref("");
const expandedKeys: any = ref([]);
const checkedKeys = ref([]);
// 查询搜索信息
function searchTreeData(val: string | number, arr: any, parentKey: string): any {
  let res = new Set();
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].title.indexOf(val) > -1) {
      res.add(arr[i].key);
      if (parentKey) res.add(parentKey);
    }
    if (arr[i].children) {
      let newRes = searchTreeData(val, arr[i].children, arr[i].key);
      res = new Set([...res, ...newRes]);
      if (newRes.length > 0 && parentKey) {
        res.add(parentKey);
      }
    }
  }
  return [...res];
}
// 显示子节点信息
let nodeMsg: any = ref({ nodeName: "", nodeId: "", nodeOrderNo: "", organiseId: "" });

// 点击查看详细信息
function showDetail(nodeId: string, nodeName: string, organiseId: string, nodeOrderNo: string) {
  nodeMsg.value = reactive({ nodeId: nodeId, nodeName: nodeName, organiseId: organiseId, nodeOrderNo: nodeOrderNo })

}
// 删除树节点
function deleteNode(nodeId: string) {
  Modal.confirm({
    title: '删除提示',
    icon: createVNode(ExclamationCircleOutlined),
    content: '确实要删除该节点吗?',
    okText: '确认',
    cancelText: '取消',
    onOk() {
      return new Promise<void>((resolve, reject) => {
        typeRequestApi(
          p_config.requestApi,
          "/XXXX/delete",
          { nodeId: nodeId },
          "get"
        ).then((res: any) => {
          if (res.status === 200) {
            getTreeList()
            resolve()
            message.success('删除成功!');
          }
        });
      }).catch(() => console.log('Oops errors!'));
    },
    onCancel() {
      //todo
    },
  });
}
const open = ref<boolean>(false);
function handleClickAddRootNode() {
  const newNode: any = {}
  newNode.nodeName = '节点'
  newNode.nodeId = '_' + randomUUID()
  newNode.parentId = 0
  newNode.nodeOrderNo = 1
  newNode.treeTypeId = rowData.treeTypeId
  newNode.organiseId = null
  newNode['nodeId'] = newNode.nodeId;
  newNode['nodeName'] = newNode.nodeName;
  newNode['children'] = [];
  //添加到树
  treeData.value.push(newNode);
  saveNodeInfo(newNode, "add")
}
function handleClickAddChildNode() {
  if (!selectedKeys.value.length) {
    message.warning("请先选择需要添加子节点的节点");
    return
  }
  const newNode: any = {}
  newNode.nodeName = '子节点'
  newNode.nodeId = '_' + randomUUID()
  newNode.parentId = nodeMsg.value.nodeId
  newNode.treeTypeId = rowData.treeTypeId
  newNode.organiseId = null
  newNode.nodeOrderNo = newNodes.value.children.length + 1
  newNode['nodeId'] = newNode.nodeId;
  newNode['nodeName'] = newNode.nodeName;
  newNode['children'] = [];
  //添加到当前节点下面
  onSelectedNodes.value[0].children.push(newNode)
  saveNodeInfo(newNode, "add")
}
function handleClickAddBrotherNode() {
  if (!selectedKeys.value.length) {
    message.warning("请先选择需要添加同级节点的节点");
    return
  }
  const newNode: any = {}
  newNode.nodeName = '同级节点'
  newNode.nodeId = '_' + randomUUID()
  newNode.parentId = newNodes.value.parentId
  newNode.treeTypeId = rowData.treeTypeId
  newNode.organiseId = null
  newNode.nodeOrderNo = newNodes.value.children.length + 1
  newNode['nodeId'] = newNode.nodeId;
  newNode['nodeName'] = newNode.nodeName;
  newNode['children'] = [];
  const nodes = newNodes.value.parentId === "0" ? null : newNodes.value;

  newNodes.value.parentId === "0" ? treeData.value.push(newNode) : nodes.children.push(newNode)
  saveNodeInfo(newNode, "add")
}
function handleClickDelNode() {
  if (!selectedKeys.value.length) {
    message.warning("请先选择需要删除的节点");
    return
  }
  deleteNode(nodeMsg.value.nodeId)
}
/**保存新增节点*/
function saveNodeInfo(newNode: any, val: any) {
  postRequestApi(p_config.requestApi, "/XXXXX/saveOrUpdate", newNode).then((res: any) => {
    const { data } = res;
    if (data.success) {
      getTreeList()
      if (val === "add") {
        message.success("新增节点成功 !")
      } else {
        message.success("编辑节点成功 !")
      }
    }
  })
}
/**机构类型*/
function organiseTree() {
  getRequestApi(p_config.requestApi, "/XXXXX/list?pageNumber=1&pageSize=10&keyWord=&pageNum=1&=").then((res: any) => {
    const { data } = res;
    if (data.success) {
      organiseData = reactive(res.data.result)
      // message.success("新增节点成功 !")
    }
  })
}
// const showModal = () => {
//   open.value = true;
// };

const onSelect = (selectedKeys: any, { selected, selectedNodes, node }: any) => {
  onSelectedNodes.value = selectedNodes;
  if (onSelectedNodes.value.length) {
    newNodes.value = node;
  }
}
onMounted(() => {
  organiseTree();
})
</script>
<template>
  <CPage :c_rest="c_rest" :p_config="p_config" />
  <a-modal v-bind="addUserModel" v-model:open="modelKey" :confirm-loading="addUserModel.loading"
    @ok="addUserModel.handleOk">
    <div class="treeWrap">
      <div class="search-box">
        <a-space style="margin-bottom: 10px">
          <a-button type="primary" size="small" @click="handleClickAddRootNode">根节点
          </a-button>
          <a-button type="primary" size="small" @click="handleClickAddChildNode">
            子节点
          </a-button>
          <a-button type="primary" size="small" @click="handleClickAddBrotherNode">
            同级节点
          </a-button>
          <a-button type="primary" size="small" @click="handleClickDelNode" danger>
            删除
          </a-button>
        </a-space>
        <a-input-search v-model:value="searchValue" style="margin-bottom: 8px" placeholder="查询节点名称" />
      </div>
    </div>

    <div class="tree-box" v-if="treeData.length">
      <a-tree :tree-data="treeData" v-model:expanded-keys="expandedKeys" v-model:selectedKeys="selectedKeys"
        @select="onSelect" autoExpandParent="true" show-line="true">
        <template #title="{ nodeName, nodeId, body, organiseId, nodeOrderNo }">
          <span v-if="nodeName.indexOf(searchValue) > -1"
            @click="showDetail(nodeId, nodeName, organiseId, nodeOrderNo)">
            {{ nodeName.substr(0, nodeName.indexOf(searchValue)) }}
            <span style="color: #f50" @click="showDetail(nodeId, nodeName, organiseId, nodeOrderNo)">{{ searchValue
              }}</span>
            {{ nodeName.substr(nodeName.indexOf(searchValue) + searchValue.length) }}
          </span>
        </template>
      </a-tree>

      <div class="treeWrap">
        <a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
          <a-form-item label="节点名称" name="节点名称">
            <a-input v-model:value="nodeMsg.nodeName" />
          </a-form-item>

          <a-form-item label="组织机构" name="组织机构">
            <a-tree-select v-model:value="nodeMsg.organiseId" show-search style="width: 100%" :field-names="{
    children: 'children',
    label: 'organiseName',
    value: 'organiseId',
  }" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="组织机构" allow-clear tree-default-expand-all
              :tree-data="organiseData" tree-node-filter-prop="value">
              <template #title="{ value: val, organiseName }">
                <span>{{ organiseName }}</span>
              </template>
            </a-tree-select>
          </a-form-item>

          <a-form-item label="排序" name="排序">
            <a-input v-model:value="nodeMsg.nodeOrderNo" />
          </a-form-item>
        </a-form>
      </div>

    </div>
  </a-modal>
</template>

<style lang="less">
.ant-form-item {
  margin-bottom: 0 !important;
}

.search-box {
  display: flex;
  flex-direction: column;

  .ant-btn-primary {
    // margin-right: 20px;
  }

  .btn-list {
    display: flex;
    margin-bottom: 20px;
  }
}

.tree-box {
  display: flex;
  width: 100%;

  .ant-tree {
    width: 50% !important;

    .ant-tree-treenode {
      width: 100%;

      .ant-tree-title {
        display: flex;
      }

      .ant-tree-block-node .ant-tree-list-holder-inner .ant-tree-node-content-wrapper {
        flex: auto;
      }
    }
  }

  .ant-form-item {
    margin-bottom: 10px !important;
  }

  .treeWrap {
    width: 50%;
  }
}
</style>```

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Vue3 TypeScript Vue-Cropper是一个基于Vue3和TypeScript开发的图片裁剪组件,使用了vue-cropper库。它主要依赖于element-plus作为UI组件库。在代码地址https://gitee.com/zhong-wenkai/cropper-image-upload.git,有三个文件夹:cropper-image-upload2.x、cropper-image-upload3.x和cropper-image-upload3.x ts。cropper-image-upload2.x是基于Vue2.x版本,依赖于element-ui;cropper-image-upload3.x是基于Vue3.x版本,依赖于element-plus;cropper-image-upload3.x ts是基于Vue3.x TypeScript版本,同样也依赖于element-plus。需要注意的是,对于Vue3.x版本的vue-cropper库,必须安装vue-cropper@next版本。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [基于 vue-cropper 库 实现的 图片裁剪组件(含vue2.x/vue3.x/vue3.x+ts 写法)](https://blog.csdn.net/qq_41709082/article/details/123094853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [vue-cropper 使用demo。 基于 vue3,实现 旋转,缩放,控制裁剪,选择阈值,上传图片等功能。](https://download.csdn.net/download/qq_32067561/84998465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值