当前位置: 首页 > article >正文

家校通小程序实战教程10部门管理前后端连接

目录

  • 1 加载后端的数据
  • 2 为什么不直接给变量赋值
  • 3 保存部门信息
  • 4 最终的效果
  • 5 总结

现在部门管理已经完成了后端功能和前端开发,就需要在前端调用后端的数据完成界面的展示,而且在录入部门信息后需要提交到数据库里,本篇我们介绍一下前后端如何交互。

1 加载后端的数据

现在后端API已经有了,页面加载的时候需要从后端读取数据。打开应用,点击编辑JSX代码

在这里插入图片描述
输入如下代码

export default function DepartmentTree(props: JSXCompProps) {
  const { $w, contentSlot1, style } = props;
  const { Modal, Input, Tree, Button, Form } = antd;

  const [treeData, setTreeData] = React.useState([]);
  const [selectedNode, setSelectedNode] = React.useState(null);
  const [addChildModalVisible, setAddChildModalVisible] = React.useState(false);
  const [newDepartmentName, setNewDepartmentName] = React.useState("");
  const [contextMenuTargetId, setContextMenuTargetId] = React.useState(null);
  const [contextMenuPosition, setContextMenuPosition] = React.useState(null);
  const [form] = Form.useForm();

  // 初始化获取部门数据
  React.useEffect(() => {
    async function fetchDepartments() {
      try {
        const result = await $w.cloud.callDataSource({
          dataSourceName: "departmentManagement_adpurdw",
          methodName: "getAllDepartments",
          params: {}
        });
        if (result && result.data) {
          setTreeData(result.data);
        } else {
          Modal.warning({ title: "数据加载失败", content: "未获取到部门数据。" });
        }
      } catch (error) {
        console.error("Error fetching departments:", error);
        Modal.error({ title: "加载失败", content: "获取部门数据时出错,请稍后重试。" });
      }
    }

    fetchDepartments();
  }, [$w]);

  // 点击节点事件
  const handleNodeClick = (node) => {
    setSelectedNode(node);
    setContextMenuTargetId(null);
    form.setFieldsValue({ name: node.name });
  };

  // 切换节点展开/收起
  const toggleNode = (node) => {
    node.isOpen = !node.isOpen;
    setTreeData([...treeData]);
  };

  // 递归更新树节点
  const updateTree = (nodes, callback) => {
    return nodes.map((node) => {
      if (callback(node)) {
        return { ...node };
      }
      if (node.children) {
        return { ...node, children: updateTree(node.children, callback) };
      }
      return node;
    });
  };

  // 添加子部门逻辑
  const handleAddChild = () => {
    if (!newDepartmentName.trim()) {
      return Modal.warning({
        title: "部门名称不能为空",
        content: "请输入部门名称后重试。"
      });
    }

    const newChild = {
      id: Date.now(),
      name: newDepartmentName,
      parentId: contextMenuTargetId,
      children: [],
      isOpen: false
    };

    setTreeData((prevTreeData) =>
      updateTree(prevTreeData, (node) => {
        if (node.id === contextMenuTargetId) {
          node.children = [...(node.children || []), newChild];
          return true;
        }
        return false;
      })
    );

    setAddChildModalVisible(false);
    setNewDepartmentName("");
  };

  // 保存表单修改
  const handleSaveForm = () => {
    form
      .validateFields()
      .then((values) => {
        setTreeData((prevTreeData) =>
          updateTree(prevTreeData, (node) => {
            if (node.id === selectedNode.id) {
              node.name = values.name;
              return true;
            }
            return false;
          })
        );
        Modal.success({ title: "保存成功", content: "部门信息已更新。" });
      })
      .catch((info) => console.error("Validate Failed:", info));
  };

  // 右键点击事件
  const handleRightClick = (e, node) => {
    e.preventDefault();
    setContextMenuTargetId(node.id);
    setContextMenuPosition({ x: e.clientX, y: e.clientY });
  };

  // 渲染树节点
  const renderTree = (nodes) =>
    nodes.map((node) => (
      <Tree.TreeNode
        key={node.id}
        title={
          <span
            onClick={() => handleNodeClick(node)}
            onContextMenu={(e) => handleRightClick(e, node)}
          >
            {node.name}
          </span>
        }
      >
        {node.children && renderTree(node.children)}
      </Tree.TreeNode>
    ));

  return (
    <div style={{ display: "flex", ...style }}>
      {/* 左侧树 */}
      <div
        style={{
          flex: "1",
          borderRight: "1px solid #ddd",
          overflowY: "auto",
          padding: "10px",
          minHeight: "300px"
        }}
      >
        <Tree showLine defaultExpandAll>
          {renderTree(treeData)}
        </Tree>
      </div>

      {/* 右侧表单 */}
      <div
        style={{
          flex: "2",
          padding: "10px",
          minHeight: "300px"
        }}
      >
        {selectedNode ? (
          <Form form={form} layout="vertical">
            <h3>部门信息</h3>
            <Form.Item
              label="部门名称"
              name="name"
              rules={[{ required: true, message: "请输入部门名称" }]}
            >
              <Input />
            </Form.Item>
            <Form.Item label="部门编号">
              <Input value={selectedNode.id} disabled />
            </Form.Item>
            <div style={{ marginTop: "10px" }}>
              <Button
                type="primary"
                onClick={handleSaveForm}
                style={{ marginRight: "10px" }}
              >
                保存
              </Button>
              <Button onClick={() => form.resetFields()}>取消</Button>
            </div>
          </Form>
        ) : (
          <p>请选择左侧树节点以查看或编辑部门信息。</p>
        )}
      </div>

      {/* 添加子部门 Modal */}
      <Modal
        title="添加子部门"
        visible={addChildModalVisible}
        onOk={handleAddChild}
        onCancel={() => setAddChildModalVisible(false)}
      >
        <Input
          placeholder="请输入部门名称"
          value={newDepartmentName}
          onChange={(e) => setNewDepartmentName(e.target.value)}
        />
      </Modal>

      {/* 右键菜单 */}
      {contextMenuPosition && (
        <div
          style={{
            position: "absolute",
            top: contextMenuPosition.y,
            left: contextMenuPosition.x,
            backgroundColor: "#fff",
            boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
            borderRadius: "4px",
            zIndex: 1000
          }}
          onMouseLeave={() => setContextMenuPosition(null)}
        >
          <div
            onClick={() => setAddChildModalVisible(true)}
            style={{
              padding: "8px 16px",
              cursor: "pointer",
              borderBottom: "1px solid #f0f0f0"
            }}
          >
            添加子部门
          </div>
        </div>
      )}

      {/* 插槽 */}
      <div>{contentSlot1}</div>
    </div>
  );
}

