feat(事件系统): 添加带回调的事件处理功能

实现原生与前端之间的双向事件通信机制,支持事件处理结果回调
添加 executeEvent 方法用于触发带回调的事件
完善 iOS 和 Android 平台的事件回调处理逻辑
更新文档说明带回调事件的使用方法
This commit is contained in:
2026-01-19 09:33:49 +08:00
parent 4e3f1c7cb8
commit 54e6a5785e
4 changed files with 338 additions and 10 deletions

191
README.md
View File

@@ -50,11 +50,17 @@ cordova.plugins.ShutoApi.getUserInfo(
- `params`: 导航参数(可选,对象类型)
#### uploadLog
当后台需要前端上传日志时触发。
当后台需要前端上传日志时触发。前端处理完成后需要通过 callback 返回上传结果。
**事件数据:**
- `logType`: 日志类型(如 "error", "warning", "info" 等)
- `message`: 日志消息内容
- `timestamp`: 日志时间戳(可选)
- `extra`: 额外的日志数据(可选,对象类型)
## 事件监听示例
### 基本用法
### 基本用法(无回调事件)
```javascript
// 监听导航事件
@@ -73,13 +79,65 @@ cordova.plugins.ShutoApi.addEventListener('navigate', function(data) {
// navigate(data.route, { state: data.params || {} });
});
// 监听日志上传事件
cordova.plugins.ShutoApi.addEventListener('uploadLog', function() {
console.log('触发日志上传');
// 监听日志上传事件(需要回调)
cordova.plugins.ShutoApi.addEventListener('uploadLog', function(data, callback) {
console.log('日志类型:', data.logType);
console.log('日志消息:', data.message);
console.log('时间戳:', data.timestamp);
console.log('额外数据:', data.extra);
// 在这里实现日志上传逻辑
// 例如,发送到日志服务器
// uploadToLogServer();
uploadToLogServer(data)
.then(function(result) {
// 上传成功,返回结果
callback({
success: true,
uploadedAt: new Date().toISOString(),
recordId: result.recordId
});
})
.catch(function(error) {
// 上传失败,返回错误
callback(null, error.message);
});
});
```
### 带回调的事件
如果事件需要前端处理后返回结果,可以使用带回调的事件:
```javascript
// 监听需要回调的事件
cordova.plugins.ShutoApi.addEventListener('confirm', function(data, callback) {
console.log('确认消息:', data.message);
// 显示确认对话框
if (confirm(data.message)) {
// 用户确认,返回成功
callback({ confirmed: true });
} else {
// 用户取消,返回错误
callback(null, 'User cancelled');
}
});
// 监听需要异步处理的事件
cordova.plugins.ShutoApi.addEventListener('fetchData', function(params, callback) {
console.log('需要获取数据:', params.url);
// 异步获取数据
fetch(params.url)
.then(response => response.json())
.then(data => {
// 成功回调
callback(data);
})
.catch(error => {
// 错误回调
callback(null, error.message);
});
});
```
@@ -119,8 +177,55 @@ NSDictionary* params = @{
};
[pluginInstance navigateToRoute:@"/user/profile" parameters:params];
// 触发日志上传事件
[pluginInstance fireEvent:@"uploadLog" parameters:nil];
// 触发带回调的事件 - 日志上传
NSDictionary* logData = @{
@"logType": @"error",
@"message": @"Network request failed",
@"timestamp": @([[NSDate date] timeIntervalSince1970]),
@"extra": @{
@"url": @"https://api.example.com/data",
@"statusCode": @500
}
};
[pluginInstance fireEventWithCallback:@"uploadLog" parameters:logData callback:^(NSDictionary* result, NSError* error) {
if (error) {
NSLog(@"日志上传失败: %@", error.localizedDescription);
} else {
NSLog(@"日志上传成功: %@", result);
BOOL success = [result[@"success"] boolValue];
NSString* recordId = result[@"recordId"];
// 处理上传结果
}
}];
// 触发带回调的事件 - 确认对话框
NSDictionary* confirmData = @{
@"message": @"确定要删除吗?"
};
[pluginInstance fireEventWithCallback:@"confirm" parameters:confirmData callback:^(NSDictionary* result, NSError* error) {
if (error) {
NSLog(@"确认失败: %@", error.localizedDescription);
} else {
BOOL confirmed = [result[@"confirmed"] boolValue];
if (confirmed) {
NSLog(@"用户已确认");
} else {
NSLog(@"用户取消");
}
}
}];
// 触发带回调的事件 - 获取数据
NSDictionary* fetchData = @{
@"url": @"https://api.example.com/data"
};
[pluginInstance fireEventWithCallback:@"fetchData" parameters:fetchData callback:^(NSDictionary* result, NSError* error) {
if (error) {
NSLog(@"获取数据失败: %@", error.localizedDescription);
} else {
NSLog(@"获取数据成功: %@", result);
}
}];
```
### Android
@@ -141,8 +246,74 @@ try {
params.put("showDetails", true);
pluginInstance.navigateToRoute("/user/profile", params);
// 触发日志上传事件
pluginInstance.fireEvent("uploadLog", null);
// 触发带回调的事件 - 日志上传
JSONObject logData = new JSONObject();
logData.put("logType", "error");
logData.put("message", "Network request failed");
logData.put("timestamp", System.currentTimeMillis() / 1000.0);
JSONObject extra = new JSONObject();
extra.put("url", "https://api.example.com/data");
extra.put("statusCode", 500);
logData.put("extra", extra);
pluginInstance.fireEventWithCallback("uploadLog", logData, new ShutoApi.EventCallback() {
@Override
public void onResult(JSONObject result) {
try {
boolean success = result.getBoolean("success");
String recordId = result.getString("recordId");
Log.d("ShutoApi", "日志上传成功: " + result.toString());
// 处理上传结果
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onError(String errorMessage) {
Log.e("ShutoApi", "日志上传失败: " + errorMessage);
}
});
// 触发带回调的事件 - 确认对话框
JSONObject confirmData = new JSONObject();
confirmData.put("message", "确定要删除吗?");
pluginInstance.fireEventWithCallback("confirm", confirmData, new ShutoApi.EventCallback() {
@Override
public void onResult(JSONObject result) {
try {
boolean confirmed = result.getBoolean("confirmed");
if (confirmed) {
Log.d("ShutoApi", "用户已确认");
} else {
Log.d("ShutoApi", "用户取消");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onError(String errorMessage) {
Log.e("ShutoApi", "确认失败: " + errorMessage);
}
});
// 触发带回调的事件 - 获取数据
JSONObject fetchData = new JSONObject();
fetchData.put("url", "https://api.example.com/data");
pluginInstance.fireEventWithCallback("fetchData", fetchData, new ShutoApi.EventCallback() {
@Override
public void onResult(JSONObject result) {
Log.d("ShutoApi", "获取数据成功: " + result.toString());
}
@Override
public void onError(String errorMessage) {
Log.e("ShutoApi", "获取数据失败: " + errorMessage);
}
});
} catch (JSONException e) {
e.printStackTrace();
}

View File

@@ -15,6 +15,15 @@ public class ShutoApi extends CordovaPlugin {
// 保存事件回调上下文
private CallbackContext eventCallbackContext;
// 保存带回调的事件回调映射
private Map<String, EventCallback> eventCallbacks = new HashMap<>();
// 事件回调接口
public interface EventCallback {
void onResult(JSONObject result);
void onError(String errorMessage);
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
@@ -28,6 +37,10 @@ public class ShutoApi extends CordovaPlugin {
} else if (action.equals("getUserInfo")) {
this.getUserInfo(callbackContext);
return true;
} else if (action.equals("eventCallback")) {
this.eventCallback(args);
callbackContext.success();
return true;
}
return false;
}
@@ -92,4 +105,61 @@ public class ShutoApi extends CordovaPlugin {
e.printStackTrace();
}
}
// 触发带回调的事件
private void fireEventWithCallback(String eventName, JSONObject parameters, final EventCallback callback) {
if (this.eventCallbackContext == null) {
if (callback != null) {
callback.onError("No event callback registered");
}
return;
}
try {
String callbackId = java.util.UUID.randomUUID().toString();
if (callback != null) {
eventCallbacks.put(callbackId, callback);
}
JSONObject eventData = new JSONObject();
eventData.put("eventName", eventName);
eventData.put("callbackId", callbackId);
if (parameters != null) {
eventData.put("params", parameters);
}
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, eventData);
pluginResult.setKeepCallback(true);
this.eventCallbackContext.sendPluginResult(pluginResult);
} catch (JSONException e) {
e.printStackTrace();
if (callback != null) {
callback.onError("Failed to create event data: " + e.getMessage());
}
}
}
// 处理前端回调
private void eventCallback(JSONArray args) {
try {
String callbackId = args.getString(0);
JSONObject result = args.optJSONObject(1);
String errorMessage = args.optString(2);
EventCallback callback = eventCallbacks.get(callbackId);
if (callback != null) {
if (errorMessage != null && !errorMessage.isEmpty()) {
callback.onError(errorMessage);
} else {
callback.onResult(result);
}
eventCallbacks.remove(callbackId);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

View File

@@ -9,6 +9,8 @@
NSString* _eventCallbackId;
//
NSDictionary* _userInfo;
// ID
NSMutableDictionary* _eventCallbacks;
}
- (void)close:(CDVInvokedUrlCommand*)command;
@@ -17,10 +19,16 @@
- (void)setUserInfo:(CDVInvokedUrlCommand*)command;
- (void)navigateToRoute:(NSString*)route parameters:(NSDictionary*)parameters __attribute__((objc_method_family(none)));
- (void)fireEvent:(NSString*)type parameters:(NSDictionary*)parameters __attribute__((objc_method_family(none)));
- (void)fireEventWithCallback:(NSString*)eventName parameters:(NSDictionary*)parameters callback:(void (^)(NSDictionary* result, NSError* error))callback __attribute__((objc_method_family(none)));
- (void)eventCallback:(CDVInvokedUrlCommand*)command;
@end
@implementation ShutoApi
- (void)pluginInitialize {
_eventCallbacks = [[NSMutableDictionary alloc] init];
}
- (void)close:(CDVInvokedUrlCommand*)command
{
// JS
@@ -129,4 +137,53 @@
[self.commandDelegate sendPluginResult:pluginResult callbackId:_eventCallbackId];
}
//
- (void)fireEventWithCallback:(NSString*)eventName parameters:(NSDictionary*)parameters callback:(void (^)(NSDictionary* result, NSError* error))callback {
if (!_eventCallbackId) {
[SGGC_Log log:@"SGGC_CDVFile" message:(@"ShutoApi: No event callback registered")];
if (callback) callback(nil, [NSError errorWithDomain:@"ShutoApi" code:-1 userInfo:@{NSLocalizedDescriptionKey:@"No event callback registered"}]);
return;
}
NSString* callbackId = [[NSUUID UUID] UUIDString];
if (callback) {
_eventCallbacks[callbackId] = callback;
}
NSMutableDictionary* eventData = [NSMutableDictionary dictionary];
[eventData setObject:eventName forKey:@"eventName"];
[eventData setObject:callbackId forKey:@"callbackId"];
if (parameters) {
[eventData setObject:parameters forKey:@"params"];
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:eventData];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:_eventCallbackId];
}
//
- (void)eventCallback:(CDVInvokedUrlCommand*)command {
NSString* callbackId = command.arguments[0];
NSDictionary* result = command.arguments[1];
NSString* errorMessage = command.arguments[2];
void (^callback)(NSDictionary* result, NSError* error) = _eventCallbacks[callbackId];
if (callback) {
NSError* error = nil;
if (errorMessage) {
error = [NSError errorWithDomain:@"ShutoApi" code:-1 userInfo:@{NSLocalizedDescriptionKey:errorMessage}];
}
callback(result, error);
[_eventCallbacks removeObjectForKey:callbackId];
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end

View File

@@ -8,6 +8,11 @@ var ShutoApi = {
},
getUserInfo: function (success, error) {
exec(success, error, 'ShutoApi', 'getUserInfo', []);
},
// 执行带回调的事件
executeEvent: function (eventName, params, success, error) {
exec(success, error, 'ShutoApi', 'executeEvent', [eventName, params]);
}
};
@@ -49,7 +54,32 @@ ShutoApi.removeEventListener = function (eventName, callback) {
// 内部事件处理函数,用于接收原生代码的事件通知
function onEvent(eventData) {
if (eventData && eventData.type) {
// 无回调的事件
eventEmitter.emit(eventData.type, eventData.params);
} else if (eventData && eventData.eventName && eventData.callbackId) {
// 带回调的事件
var eventName = eventData.eventName;
var params = eventData.params || {};
var callbackId = eventData.callbackId;
var listeners = eventEmitter.listeners[eventName] || [];
if (listeners.length > 0) {
var callback = function(result, error) {
exec(null, null, 'ShutoApi', 'eventCallback', [callbackId, result, error]);
};
listeners.forEach(function(listener) {
try {
listener(params, callback);
} catch (error) {
console.error('Error in event listener for ' + eventName + ': ' + error);
callback(null, error.message);
}
});
} else {
exec(null, null, 'ShutoApi', 'eventCallback', [callbackId, null, 'No listener for event: ' + eventName]);
}
}
}