controller.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  4. import 'package:get/get.dart';
  5. import '../../../common/api/index.dart';
  6. import '../../../common/models/index.dart';
  7. import '../../../common/routers/index.dart';
  8. import '../../../common/utils/index.dart';
  9. import '../../../common/values/index.dart';
  10. import '../../../common/widgets/index.dart';
  11. import '../../apps/cms/cms_app/index.dart';
  12. import '../../apps/cms/cms_document/index.dart';
  13. import '../create_form/index.dart';
  14. import '../process_webview/index.dart';
  15. import 'index.dart';
  16. class PortalController extends GetxController implements JsNavigationInterface {
  17. PortalController({this.initMap});
  18. final state = PortalState();
  19. final _eventBus = EventBus();
  20. final _eventId = 'portal_page';
  21. final channel = O2FlutterMethodChannelUtils();
  22. // webview控件的控制器
  23. final GlobalKey webViewKey = GlobalKey();
  24. InAppWebViewController? webviewController;
  25. late PullToRefreshController pullToRefreshController;
  26. // webview 通用方法
  27. final webviewHelper = WebviewHelper();
  28. String? currentPortalId;
  29. Map<String, dynamic>? initMap;
  30. // 安装转化js
  31. var isInstallJsName = false;
  32. // cms 创建文档使用的对象
  33. CmsAppData? _app;
  34. CmsCategoryData? _category;
  35. Map<String, dynamic>? _cmsMessageData;
  36. /// 在 widget 内存中分配后立即调用。
  37. @override
  38. void onInit() {
  39. pullToRefreshController = PullToRefreshController(
  40. options: PullToRefreshOptions(
  41. color: Colors.blue,
  42. ),
  43. onRefresh: () async {
  44. OLogger.d('开始刷新。。。。。');
  45. if (GetPlatform.isAndroid) {
  46. webviewController?.reload();
  47. } else if (GetPlatform.isIOS) {
  48. webviewController?.loadUrl(
  49. urlRequest: URLRequest(url: await webviewController?.getUrl()));
  50. }
  51. },
  52. );
  53. super.onInit();
  54. }
  55. /// 在 onInit() 之后调用 1 帧。这是进入的理想场所
  56. @override
  57. void onReady() {
  58. if (initMap != null) {
  59. state.isPage = false; // 嵌入到其他页面内的
  60. state.title = initMap?["title"] ?? "app_type_portal".tr;
  61. String id = initMap?["portalId"] ?? "";
  62. String? pageId = initMap?["pageId"];
  63. String? portalParameters = initMap?["portalParameters"];
  64. state.hiddenAppBar = initMap?['hiddenAppBar'] ?? true;
  65. if (id.isNotEmpty) {
  66. currentPortalId = id;
  67. _initPortalUrl(id, pageId: pageId, portalParameters: portalParameters);
  68. } else {
  69. OLogger.e('没有传入门户 id!!!');
  70. Loading.showError('args_error'.tr);
  71. }
  72. } else {
  73. state.isPage = true; //单独页面
  74. var map = Get.arguments;
  75. if (map != null) {
  76. state.hiddenAppBar = false;
  77. state.title = map["title"] ?? "app_type_portal".tr;
  78. String id = map["portalId"] ?? "";
  79. String? pageId = map["pageId"];
  80. String? portalParameters = map["portalParameters"];
  81. OLogger.i(
  82. "门户id: $id pageId:$pageId portalParameters: $portalParameters");
  83. if (id.isNotEmpty) {
  84. currentPortalId = id;
  85. _initPortalUrl(id,
  86. pageId: pageId, portalParameters: portalParameters);
  87. } else {
  88. Loading.showError('args_error'.tr);
  89. Get.back();
  90. return;
  91. }
  92. }
  93. }
  94. _eventBus.on(EventBus.cmsDocumentCloseMsg, '${_eventId}_$currentPortalId',
  95. (arg) {
  96. _refreshPage();
  97. OLogger.d('cms 文档 关闭后刷新portal页面。。。。。');
  98. });
  99. _eventBus.on(EventBus.processWorkCloseMsg, '${_eventId}_$currentPortalId',
  100. (arg) {
  101. _refreshPage();
  102. OLogger.d('流程工作文档 关闭后刷新portal页面。。。。。');
  103. });
  104. _eventBus.on(EventBus.refreshPortalMsg, '${_eventId}_$currentPortalId',
  105. (arg) {
  106. OLogger.d('刷新门户, $arg');
  107. if (arg != null && arg is String && arg == currentPortalId) {
  108. _startPullToRefresh();
  109. } else {
  110. _refreshPage();
  111. }
  112. });
  113. super.onReady();
  114. }
  115. /// 在 [onDelete] 方法之前调用。
  116. @override
  117. void onClose() {
  118. _eventBus.off(EventBus.cmsDocumentCloseMsg, '${_eventId}_$currentPortalId');
  119. _eventBus.off(EventBus.processWorkCloseMsg, '${_eventId}_$currentPortalId');
  120. _eventBus.off(EventBus.refreshPortalMsg, '${_eventId}_$currentPortalId');
  121. super.onClose();
  122. }
  123. /// dispose 释放内存
  124. @override
  125. void dispose() {
  126. super.dispose();
  127. }
  128. @override
  129. void closeWindow() {
  130. OLogger.d('执行了 jsapi close ');
  131. tapCloseBtn();
  132. }
  133. @override
  134. void goBack() {
  135. OLogger.d('执行了 jsapi goBack ');
  136. tapBackBtn();
  137. }
  138. @override
  139. void setNavigationTitle(String title) {
  140. OLogger.d('执行了 jsapi setNavigationTitle $title ');
  141. state.title = title;
  142. }
  143. /// 显示刷新动画并 刷新页面
  144. void _startPullToRefresh() {
  145. pullToRefreshController.beginRefreshing();
  146. _refreshPage();
  147. }
  148. /// 刷新当前页面
  149. void _refreshPage() {
  150. isInstallJsName = false;
  151. webviewController?.reload();
  152. OLogger.d('执行了页面刷新 reload');
  153. }
  154. void _initPortalUrl(String id,
  155. {String? pageId, String? portalParameters}) async {
  156. var url = O2ApiManager.instance.getPortalUrl(id,
  157. pageId: pageId, portalParameters: portalParameters) ??
  158. "";
  159. if (url.isNotEmpty) {
  160. final uurl = Uri.parse(url);
  161. var host = O2ApiManager.instance.getWebHost();
  162. var domain = uurl.host;
  163. var tokenName = O2ApiManager.instance.tokenName;
  164. var token = O2ApiManager.instance.o2User?.token ?? '';
  165. OLogger.d(
  166. "加载webview cookie,url: $url domain: $domain tokenName: $tokenName token: $token");
  167. CookieManager cookieManager = CookieManager.instance();
  168. await cookieManager.deleteAllCookies();
  169. await cookieManager.setCookie(
  170. url: uurl,
  171. name: tokenName,
  172. value: token,
  173. domain: (domain.isEmpty ? host : domain));
  174. state.url = url;
  175. }
  176. OLogger.d("打开网址: $url");
  177. }
  178. ///
  179. /// 点击appbar 返回按钮
  180. ///
  181. Future<bool> tapBackBtn() async {
  182. if (await webviewController?.canGoBack() == true) {
  183. webviewController?.goBack();
  184. return false;
  185. } else {
  186. tapCloseBtn();
  187. return true;
  188. }
  189. }
  190. void tapCloseBtn() {
  191. _eventBus.emit(EventBus.portalCloseMsg, 'close');
  192. Get.back();
  193. }
  194. ///
  195. /// 加载js通道
  196. ///
  197. void setupWebviewJsHandler(InAppWebViewController c) async {
  198. webviewController = c;
  199. webviewController?.addJavaScriptHandler(
  200. handlerName: O2.webviewChannelNameCommonKey,
  201. callback: (msgs) {
  202. OLogger.d(
  203. "js 通信, name: ${O2.webviewChannelNameCommonKey} msg: $msgs");
  204. if (msgs.isNotEmpty) {
  205. String msg = msgs[0] as String? ?? "";
  206. _jsChannelMessageReceived(msg);
  207. }
  208. });
  209. // 添加 js handler
  210. webviewHelper.setupWebviewJsHandler(c);
  211. webviewHelper.setupJsNavigationInterface(this);
  212. }
  213. /// webview 加载进度
  214. void progressChanged(InAppWebViewController c, int p) {
  215. OLogger.d("o2Webview process progress: $p");
  216. // 这里把 inappwebview的 js handler 方式修改成 我们自定义的
  217. if (p == 100 && !isInstallJsName) {
  218. isInstallJsName = true;
  219. // o2android
  220. var js = '''
  221. if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
  222. window.o2android = {};
  223. window.o2android.postMessage = function(message){
  224. window.flutter_inappwebview.callHandler('o2android', message);
  225. };
  226. }
  227. ''';
  228. c.evaluateJavascript(source: js);
  229. OLogger.d("执行o2android转化js完成。。。");
  230. webviewHelper.changeJsHandlerFunName(c);
  231. }
  232. }
  233. ///
  234. /// 接收js通道返回数据
  235. /// h5上调用js执行flutter这边的原生方法
  236. ///
  237. // void jsChannelMessageReceived(JavascriptMessage message) {
  238. // if (message.message.isNotEmpty) {
  239. void _jsChannelMessageReceived(String message) {
  240. OLogger.d("h5执行原生方法,message: $message");
  241. if (message.isEmpty) {
  242. return;
  243. }
  244. var jsMessage = JsMessage.fromJson(O2Utils.parseStringToJson(message));
  245. switch (jsMessage.type) {
  246. case 'openO2Work':
  247. _openWork(jsMessage.data);
  248. break;
  249. case 'createO2CmsDocument':
  250. _createO2CmsDocument(jsMessage.data);
  251. break;
  252. case 'startProcess':
  253. _startProcess(jsMessage.data);
  254. break;
  255. case 'openO2CmsApplication':
  256. _openO2CmsApplication(jsMessage.data);
  257. break;
  258. case 'openO2CmsDocument':
  259. _openO2CmsDocument(jsMessage.data);
  260. break;
  261. case 'openO2Meeting':
  262. _openO2Meeting(jsMessage.data);
  263. break;
  264. case 'openO2Calendar':
  265. _openO2Calendar(jsMessage.data);
  266. break;
  267. case 'openO2WorkSpace':
  268. _openO2WorkSpace(jsMessage.data);
  269. break;
  270. default:
  271. OLogger.e('错误的类型,$message');
  272. break;
  273. }
  274. _executeCallbackJs(jsMessage.callback, null);
  275. }
  276. ///
  277. /// 打开工作
  278. /// workId: String, workCompletedId: String, title: String
  279. void _openWork(Map<String, dynamic>? data) {
  280. OLogger.d('===> _openWork, data: $data');
  281. if (data != null) {
  282. String workid = data['workId'] ?? '';
  283. if (workid.isEmpty) {
  284. workid = data['workCompletedId'] ?? '';
  285. }
  286. String darftId = data['draftId'] ?? '';
  287. if (workid.isNotEmpty) {
  288. ProcessWebviewPage.open(workid,
  289. title: data['title'] ?? 'process_work_no_title_no_process'.tr);
  290. } else if (darftId.isNotEmpty) {
  291. ProcessWebviewPage.openDraftById(darftId,
  292. title: data['title'] ?? 'process_work_no_title_no_process'.tr);
  293. }
  294. }
  295. }
  296. /// 启动流程,创建工作
  297. /// {"app": app, "process": ""}
  298. Future<void> _startProcess(Map<String, dynamic>? data) async {
  299. OLogger.d('===> _startProcess, data: $data');
  300. if (data == null) {
  301. OLogger.e('data is empty!!!!!!!!!!!!');
  302. Loading.toast('args_error'.tr);
  303. return;
  304. }
  305. var app = data['app'];
  306. var process = data['process'];
  307. if (app != null &&
  308. app is String &&
  309. app.isNotEmpty &&
  310. process != null &&
  311. process is String &&
  312. process.isNotEmpty) {
  313. OLogger.d('app $app process $process');
  314. final processInfo = await ProcessSurfaceService.to
  315. .getWithProcessWithApplication(app, process);
  316. if (processInfo == null) {
  317. Loading.toast('args_error'.tr);
  318. return;
  319. }
  320. //data中获取表单的 data 数据
  321. final workData = data['data'] ?? {};
  322. CreateFormPage.startProcess(true,
  323. process: processInfo, workData: workData);
  324. } else {
  325. OLogger.e('app or process is empty!!!!!!!!!!!!');
  326. Loading.toast('args_error'.tr);
  327. }
  328. }
  329. ///
  330. /// 创建文档
  331. /// * 创建文档 目前只有 column 和 category 有效果
  332. /* {
  333. "column" : column, //(string)可选,内容管理应用(栏目)的名称、别名或ID
  334. "category" : category, //(string)可选,要创建的文档所属的分类的名称、别名或ID
  335. "data" : data, //(json object)可选,创建文档时默认的业务数据
  336. "identity" : identity, //(string)可选,创建文档所使用的身份。如果此参数为空,且当前人有多个身份的情况下,会弹出身份选择对话框;否则使用默认身份。
  337. "callback" : callback, //(funcation)可选,文档创建后的回调函数。
  338. "target" : target, //(boolean)可选,为true时,在当前页面打开创建的文档;否则打开新窗口。默认false。
  339. "latest" : latest, //(boolean)可选,为true时,如果当前用户已经创建了此分类的文档,并且没有发布过,直接调用此文档为新文档;否则创建一个新文档。默认true。
  340. "selectColumnEnable" : selectColumnEnable, //(boolean)可选,是否可以选择应用和分类进行创建文档。有category参数时为默认false,否则默认为true。
  341. "ignoreTitle" : ignoreTitle //(boolean)可选,值为false时,创建的时候需要强制填写标题,默认为false。
  342. "restrictToColumn" : restrictToColumn //(boolean)可选,值为true时,会限制在传入的栏目中选择分类,默认为false。
  343. } */
  344. ///
  345. void _createO2CmsDocument(Map<String, dynamic>? data) async {
  346. OLogger.d('===> _createO2CmsDocument, data: $data');
  347. _app = null;
  348. _category = null;
  349. if (data != null) {
  350. _cmsMessageData = data;
  351. var categoryId = _cmsMessageData?['category'];
  352. if (categoryId != null && categoryId is String && categoryId.isNotEmpty) {
  353. // 根据分类id查询 分类和应用
  354. _category = await CmsAssembleControlService.to.getCategory(categoryId);
  355. if (_category != null && _category?.appId != null && _app == null) {
  356. _app = await CmsAssembleControlService.to.getApp(_category!.appId!);
  357. }
  358. } else {
  359. var appId = _cmsMessageData?['column'];
  360. if (appId != null && appId is String && appId.isNotEmpty) {
  361. // 只有应用id的情况 弹出分类选择器
  362. _app = await CmsAssembleControlService.to
  363. .getAppCanPublishCategories(appId);
  364. if (_app != null &&
  365. _app?.wrapOutCategoryList != null &&
  366. _app!.wrapOutCategoryList!.isNotEmpty) {
  367. var cList = _app!.wrapOutCategoryList!;
  368. if (cList.length == 1) {
  369. _category = cList[0];
  370. } else {
  371. _showCmsCategoryChoose(cList);
  372. return;
  373. }
  374. }
  375. }
  376. }
  377. }
  378. // 传入参数不足,选择分类
  379. if (_app == null || _category == null) {
  380. var category = await Get.toNamed(O2OARoutes.appCmsCategoryPicker);
  381. if (category != null && category is CmsCategoryData) {
  382. OLogger.d('选择了分类: ${category.toJson()}');
  383. _app = category.withApp;
  384. _category = category;
  385. }
  386. }
  387. _startCreateDocument();
  388. }
  389. /// 选择分类
  390. void _showCmsCategoryChoose(List<CmsCategoryData> list) {
  391. final c = Get.context;
  392. if (c != null) {
  393. O2UI.showBottomSheetWithCancel(
  394. c,
  395. list
  396. .map((e) => ListTile(
  397. onTap: () {
  398. Navigator.pop(c);
  399. _category = e;
  400. _startCreateDocument();
  401. },
  402. title: Align(
  403. alignment: Alignment.center,
  404. child: Text(e.categoryName ?? '',
  405. style: Theme.of(c).textTheme.bodyMedium),
  406. ),
  407. ))
  408. .toList());
  409. }
  410. }
  411. ///
  412. /// 参数查询完成后开始创建文档
  413. /// _app、_category 都赋值后
  414. ///
  415. void _startCreateDocument() async {
  416. if (_app == null || _category == null) {
  417. Loading.toast('cms_create_document_no_args'.tr);
  418. return;
  419. }
  420. OLogger.d('创建文档,app:${_app?.toJson()}');
  421. OLogger.d('创建文档,category:${_category?.toJson()}');
  422. final config = _app?.config ?? '{}';
  423. final configMap = O2Utils.parseStringToJson(config);
  424. final messageData = _cmsMessageData ?? {};
  425. bool ignoreTitle = false; // 是否忽略标题
  426. bool latest = true; // 是否查询草稿
  427. if (messageData.containsKey('ignoreTitle')) {
  428. ignoreTitle = messageData['ignoreTitle'] ?? false;
  429. } else if (configMap.containsKey('ignoreTitle')) {
  430. ignoreTitle = configMap['ignoreTitle'] ?? false;
  431. }
  432. if (messageData.containsKey('latest')) {
  433. latest = messageData['latest'] ?? true;
  434. } else if (configMap.containsKey('latest')) {
  435. latest = configMap['latest'] ?? true;
  436. }
  437. if (latest) {
  438. var drafts =
  439. await CmsAssembleControlService.to.listDocumentDraft(_category!.id!);
  440. if (drafts != null && drafts.isNotEmpty) {
  441. OLogger.d('有草稿。。。。');
  442. CmsDocumentPage.open(drafts[0].id!,
  443. title: drafts[0].title ?? '', options: {"readonly": false});
  444. return;
  445. }
  446. }
  447. CreateFormPage.startCmsDoc(ignoreTitle, category: _category!, cmsData: messageData['data']);
  448. }
  449. ///
  450. /// 打开信息中心
  451. /// appId: String, title: String
  452. ///
  453. void _openO2CmsApplication(Map<String, dynamic>? data) async {
  454. if (data != null && data['appId'] != null && data['appId'] is String) {
  455. var list =
  456. await CmsAssembleControlService.to.listAppWithCategoryUserCanView();
  457. if (list != null) {
  458. var app = list.firstWhereOrNull((element) =>
  459. element.id == data['appId'] ||
  460. element.appAlias == data['appId'] ||
  461. element.appName == data['appId']);
  462. if (app != null) {
  463. CmsAppPage.open(app); // 打开对应的
  464. return;
  465. }
  466. }
  467. }
  468. // 没有参数 直接打开cms
  469. Get.toNamed(O2OARoutes.appCms);
  470. }
  471. ///
  472. /// 打开信息文档
  473. /// docId: String, title: String, options: Map<String, dynamic>?
  474. ///
  475. void _openO2CmsDocument(Map<String, dynamic>? data) {
  476. if (data != null) {
  477. String docId = data['docId'] ?? '';
  478. if (docId.isNotEmpty) {
  479. CmsDocumentPage.open(docId,
  480. title: data['title'] ?? '', options: data['options']);
  481. }
  482. }
  483. }
  484. ///
  485. /// 打开 会议管理
  486. ///
  487. void _openO2Meeting(Map<String, dynamic>? data) {
  488. Get.toNamed(O2OARoutes.appMeeting);
  489. }
  490. ///
  491. /// 打开 日程管理
  492. ///
  493. void _openO2Calendar(Map<String, dynamic>? data) {
  494. Get.toNamed(O2OARoutes.appCalendar);
  495. }
  496. ///
  497. /// 打开 办公中心
  498. /// type: String task taskcompleted read readcompleted
  499. ///
  500. void _openO2WorkSpace(Map<String, dynamic>? data) {
  501. if (data != null) {
  502. String type = data['type'];
  503. switch (type) {
  504. case 'task':
  505. Get.toNamed(O2OARoutes.appTask);
  506. break;
  507. case 'taskcompleted':
  508. Get.toNamed(O2OARoutes.appTaskcompleted);
  509. break;
  510. case 'read':
  511. Get.toNamed(O2OARoutes.appRead);
  512. break;
  513. case 'readcompleted':
  514. Get.toNamed(O2OARoutes.appReadcompleted);
  515. break;
  516. default:
  517. break;
  518. }
  519. }
  520. }
  521. ///
  522. /// 如果有callback函数 就执行
  523. ///
  524. void _executeCallbackJs(String? callback, dynamic result) {
  525. if (callback != null && callback.isNotEmpty) {
  526. if (result != null) {
  527. webviewController?.evaluateJavascript(source: '$callback($result)');
  528. } else {
  529. webviewController?.evaluateJavascript(source: '$callback()');
  530. }
  531. }
  532. }
  533. /// 复制当前链接
  534. void copyLink() async {
  535. final url = await webviewController?.getUrl();
  536. if (url != null) {
  537. Clipboard.setData(ClipboardData(text: url.toString()));
  538. Loading.toast('im_chat_success_copy'.tr);
  539. }
  540. }
  541. }