我们追加了一个代码,主要是要异步调用后端API的

  // 初始化获取部门数据
  React.useEffect(() => {
    async function fetchDepartments() {
      try {
        const result = await $w.cloud.callDataSource({
          dataSourceName: "departmentManagement_adpurdw",
          methodName: "getAllDepartments",
          params: {}
        });
        if (result && result.data) {
          setTreeData(result.data);
        } else {
          Modal.warning({ title: "数据加载失败", content: "未获取到部门数据。" });
        }
      } catch (error) {
        console.error("Error fetching departments:", error);
        Modal.error({ title: "加载失败", content: "获取部门数据时出错,请稍后重试。" });
      }
    }

    fetchDepartments();
  }, [$w]);

这里的dataSourceName和methodName是从我们的API里获取的
在这里插入图片描述
部门管理旁边的相当于我们的dataSourceName,而标识相当于我们的methodName

2 为什么不直接给变量赋值

我一开始认为,我直接获取数据就可以,比如这样

const treeData = $w.cloud.callDataSource({
  dataSourceName: "departmentManagement_adpurdw",
  methodName: "getAllDepartments",
  params: {}
});

但这种操作发现树是空的,并没有从后台读取数据过来。主要的原因是两方面,首先callDataSource是异步的,你这个执行完了数据其实是没返回的

两一方面,数据加载完毕并不会通知React重新渲染组件。改成useEffect的好处是,在组件首次渲染后启动数据加载任务。当数据加载完成后,通过 setTreeData 更新组件状态,React 会自动重新渲染组件并展示新数据。

尤其如果部门比较多的情况下,界面可以先出来,等数据获取完毕组装好了再次渲染树形组件,这种体验就比较好了

