家校通小程序实战教程08搭建部门管理前端界面

目录

  • [1 微搭的部门管理](#1 微搭的部门管理)
  • [2 如何自定义树形组件](#2 如何自定义树形组件)
  • [3 搭建界面](#3 搭建界面)
  • [4 组件搭建思路](#4 组件搭建思路)
    • [4.1 创建布局框架](#4.1 创建布局框架)
    • [4.2 引入树形组件](#4.2 引入树形组件)
    • [4.3 实现右键菜单功能](#4.3 实现右键菜单功能)
    • [4.4 点击节点显示表单](#4.4 点击节点显示表单)
  • 总结

部门表设计之后,就需要考虑如何搭建界面了。界面这个部分搭建有多种方案,主要是要考虑方便用户添加和修改数据。微搭的角色用户就提供了一个不错的功能模板,我们先拆解一下微搭的方案。

1 微搭的部门管理

在产品自带的内部用户管理,提供了一种方便录入数据的功能。

移动上去会有更多的功能

这个时候就可以添加子部门或者修改部门了

他这种方式也没有问题,那更多的我们是习惯用右键,弹出更多的功能去操作。

2 如何自定义树形组件

官方提供的树形组件没有办法定义右键功能,这里我们就需要使用JSX组件。JSX组件相当于提供了一个React语法的自定义组件,支持你按照JSX的语法编制组件。但是缺点是不允许npm引入第三方的包。

我们就需要通过CDN的方式进行引入。

打开组件的说明文档

https://docs.cloudbase.net/lowcode/components/wedaUI/src/docs/compsdocs/super/Jsx#3-如何引入第三方组件库

参考官方提供的CDN的地址

bash 复制代码
https://cdnjs.cloudflare.com/ajax/libs/antd/5.8.5/reset.min.css
https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.9/dayjs.min.js
https://cdnjs.cloudflare.com/ajax/libs/antd/5.8.5/antd.min.js

点击应用的设置图标

一次将CSS和JS的资源加入

3 搭建界面

资源库引入之后我们就可以搭建页面了,点击创建页面的图标

输入页面的名称,选择左侧导航布局

在内容插槽里添加布局组件

选中布局内容,添加AI代码块组件

点击编辑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([
    { id: 1, name: "总公司", parentId: null, children: [], isOpen: true }
  ]);
  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();

  // 点击节点事件
  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>
  );
}

点击保存按钮

点击实时预览的功能查看效果

选择总公司,右键,点击添加子部门

录入部门名称,科技部,点击保存将部门更新到树里

4 组件搭建思路

首先,我们需要构建一个显示部门信息的页面。在页面的左侧,我们将展示一个树形结构,右侧则展示与选中的部门相关的详细信息和表单。

4.1 创建布局框架

我们开始构建基础布局,使用 flexbox 来设置左右两栏。左侧是树形结构,右侧是显示部门详细信息的表单。整体布局如下:

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

  // 设置树的数据
  const [treeData, setTreeData] = React.useState([
    { id: 1, name: "总公司", parentId: null, children: [], isOpen: true }
  ]);

  // 选择的节点和表单
  const [selectedNode, setSelectedNode] = React.useState(null);
  const [form] = Form.useForm();

  return (
    <div style={{ display: "flex", ...style }}>
      {/* 左侧树 */}
      <div
        style={{
          flex: "1",
          borderRight: "1px solid #ddd",
          overflowY: "auto",
          padding: "10px",
          minHeight: "300px"
        }}
      >
        {/* 树形组件 */}
      </div>

      {/* 右侧表单 */}
      <div
        style={{
          flex: "2",
          padding: "10px",
          minHeight: "300px"
        }}
      >
        {/* 表单组件 */}
      </div>

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

使用 flex 布局来让左侧树和右侧表单自适应宽度,左侧占 1 列,右侧占 2 列。左侧树的 minHeight 为 300px,右侧表单区域也有相同的高度,保证页面美观。

4.2 引入树形组件

在左侧区域展示树形结构时,我们使用 antd 的 Tree 组件。我们首先在组件的顶部引入 Ant Design 库中的相关组件:

bash 复制代码
const { Tree } = antd;

然后我们使用 Tree 组件来展示部门的树结构。Tree 组件允许嵌套节点,这正符合我们的需求。树的数据结构是一个包含 id、name 和 children 的对象数组。

我们通过递归来渲染树形节点,每个节点的 title 是部门的名称,点击时会选择该节点,并展示其详细信息。

bash 复制代码
const renderTree = (nodes) =>
  nodes.map((node) => (
    <Tree.TreeNode
      key={node.id}
      title={<span>{node.name}</span>}
    >
      {node.children && renderTree(node.children)}
    </Tree.TreeNode>
  ));

<Tree>{renderTree(treeData)}</Tree>;

在 Tree 组件中,我们递归地渲染每个部门节点。每个部门节点的 key 是唯一的 ID,title 是部门的名称。

4.3 实现右键菜单功能

我们希望在树节点上右键点击时,弹出一个菜单,允许用户执行一些操作,比如"添加子部门"。为了实现这一功能,我们需要监听右键点击事件。

在树节点上添加 onContextMenu 事件,捕获鼠标点击的位置,显示一个右键菜单。

bash 复制代码
const handleRightClick = (e, node) => {
  e.preventDefault();
  setContextMenuTargetId(node.id);
  setContextMenuPosition({ x: e.clientX, y: e.clientY });
};

接下来,使用一个 div 来展示右键菜单,位置由 contextMenuPosition 确定。右键菜单中,我们可以添加操作,例如"添加子部门"。

bash 复制代码
{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>
)}

4.4 点击节点显示表单

我们希望用户点击树节点时,右侧展示该节点的详细信息(如部门名称、部门编号)。这些信息将通过一个表单展示,用户可以修改后保存。

通过 Form 组件,展示当前选中节点的信息。通过 form.setFieldsValue 设置表单的初始值。

bash 复制代码
const handleNodeClick = (node) => {
  setSelectedNode(node);
  setContextMenuTargetId(null);
  form.setFieldsValue({ name: node.name });
};

{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>
    <Button type="primary" onClick={handleSaveForm}>保存</Button>
  </Form>
) : (
  <p>请选择左侧树节点以查看或编辑部门信息。</p>
)}

当用户修改了表单数据后,可以点击保存按钮,保存修改后的部门信息。此时,我们更新树形数据中的相应节点。

bash 复制代码
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));
};

总结

本篇我们介绍了通过引入antd来实现部门管理的功能,有时候如果低代码组件的功能有限时,就需要借助第三方的组件库来搭建自己需要的功能。

相关推荐
幽兰的天空2 分钟前
CSS3 常用特性及应用全解析
前端·html·css3·html5
冰镇屎壳郎6 分钟前
Vue八股青春版
前端·javascript·vue.js
V+zmm1013425 分钟前
基于微信小程序的校园二手交易平台系统设计与开发ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·ssm
hxx22127 分钟前
iOS swift开发系列 -- tabbar问题总结
前端
远洋录1 小时前
前端性能优化实战:从加载到渲染的全链路提升
前端·人工智能·react
雯0609~1 小时前
vue3-tp8-Element:对话框实现
前端·javascript·vue.js
雕花の刺猬2 小时前
UE4与WEB-UI通信
前端·ui·ue4·webui
web182859970892 小时前
【Web——HTML 初阶】网页设计标题
前端·html
低代码布道师3 小时前
家校通小程序实战教程09搭建部门管理APIs
低代码·小程序