表单的前端数据流向
在CRM项目中,会涉及很多张表单。每张表单的前端代码都会放在一个单独的文件夹中。这个文件夹下包含三个文件,分别是:
index.js
(以下称为index
):负责组件的渲染和交互逻辑。model.js
(以下称为model
):负责状态管理和业务逻辑。service.js
(以下称为service
):负责接口的定义和调用。
数据流向
index
调用model
的方法。model
的方法中会调用service
的接口。service
文件定义了与表单相关的所有接口。
状态管理的设计
在项目中,状态变量可以存在于 index
和 model
中,但状态变量的归属并不是固定的,而是根据具体的需求来决定。状态变量的类型可以分为以下两种:
-
控制组件展示的状态变量
用于控制组件的隐藏、显示等 UI 状态。 -
控制数据展示的状态变量
用于存储表单的输入值、接口返回的数据等业务状态。
状态变量的使用和更新
-
状态变量的使用
index
可以使用自己的状态变量(通过this.state.状态变量
)或model
的状态变量(通过this.props.状态变量
)。model
的状态变量可以通过index
的props
获取,但model
不直接使用index
的状态变量。
-
状态变量的更新
index
的状态变量通过this.setState({状态变量: 值})
更新。model
的状态变量可以通过index
或model
自身调用actions.业务对象名字.updateState({状态变量: 值})
的方式更新。
交互逻辑
在表单页面中,每次用户点击或触发交互时,会调用对应的方法。这些方法的执行逻辑如下:
- 调用接口获取数据(通过
service
)。 - 根据接口返回的数据更新状态变量(通过
actions.updateState
方法)。 - 状态变量的改变会触发组件的重新渲染,从而实现组件的展示或数据的更新。
举个例子,当我点击“分配”按钮中的分配负责人的时候,前端代码的逻辑是这样的:
1. index.js
文件
在 index.js
文件中,主要处理用户交互逻辑。以下是 Buttons
组件的定义和事件绑定:
<Buttons
buttonTpl={listBtnsTpl} // 按钮模板
onClick={this.listBtnsClick} // 点击事件处理函数
/>
Buttons
组件:一个通用按钮组件,通过buttonTpl
属性传入按钮配置模板,通过onClick
属性绑定点击事件处理函数。listBtnsClick
方法:处理按钮点击事件,根据不同的按钮类型执行不同的逻辑。
以下是 listBtnsClick
方法的代码:
listBtnsClick(clickBtn, belongBtn) {
const clickedKey = _.get(clickBtn, "key"); // 获取点击按钮的 key
const belongBtnKey = _.get(belongBtn, "key"); // 获取所属按钮的 key
if (belongBtnKey === "add") {
// 新增操作
let obj = belongBtn.comp.filter(item => item.key == clickBtn.key);
obj = obj ? obj[0] : {};
_this.onClickNew({
custChanTypeName: obj.name,
custChanTypeId: obj.key
});
} else if (clickedKey === "add") {
// 特殊新增操作
if (clickBtn?.comp?.length === 1) {
_this.onClickNew({
custChanTypeName: _.get(clickBtn, "comp[0].name", ""),
custChanTypeId: _.get(clickBtn, "comp[0].key", "")
});
}
} else if (belongBtnKey === "distribute") {
// 分配操作
if (!_this.state.selectedRowKeys.length) {
Warning("请选择客户"); // 如果没有选择客户,弹出警告
return;
}
let assignObj = belongBtn.comp.filter(item => item.key == clickBtn.key);
_this.showAssign(assignObj[0].key); // 调用分配方法
} else if (belongBtnKey === "recycle") {
// 回收操作
if (!_this.state.selectedRowKeys.length) {
Warning("请选择客户"); // 如果没有选择客户,弹出警告
return;
}
let assignObj = belongBtn.comp.filter(item => item.key == clickBtn.key);
_this.showAssign(assignObj[0].key); // 调用回收方法
}
}
- 逻辑说明:
- 根据
belongBtnKey
和clickedKey
判断按钮的类型,执行不同的操作。 - 分支逻辑包括新增、分配、回收等功能。
- 调用对应的方法(如
onClickNew
或showAssign
)完成具体操作。
- 根据
在 listBtnsClick
方法中,当按钮类型为 "分配" 或 "回收" 时,会调用 showAssign
方法。以下是 showAssign
方法的代码:
showAssign(key) {
let assNum = key.match(/\d+/) ? key.match(/\d+/)[0] - 1 : 0; // 提取数字并减一
let dispatchCustomer = this.state.selectedRowKeys; // 获取选中的客户
if (!dispatchCustomer.length) {
Warning("请选择客户"); // 如果没有选中客户,弹出警告
return;
}
let param = { customerId: dispatchCustomer[0].id }; // 构造请求参数
actions.customerhome.dispatchResponsiblesByCustomerId(param).then(resultData => {
// 调用 model 中的方法,获取数据
console.log(JSON.stringify(resultData, null, 2) + " resultData2222");
// 遍历返回数据,新增字段
const updatedDispatchList = resultData.map(item => ({
...item,
personId: item.staffUserId,
personName: item.staffUserName,
personCode: item.staffUserCode
}));
// 去重逻辑:只保留 personId 唯一的对象
const uniqueDispatchList = updatedDispatchList.reduce((acc, current) => {
if (!acc.find(item => item.personId === current.personId)) {
acc.push(current);
}
return acc;
}, []);
console.log(JSON.stringify(updatedDispatchList, null, 2) + " updatedDispatchList");
// 更新组件状态
this.setState({
dispatchList: uniqueDispatchList // 更新 dispatchList
});
// 更新 model 状态
actions.customerhome.updateState({
assignVisiable: true,
clickAssign: key,
dispatchRange: assNum
});
});
}
- 逻辑说明:
- 提取
key
中的数字,用于计算分配范围。 - 获取选中的客户 ID,构造请求参数
param
。 - 调用
model
文件中的方法dispatchResponsiblesByCustomerId
发起接口请求。 - 对接口返回的数据进行处理:
- 新增字段(如
personId
、personName
)。 - 去重处理,确保
personId
唯一。
- 新增字段(如
- 更新组件的
state
和model
的状态。
- 提取
2. model.js
文件
在 model.js
文件中,封装了具体的业务逻辑。以下是 dispatchResponsiblesByCustomerId
方法的代码:
async dispatchResponsiblesByCustomerId(param) {
let result = await api.dispatchResponsiblesByCustomerId(param); // 调用接口
if (result && result.data) {
var resultData = result.data; // 提取数据
return resultData; // 返回处理后的数据
}
}
- 逻辑说明:
- 使用
async/await
处理异步请求。 - 调用
service.js
文件中的dispatchResponsiblesByCustomerId
方法发起接口请求。 - 提取接口返回的数据并返回给调用方。
- 使用
3. service.js
文件
在 service.js
文件中,定义了接口调用的具体实现。以下是 dispatchResponsiblesByCustomerId
方法的代码:
/**
* 客户分配中,根据客户ID找到客户对应的负责人的信息
* @param {*} params
*/
export const dispatchResponsiblesByCustomerId = param => {
return requestSec(URL.DISTRIBUTE_RESPONSIBLE_URL, {
method: "get",
param
});
};
- 逻辑说明:
- 使用
requestSec
方法发起 HTTP 请求。 - 请求方法为
GET
,参数为param
。 - 返回后端接口的数据。
- 使用