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

目录

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

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

1 加载后端的数据

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

输入如下代码

bash 复制代码
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的

bash 复制代码
  // 初始化获取部门数据
  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 为什么不直接给变量赋值

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

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

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

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

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

3 保存部门信息

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

bash 复制代码
// 添加子部门逻辑
  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组件加载的机制,副作用的理解,以及微搭的异步加载机制。要想理解好这些概念需要你亲自做一遍,才会有深入的体会。

相关推荐
前端 贾公子11 小时前
小程序蓝牙打印探索与实践(上)
小程序
UXbot11 小时前
AI网页开发工具能替代工具吗?5大平台对比
前端·人工智能·低代码·ui·原型模式·web app
拙慕JULY13 小时前
小程序返回 base64 文件报错
开发语言·javascript·小程序
dh1312225052513 小时前
按月季度销售业绩核算小程序
小程序·销售小程序·绩效小程序·业绩统计小程序·业绩核算小程序
拙慕JULY14 小时前
微信小程序自定义标题背景色
微信小程序·小程序
yinmaisoft14 小时前
JNPF 三大主流行业解决方案,按需定制
大数据·低代码·开发工具
前端 贾公子15 小时前
小程序蓝牙打印探索与实践(下)
小程序·apache
00后程序员张15 小时前
Jenkins 自动上传 IPA 到 App Store 把发布步骤融入 CI/CD
android·ios·小程序·https·uni-app·iphone·webview
SL-staff18 小时前
Vue3私有化AI白板落地实战|解决政企项目智能绘图合规难题(可直接复用源码)
人工智能·低代码·开源·vue3·白板·jvs规则引擎·jvs-draw
万岳科技系统开发18 小时前
骑手配送系统如何支持外卖与跑腿一体化运营
大数据·前端·小程序