import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:get/get.dart'; import '../../../common/api/index.dart'; import '../../../common/models/index.dart'; import '../../../common/routers/index.dart'; import '../../../common/utils/index.dart'; import '../../../common/values/index.dart'; import '../../../common/widgets/index.dart'; import '../../apps/cms/cms_app/index.dart'; import '../../apps/cms/cms_document/index.dart'; import '../create_form/index.dart'; import '../process_webview/index.dart'; import 'index.dart'; class PortalController extends GetxController implements JsNavigationInterface { PortalController({this.initMap}); final state = PortalState(); final _eventBus = EventBus(); final _eventId = 'portal_page'; final channel = O2FlutterMethodChannelUtils(); // webview控件的控制器 final GlobalKey webViewKey = GlobalKey(); InAppWebViewController? webviewController; late PullToRefreshController pullToRefreshController; // webview 通用方法 final webviewHelper = WebviewHelper(); String? currentPortalId; Map? initMap; // 安装转化js var isInstallJsName = false; // cms 创建文档使用的对象 CmsAppData? _app; CmsCategoryData? _category; Map? _cmsMessageData; /// 在 widget 内存中分配后立即调用。 @override void onInit() { pullToRefreshController = PullToRefreshController( options: PullToRefreshOptions( color: Colors.blue, ), onRefresh: () async { OLogger.d('开始刷新。。。。。'); if (GetPlatform.isAndroid) { webviewController?.reload(); } else if (GetPlatform.isIOS) { webviewController?.loadUrl( urlRequest: URLRequest(url: await webviewController?.getUrl())); } }, ); super.onInit(); } /// 在 onInit() 之后调用 1 帧。这是进入的理想场所 @override void onReady() { if (initMap != null) { state.isPage = false; // 嵌入到其他页面内的 state.title = initMap?["title"] ?? "app_type_portal".tr; String id = initMap?["portalId"] ?? ""; String? pageId = initMap?["pageId"]; String? portalParameters = initMap?["portalParameters"]; state.hiddenAppBar = initMap?['hiddenAppBar'] ?? true; if (id.isNotEmpty) { currentPortalId = id; _initPortalUrl(id, pageId: pageId, portalParameters: portalParameters); } else { OLogger.e('没有传入门户 id!!!'); Loading.showError('args_error'.tr); } } else { state.isPage = true; //单独页面 var map = Get.arguments; if (map != null) { state.hiddenAppBar = false; state.title = map["title"] ?? "app_type_portal".tr; String id = map["portalId"] ?? ""; String? pageId = map["pageId"]; String? portalParameters = map["portalParameters"]; OLogger.i( "门户id: $id pageId:$pageId portalParameters: $portalParameters"); if (id.isNotEmpty) { currentPortalId = id; _initPortalUrl(id, pageId: pageId, portalParameters: portalParameters); } else { Loading.showError('args_error'.tr); Get.back(); return; } } } _eventBus.on(EventBus.cmsDocumentCloseMsg, '${_eventId}_$currentPortalId', (arg) { _refreshPage(); OLogger.d('cms 文档 关闭后刷新portal页面。。。。。'); }); _eventBus.on(EventBus.processWorkCloseMsg, '${_eventId}_$currentPortalId', (arg) { _refreshPage(); OLogger.d('流程工作文档 关闭后刷新portal页面。。。。。'); }); _eventBus.on(EventBus.refreshPortalMsg, '${_eventId}_$currentPortalId', (arg) { OLogger.d('刷新门户, $arg'); if (arg != null && arg is String && arg == currentPortalId) { _startPullToRefresh(); } else { _refreshPage(); } }); super.onReady(); } /// 在 [onDelete] 方法之前调用。 @override void onClose() { _eventBus.off(EventBus.cmsDocumentCloseMsg, '${_eventId}_$currentPortalId'); _eventBus.off(EventBus.processWorkCloseMsg, '${_eventId}_$currentPortalId'); _eventBus.off(EventBus.refreshPortalMsg, '${_eventId}_$currentPortalId'); super.onClose(); } /// dispose 释放内存 @override void dispose() { super.dispose(); } @override void closeWindow() { OLogger.d('执行了 jsapi close '); tapCloseBtn(); } @override void goBack() { OLogger.d('执行了 jsapi goBack '); tapBackBtn(); } @override void setNavigationTitle(String title) { OLogger.d('执行了 jsapi setNavigationTitle $title '); state.title = title; } /// 显示刷新动画并 刷新页面 void _startPullToRefresh() { pullToRefreshController.beginRefreshing(); _refreshPage(); } /// 刷新当前页面 void _refreshPage() { isInstallJsName = false; webviewController?.reload(); OLogger.d('执行了页面刷新 reload'); } void _initPortalUrl(String id, {String? pageId, String? portalParameters}) async { var url = O2ApiManager.instance.getPortalUrl(id, pageId: pageId, portalParameters: portalParameters) ?? ""; if (url.isNotEmpty) { final uurl = Uri.parse(url); var host = O2ApiManager.instance.getWebHost(); var domain = uurl.host; var tokenName = O2ApiManager.instance.tokenName; var token = O2ApiManager.instance.o2User?.token ?? ''; OLogger.d( "加载webview cookie,url: $url domain: $domain tokenName: $tokenName token: $token"); CookieManager cookieManager = CookieManager.instance(); await cookieManager.deleteAllCookies(); await cookieManager.setCookie( url: uurl, name: tokenName, value: token, domain: (domain.isEmpty ? host : domain)); state.url = url; } OLogger.d("打开网址: $url"); } /// /// 点击appbar 返回按钮 /// Future tapBackBtn() async { if (await webviewController?.canGoBack() == true) { webviewController?.goBack(); return false; } else { tapCloseBtn(); return true; } } void tapCloseBtn() { _eventBus.emit(EventBus.portalCloseMsg, 'close'); Get.back(); } /// /// 加载js通道 /// void setupWebviewJsHandler(InAppWebViewController c) async { webviewController = c; webviewController?.addJavaScriptHandler( handlerName: O2.webviewChannelNameCommonKey, callback: (msgs) { OLogger.d( "js 通信, name: ${O2.webviewChannelNameCommonKey} msg: $msgs"); if (msgs.isNotEmpty) { String msg = msgs[0] as String? ?? ""; _jsChannelMessageReceived(msg); } }); // 添加 js handler webviewHelper.setupWebviewJsHandler(c); webviewHelper.setupJsNavigationInterface(this); } /// webview 加载进度 void progressChanged(InAppWebViewController c, int p) { OLogger.d("o2Webview process progress: $p"); // 这里把 inappwebview的 js handler 方式修改成 我们自定义的 if (p == 100 && !isInstallJsName) { isInstallJsName = true; // o2android var js = ''' if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) { window.o2android = {}; window.o2android.postMessage = function(message){ window.flutter_inappwebview.callHandler('o2android', message); }; } '''; c.evaluateJavascript(source: js); OLogger.d("执行o2android转化js完成。。。"); webviewHelper.changeJsHandlerFunName(c); } } /// /// 接收js通道返回数据 /// h5上调用js执行flutter这边的原生方法 /// // void jsChannelMessageReceived(JavascriptMessage message) { // if (message.message.isNotEmpty) { void _jsChannelMessageReceived(String message) { OLogger.d("h5执行原生方法,message: $message"); if (message.isEmpty) { return; } var jsMessage = JsMessage.fromJson(O2Utils.parseStringToJson(message)); switch (jsMessage.type) { case 'openO2Work': _openWork(jsMessage.data); break; case 'createO2CmsDocument': _createO2CmsDocument(jsMessage.data); break; case 'startProcess': _startProcess(jsMessage.data); break; case 'openO2CmsApplication': _openO2CmsApplication(jsMessage.data); break; case 'openO2CmsDocument': _openO2CmsDocument(jsMessage.data); break; case 'openO2Meeting': _openO2Meeting(jsMessage.data); break; case 'openO2Calendar': _openO2Calendar(jsMessage.data); break; case 'openO2WorkSpace': _openO2WorkSpace(jsMessage.data); break; default: OLogger.e('错误的类型,$message'); break; } _executeCallbackJs(jsMessage.callback, null); } /// /// 打开工作 /// workId: String, workCompletedId: String, title: String void _openWork(Map? data) { OLogger.d('===> _openWork, data: $data'); if (data != null) { String workid = data['workId'] ?? ''; if (workid.isEmpty) { workid = data['workCompletedId'] ?? ''; } String darftId = data['draftId'] ?? ''; if (workid.isNotEmpty) { ProcessWebviewPage.open(workid, title: data['title'] ?? 'process_work_no_title_no_process'.tr); } else if (darftId.isNotEmpty) { ProcessWebviewPage.openDraftById(darftId, title: data['title'] ?? 'process_work_no_title_no_process'.tr); } } } /// 启动流程,创建工作 /// {"app": app, "process": ""} Future _startProcess(Map? data) async { OLogger.d('===> _startProcess, data: $data'); if (data == null) { OLogger.e('data is empty!!!!!!!!!!!!'); Loading.toast('args_error'.tr); return; } var app = data['app']; var process = data['process']; if (app != null && app is String && app.isNotEmpty && process != null && process is String && process.isNotEmpty) { OLogger.d('app $app process $process'); final processInfo = await ProcessSurfaceService.to .getWithProcessWithApplication(app, process); if (processInfo == null) { Loading.toast('args_error'.tr); return; } //data中获取表单的 data 数据 final workData = data['data'] ?? {}; CreateFormPage.startProcess(true, process: processInfo, workData: workData); } else { OLogger.e('app or process is empty!!!!!!!!!!!!'); Loading.toast('args_error'.tr); } } /// /// 创建文档 /// * 创建文档 目前只有 column 和 category 有效果 /* { "column" : column, //(string)可选,内容管理应用(栏目)的名称、别名或ID "category" : category, //(string)可选,要创建的文档所属的分类的名称、别名或ID "data" : data, //(json object)可选,创建文档时默认的业务数据 "identity" : identity, //(string)可选,创建文档所使用的身份。如果此参数为空,且当前人有多个身份的情况下,会弹出身份选择对话框;否则使用默认身份。 "callback" : callback, //(funcation)可选,文档创建后的回调函数。 "target" : target, //(boolean)可选,为true时,在当前页面打开创建的文档;否则打开新窗口。默认false。 "latest" : latest, //(boolean)可选,为true时,如果当前用户已经创建了此分类的文档,并且没有发布过,直接调用此文档为新文档;否则创建一个新文档。默认true。 "selectColumnEnable" : selectColumnEnable, //(boolean)可选,是否可以选择应用和分类进行创建文档。有category参数时为默认false,否则默认为true。 "ignoreTitle" : ignoreTitle //(boolean)可选,值为false时,创建的时候需要强制填写标题,默认为false。 "restrictToColumn" : restrictToColumn //(boolean)可选,值为true时,会限制在传入的栏目中选择分类,默认为false。 } */ /// void _createO2CmsDocument(Map? data) async { OLogger.d('===> _createO2CmsDocument, data: $data'); _app = null; _category = null; if (data != null) { _cmsMessageData = data; var categoryId = _cmsMessageData?['category']; if (categoryId != null && categoryId is String && categoryId.isNotEmpty) { // 根据分类id查询 分类和应用 _category = await CmsAssembleControlService.to.getCategory(categoryId); if (_category != null && _category?.appId != null && _app == null) { _app = await CmsAssembleControlService.to.getApp(_category!.appId!); } } else { var appId = _cmsMessageData?['column']; if (appId != null && appId is String && appId.isNotEmpty) { // 只有应用id的情况 弹出分类选择器 _app = await CmsAssembleControlService.to .getAppCanPublishCategories(appId); if (_app != null && _app?.wrapOutCategoryList != null && _app!.wrapOutCategoryList!.isNotEmpty) { var cList = _app!.wrapOutCategoryList!; if (cList.length == 1) { _category = cList[0]; } else { _showCmsCategoryChoose(cList); return; } } } } } // 传入参数不足,选择分类 if (_app == null || _category == null) { var category = await Get.toNamed(O2OARoutes.appCmsCategoryPicker); if (category != null && category is CmsCategoryData) { OLogger.d('选择了分类: ${category.toJson()}'); _app = category.withApp; _category = category; } } _startCreateDocument(); } /// 选择分类 void _showCmsCategoryChoose(List list) { final c = Get.context; if (c != null) { O2UI.showBottomSheetWithCancel( c, list .map((e) => ListTile( onTap: () { Navigator.pop(c); _category = e; _startCreateDocument(); }, title: Align( alignment: Alignment.center, child: Text(e.categoryName ?? '', style: Theme.of(c).textTheme.bodyMedium), ), )) .toList()); } } /// /// 参数查询完成后开始创建文档 /// _app、_category 都赋值后 /// void _startCreateDocument() async { if (_app == null || _category == null) { Loading.toast('cms_create_document_no_args'.tr); return; } OLogger.d('创建文档,app:${_app?.toJson()}'); OLogger.d('创建文档,category:${_category?.toJson()}'); final config = _app?.config ?? '{}'; final configMap = O2Utils.parseStringToJson(config); final messageData = _cmsMessageData ?? {}; bool ignoreTitle = false; // 是否忽略标题 bool latest = true; // 是否查询草稿 if (messageData.containsKey('ignoreTitle')) { ignoreTitle = messageData['ignoreTitle'] ?? false; } else if (configMap.containsKey('ignoreTitle')) { ignoreTitle = configMap['ignoreTitle'] ?? false; } if (messageData.containsKey('latest')) { latest = messageData['latest'] ?? true; } else if (configMap.containsKey('latest')) { latest = configMap['latest'] ?? true; } if (latest) { var drafts = await CmsAssembleControlService.to.listDocumentDraft(_category!.id!); if (drafts != null && drafts.isNotEmpty) { OLogger.d('有草稿。。。。'); CmsDocumentPage.open(drafts[0].id!, title: drafts[0].title ?? '', options: {"readonly": false}); return; } } CreateFormPage.startCmsDoc(ignoreTitle, category: _category!, cmsData: messageData['data']); } /// /// 打开信息中心 /// appId: String, title: String /// void _openO2CmsApplication(Map? data) async { if (data != null && data['appId'] != null && data['appId'] is String) { var list = await CmsAssembleControlService.to.listAppWithCategoryUserCanView(); if (list != null) { var app = list.firstWhereOrNull((element) => element.id == data['appId'] || element.appAlias == data['appId'] || element.appName == data['appId']); if (app != null) { CmsAppPage.open(app); // 打开对应的 return; } } } // 没有参数 直接打开cms Get.toNamed(O2OARoutes.appCms); } /// /// 打开信息文档 /// docId: String, title: String, options: Map? /// void _openO2CmsDocument(Map? data) { if (data != null) { String docId = data['docId'] ?? ''; if (docId.isNotEmpty) { CmsDocumentPage.open(docId, title: data['title'] ?? '', options: data['options']); } } } /// /// 打开 会议管理 /// void _openO2Meeting(Map? data) { Get.toNamed(O2OARoutes.appMeeting); } /// /// 打开 日程管理 /// void _openO2Calendar(Map? data) { Get.toNamed(O2OARoutes.appCalendar); } /// /// 打开 办公中心 /// type: String task taskcompleted read readcompleted /// void _openO2WorkSpace(Map? data) { if (data != null) { String type = data['type']; switch (type) { case 'task': Get.toNamed(O2OARoutes.appTask); break; case 'taskcompleted': Get.toNamed(O2OARoutes.appTaskcompleted); break; case 'read': Get.toNamed(O2OARoutes.appRead); break; case 'readcompleted': Get.toNamed(O2OARoutes.appReadcompleted); break; default: break; } } } /// /// 如果有callback函数 就执行 /// void _executeCallbackJs(String? callback, dynamic result) { if (callback != null && callback.isNotEmpty) { if (result != null) { webviewController?.evaluateJavascript(source: '$callback($result)'); } else { webviewController?.evaluateJavascript(source: '$callback()'); } } } /// 复制当前链接 void copyLink() async { final url = await webviewController?.getUrl(); if (url != null) { Clipboard.setData(ClipboardData(text: url.toString())); Loading.toast('im_chat_success_copy'.tr); } } }