家校通小程序实战教程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组件加载的机制,副作用的理解,以及微搭的异步加载机制。要想理解好这些概念需要你亲自做一遍,才会有深入的体会。