3 保存部门信息

数据我们现在已经可以从变量中读取了,剩下就是添加的时候将新增的部门信息保存到数据源里。这里我们调用微搭的写入方法,修改如下代码

// 添加子部门逻辑
  const handleAddChild = () => {
    if (!newDepartmentName.trim()) {
      return Modal.warning({
        title: "部门名称不能为空",
        content: "请输入部门名称后重试。",
      });
    }

    // 调用数据源写入新部门
    $w.cloud
      .callDataSource({
        dataSourceName: "bmb", // 数据源名称
        methodName: "wedaCreateV2", // 假设存在 createDepartment 方法
        params: {
          data: {
            bmmc: newDepartmentName, // 部门名称
            fbm: { _id: contextMenuTargetId }, // 父部门ID
          },
        },
      })
      .then((response) => {
        const newId = response?.id; // 从响应中获取新节点的 ID
        if (!newId) {
          throw new Error("数据源未返回有效ID");
        }

        const newChild = {
          id: newId, // 使用后端返回的 ID
          name: newDepartmentName,
          parentId: contextMenuTargetId,
          children: [],
          isOpen: false,
        };

        // 更新前端树数据
        setTreeData((prevTreeData) =>
          updateTree(prevTreeData, (node) => {
            if (node.id === contextMenuTargetId) {
              node.children = [...(node.children || []), newChild];
              return true;
            }
            return false;
          })
        );

        // 关闭模态框并清空输入
        setAddChildModalVisible(false);
        setNewDepartmentName("");
        Modal.success({
          title: "添加成功",
          content: `子部门 "${newDepartmentName}" 已成功添加。`,
        });
      })
      .catch((error) => {
        console.error("数据源写入失败", error);
        Modal.error({
          title: "操作失败",
          content: "添加子部门时出现错误,请稍后重试。",
        });
      });
  };

需要把上边给的完整代码的handleAddChild 进行替换,这里主要是调用了微搭的创建单条的API。在创建的时候需要传递对应的数据,因为我们的父部门是关联关系,新版的关联关系是对象类型,所以我们是按照对象的结构组织了数据。

4 最终的效果

现在已经可以从数据库里读取部门的信息,并且按照树形的结构进行展示
在这里插入图片描述
点击右键的时候可以添加部门,数据库里可以看到写入的数据
在这里插入图片描述

5 总结

我们本篇介绍了如何将前后端的功能连接起来,从代码上来讲还是比较复杂的,主要需要考虑react组件加载的机制,副作用的理解,以及微搭的异步加载机制。要想理解好这些概念需要你亲自做一遍,才会有深入的体会。


http://www.kler.cn/a/443661.html

相关文章:

  • Nginx界的天花板-Oracle 中间件OHS 11g服务器环境搭建
  • 一款5k star的 Redis 客户端!!简洁高效!
  • SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
  • EasyExcel 模板+公式填充
  • 观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?
  • 秒鲨后端之MyBatis【2】默认的类型别名、MyBatis的增删改查、idea中设置文件的配置模板、MyBatis获取参数值的两种方式、特殊SQL的执行
  • AI工具如何深刻改变我们的工作与生活
  • Java 学习之路:开启编程世界的大门
  • 2.学习TypeScript 编译选项配置
  • Appium怎样连接多台设备?
  • Java项目--仿RabbitMQ的消息队列--虚拟主机设计
  • 如何使用Selenium处理JavaScript动态加载的内容?
  • 《算法SM4》题目
  • uniapp连接蓝牙操作(蓝牙设备地锁)
  • 授权模型DAC
  • PostgreSQL 常用运维SQL整理
  • 3.zabbix中文设置
  • 洛谷 B3836 [GESP202303 二级] 百鸡问题
  • day-21 内核链表以及栈
  • CSS系列(17)-- 工具与生态系统详解
  • 【实用技能】如何在 Unity3D 中将网页内容渲染为纹理
  • ChatGPT与领域特定语言的集成
  • [手机Linux] 六,ubuntu18.04私有网盘(NextCloud)安装
  • MFC扩展库BCGControlBar Pro v36.0 - 工具栏 对话框组件升级
  • SQL Server 中对网络数据库文件的支持说明
  • @pathvariable什么作用