Browse Source

[new]调整思维导图导出功能

zk 1 year ago
parent
commit
2eaa33a4da

File diff suppressed because it is too large
+ 15 - 77
assets/html/index.html


+ 125 - 0
assets/html/index_export.html

@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <title>思维导图</title>
+    <style>
+        * {
+          margin: 0;
+          padding: 0;
+          box-sizing: border-box;
+        }
+
+        #markmap {
+          display: flex;
+          width: 100%;
+          height: auto;
+          aspect-ratio: 1;
+        }
+    </style>
+
+    <script src="./dsbridge.js"></script>
+    <script src="./d3.js"></script>
+    <script src="./markmap-lib.js"></script>
+    <script src="./markmap-view.js"></script>
+    <script src="./svg-png.js"></script>
+</head>
+
+<body>
+<svg id="markmap"></svg>
+
+</body>
+
+<script>
+    const { markmap } = window;
+    const { Transformer, Markmap, loadCSS, loadJS } = window.markmap;
+    const transformer = new Transformer([]);
+    const { scripts, styles } = transformer.getAssets();
+    loadCSS(styles);
+    loadJS(scripts, { getMarkmap: () => markmap });
+
+    let mm;
+    let index = 0;
+    let title = "思维导图";
+    let description = "";
+    const branchColors = {};
+    const colors = ["#F63F2F", "#F6A24B", "#F7D422", "#02BB7B", "#4869FF", "#7416E6"];
+
+   
+
+    async function mindMapSaveAsPngCreateFile() {
+      const svgEl = document.querySelector("#markmap");
+      const markmap = Markmap.create(svgEl, {
+        color: (node) => {
+          const { depth, path, id } = node.state;
+          // 根节点
+          if (depth === 1) {
+            return colors[0];
+          }
+          // 二级节点
+          if (depth === 2) {
+            if (!branchColors[path]) {
+              branchColors[path] = {
+                color: colors[index % colors.length],
+                index: index,
+              };
+              index++;
+            }
+            return branchColors[path].color;
+          }
+          // 二级以下节点
+          const rootPath = path.split(".")[1];
+          return branchColors["1." + rootPath].color;
+        },
+        duration: 0, // 动画时长
+        maxWidth: 400, // 节点最大宽度
+      });
+
+      const { root } = transformer.transform(description);
+      markmap.setData(root);
+      markmap.fit();
+
+      const { minX, maxX, minY, maxY } = markmap.state;
+      const naturalWidth = maxY - minY;
+      const naturalHeight = maxX - minX;
+      const ratio = naturalHeight / naturalWidth;
+      svgEl.style.aspectRatio = naturalWidth / naturalHeight;
+
+      markmap.setData(root);
+      markmap.fit();
+     
+      await delay(300);
+      
+      const base64 = await svgToPng("#markmap", title, { scale: 3, format: "png", background: "white" });
+      markmap.destroy();
+      return base64;
+    }
+
+    function delay(time) {
+      return new Promise((resolve) => {
+        setTimeout(() => {
+          resolve();
+        }, time);
+      });
+    }
+
+
+    // 注册更新数据的方法
+    dsBridge.register("updateValue", function (value) {
+      description = value;
+      updateValue(value);
+    });
+    // 导出
+    dsBridge.registerAsyn("export", async (fileName,summary, responseCallback) => {
+      if (fileName) {
+        title = fileName;
+      }
+      if(summary){
+        description = summary;
+      }
+      const bytes = await mindMapSaveAsPngCreateFile();
+      responseCallback(bytes);
+    });
+</script>
+</html>

+ 58 - 0
assets/html/svg-png.js

@@ -0,0 +1,58 @@
+/**
+ * Minified by jsDelivr using Terser v5.15.1.
+ * Original file: /npm/d3-svg-to-png@0.3.1/index.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+function inlineStyles(e, t) {
+  const n = window.getComputedStyle(e);
+  for (const e of n) t.style[e] = n[e];
+  for (let n = 0; n < e.children.length; n++) inlineStyles(e.children[n], t.children[n]);
+}
+function copyToCanvas({ source: e, target: t, scale: n, format: o, quality: l }) {
+  let a = new XMLSerializer().serializeToString(t),
+    i = document.createElement("canvas"),
+    r = e.getBoundingClientRect();
+  (i.width = r.width * n), (i.height = r.height * n), (i.style.width = r.width), (i.style.height = r.height);
+  let c = i.getContext("2d");
+  c.scale(n, n);
+  let d = document.createElement("img");
+  return (
+    d.setAttribute("src", "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(a)))),
+    new Promise((e) => {
+      d.onload = () => {
+        c.drawImage(d, 0, 0), e(i.toDataURL(`image/${"jpg" === o ? "jpeg" : o}`, l));
+      };
+    })
+  );
+}
+function downloadImage({ file: e, name: t, format: n }) {
+  let o = document.createElement("a");
+  (o.download = `${t}.${n}`), (o.href = e), document.body.appendChild(o), o.click(), document.body.removeChild(o);
+}
+async function svgToPng(
+  e,
+  t,
+  {
+    scale: n = 1,
+    format: o = "png",
+    quality: l = 0.92,
+    download: a = !0,
+    ignore: i = null,
+    cssinline: r = 1,
+    background: c = null,
+  } = {}
+) {
+  e = e instanceof Element ? e : document.querySelector(e);
+  const d = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+  d.innerHTML = e.innerHTML;
+  for (const t of e.attributes) d.setAttribute(t.name, t.value);
+  if ((1 === r && inlineStyles(e, d), c && (d.style.background = c), null != i)) {
+    const e = d.querySelectorAll(i);
+    [].forEach.call(e, (e) => e.parentNode.removeChild(e));
+  }
+  const s = await copyToCanvas({ source: e, target: d, scale: n, format: o, quality: l });
+  return a && downloadImage({ file: s, name: t, format: o }), s;
+}
+window.svgToPng = svgToPng;
+//# sourceMappingURL=/sm/14eea0125d103380ac8a8d5d87aec7122e2b707361f72c63dfeb2e62361d38e4.map

+ 3 - 0
assets/string/base/string.xml

@@ -149,4 +149,7 @@
     <string name="talk_uploading_file_tip">录音上传中,请勿关闭小听</string>
     <string name="talk_uploading_file_tip">录音上传中,请勿关闭小听</string>
     <string name="talk_add_template">新建模板</string>
     <string name="talk_add_template">新建模板</string>
     <string name="talk_mind_full_screen">全屏观看</string>
     <string name="talk_mind_full_screen">全屏观看</string>
+    <string name="template_add_title">自定义模板</string>
+    <string name="template_too_much">模板太多啦,去模板管理做下精简吧</string>
+    <string name="template_to_manage">去管理</string>
 </resources>
 </resources>

+ 4 - 1
lib/data/api/response/talk_info_response.dart

@@ -13,7 +13,10 @@ class TalkInfoResponse {
   @JsonKey(name: 'templates')
   @JsonKey(name: 'templates')
   List<TemplateBean>? templateList;
   List<TemplateBean>? templateList;
 
 
-  TalkInfoResponse({this.talkInfo, this.templateList});
+  @JsonKey(name: 'maxTemplateCount')
+  int? maxTemplateCount;
+
+  TalkInfoResponse({this.talkInfo, this.templateList, this.maxTemplateCount});
 
 
   factory TalkInfoResponse.fromJson(Map<String, dynamic> json) =>
   factory TalkInfoResponse.fromJson(Map<String, dynamic> json) =>
       _$TalkInfoResponseFromJson(json);
       _$TalkInfoResponseFromJson(json);

+ 0 - 1
lib/dialog/talk_share_dialog.dart

@@ -25,7 +25,6 @@ void showTalkShareDialog(String? title, TalkShareCallback callback) {
     } else {
     } else {
       return '[${StringName.talkTabOriginal.tr}] $title.txt';
       return '[${StringName.talkTabOriginal.tr}] $title.txt';
     }
     }
-    return '${shareType.value == ShareTalkType.summary ? '[谈话总结]' : '[谈话原文]'} $title.txt';
   }
   }
 
 
   SmartDialog.show(
   SmartDialog.show(

+ 23 - 19
lib/module/talk/common_view.dart

@@ -411,25 +411,29 @@ Widget buildTemplateView(List<TemplateBean>? templateList, int? id,
 }
 }
 
 
 Widget _buildAddTemplateView(VoidCallback? addCallback) {
 Widget _buildAddTemplateView(VoidCallback? addCallback) {
-  return Container(
-    height: double.infinity,
-    decoration: BoxDecoration(
-      color: "#FAF9FB".toColor(),
-      borderRadius: BorderRadius.circular(6.w),
-      border: Border.all(color: '#F2EFF5'.toColor(), width: 1.w),
-    ),
-    padding: EdgeInsets.symmetric(horizontal: 8.w),
-    child: Center(
-      child: IntrinsicWidth(
-        child: Row(
-          children: [
-            Assets.images.iconTalkAddTemplate.image(width: 16.w, height: 16.w),
-            Text(StringName.talkAddTemplate.tr,
-                style: TextStyle(
-                    height: 1,
-                    fontSize: 13.sp,
-                    color: ColorName.tertiaryTextColor))
-          ],
+  return GestureDetector(
+    onTap: addCallback,
+    child: Container(
+      height: double.infinity,
+      decoration: BoxDecoration(
+        color: "#FAF9FB".toColor(),
+        borderRadius: BorderRadius.circular(6.w),
+        border: Border.all(color: '#F2EFF5'.toColor(), width: 1.w),
+      ),
+      padding: EdgeInsets.symmetric(horizontal: 8.w),
+      child: Center(
+        child: IntrinsicWidth(
+          child: Row(
+            children: [
+              Assets.images.iconTalkAddTemplate
+                  .image(width: 16.w, height: 16.w),
+              Text(StringName.talkAddTemplate.tr,
+                  style: TextStyle(
+                      height: 1,
+                      fontSize: 13.sp,
+                      color: ColorName.tertiaryTextColor))
+            ],
+          ),
         ),
         ),
       ),
       ),
     ),
     ),

+ 44 - 32
lib/module/talk/controller.dart

@@ -1,4 +1,5 @@
 import 'dart:async';
 import 'dart:async';
+import 'dart:ffi';
 import 'dart:io';
 import 'dart:io';
 import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:dsbridge_flutter/dsbridge_flutter.dart';
 import 'package:dsbridge_flutter/dsbridge_flutter.dart';
@@ -134,6 +135,9 @@ class TalkController extends BaseController {
 
 
   final RxBool isShowMindFullScreen = false.obs;
   final RxBool isShowMindFullScreen = false.obs;
 
 
+  //模板最大数量
+  int? maxTemplateCount;
+
   final mindFullDuration = const Duration(milliseconds: 250);
   final mindFullDuration = const Duration(milliseconds: 250);
 
 
   GlobalKey headGlobalKey = GlobalKey();
   GlobalKey headGlobalKey = GlobalKey();
@@ -147,6 +151,10 @@ class TalkController extends BaseController {
   final DWebViewController webViewController =
   final DWebViewController webViewController =
       MindUtil.createMindWebViewController();
       MindUtil.createMindWebViewController();
 
 
+  final Rxn<DWebViewController> _temporaryController = Rxn();
+
+  DWebViewController? get temporaryController => _temporaryController.value;
+
   @override
   @override
   void onInit() {
   void onInit() {
     super.onInit();
     super.onInit();
@@ -635,42 +643,45 @@ class TalkController extends BaseController {
         _shareSummaryOrOriginal(
         _shareSummaryOrOriginal(
             talkBean.value!.id, fileName, type, shareTo, tag);
             talkBean.value!.id, fileName, type, shareTo, tag);
       } else if (type == ShareTalkType.mindMap) {
       } else if (type == ShareTalkType.mindMap) {
-        _shareMindMap(
-            talkBean.value?.title.value, talkBean.value?.summary.value);
+        _shareMindMap(fileName, talkBean.value?.summary.value);
       }
       }
     });
     });
   }
   }
 
 
-  Future<String> loadSvgFromAssets() async {
-    String svgString = await rootBundle.loadString('assets/images/test2.svg');
-    return svgString;
-  }
-
-  void _shareMindMap(String? title, String? summary) async {
-    // String svgString = await loadSvgFromAssets();
-    // final pngBytes = await MindUtil.svgToPng(svgString, Get.context!);
-    // MindUtil.convertToFile(pngBytes, 'test.png');
-    // webViewController.callHandler(MindUtil.functionToJsExport, args: ['test'],
-    //     handler: (value) async {
-    //       if (value == null) {
-    //         throw Exception('Unable to convert SVG to PNG');
-    //       }
-    //       debugPrint('mindMap summary: $value');
-    //
-    //       ToastUtil.showToast('思维导图svg下载成功');
-    //     });
-
-    // await webViewController.loadFlutterAsset(Assets.html.index);
-    // webViewController.setNavigationDelegate(
-    //   NavigationDelegate(
-    //     onPageFinished: (String url) {
-    //
-    //     },
-    //     onNavigationRequest: (NavigationRequest request) {
-    //       return NavigationDecision.navigate;
-    //     },
-    //   ),
-    // );
+  void _shareMindMap(String title, String? summary) {
+    if (_temporaryController.value != null) {
+      _temporaryController.value?.dispose();
+      _temporaryController.value = null;
+    }
+    final temporaryController = MindUtil.createMindWebViewController();
+    try {
+      _temporaryController.value = temporaryController;
+      temporaryController.loadFlutterAsset(Assets.html.indexExport);
+      temporaryController.setNavigationDelegate(
+        NavigationDelegate(
+          onHttpError: (error) {
+            temporaryController.dispose();
+          },
+          onWebResourceError: (error) {
+            temporaryController.dispose();
+          },
+          onPageFinished: (String url) {
+            temporaryController.callHandler(MindUtil.functionToJsExport,
+                args: [title, summary], handler: (value) async {
+              if (value == null) {
+                throw Exception('data is null');
+              }
+              await MindUtil.saveToFile(value, title);
+              temporaryController.dispose();
+              ToastUtil.showToast('下载成功');
+            });
+          },
+        ),
+      );
+    } catch (e) {
+      temporaryController.dispose();
+      ToastUtil.showToast('思维导图导出失败');
+    }
   }
   }
 
 
   void _shareSummaryOrOriginal(String id, String fileName, ShareTalkType type,
   void _shareSummaryOrOriginal(String id, String fileName, ShareTalkType type,
@@ -713,6 +724,7 @@ class TalkController extends BaseController {
         } else {
         } else {
           talkBean.value?.updateBean(bean);
           talkBean.value?.updateBean(bean);
         }
         }
+        maxTemplateCount = data.maxTemplateCount;
         templateSelectId.value = bean.templateId;
         templateSelectId.value = bean.templateId;
       }
       }
       templateList.value = data.templateList;
       templateList.value = data.templateList;

+ 1 - 37
lib/module/talk/mindmap/controller.dart

@@ -1,10 +1,5 @@
 import 'dart:async';
 import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
-import 'dart:typed_data';
-import 'dart:ui';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:path_provider/path_provider.dart';
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/module/talk/mindmap/mind_util.dart';
 import 'package:electronic_assistant/module/talk/mindmap/mind_util.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
 import 'package:electronic_assistant/resource/assets.gen.dart';
@@ -35,10 +30,6 @@ class MindMapController extends BaseController {
   @override
   @override
   void onReady() async {
   void onReady() async {
     super.onReady();
     super.onReady();
-    // await talkController.webViewController
-    //     .loadRequest(Uri.parse('http://192.168.10.144:9528/'));
-    // await talkController.webViewController.loadFlutterAsset(Assets.images.test);
-    // talkController.webViewController.setBackgroundColor(Colors.blue);
     talkController.webViewController.loadFlutterAsset(Assets.html.index);
     talkController.webViewController.loadFlutterAsset(Assets.html.index);
     talkController.webViewController.setNavigationDelegate(
     talkController.webViewController.setNavigationDelegate(
       NavigationDelegate(
       NavigationDelegate(
@@ -56,39 +47,12 @@ class MindMapController extends BaseController {
     });
     });
   }
   }
 
 
-  // // Capture WebView content as a PNG image
-  // Future<void> _captureAndSaveScreenshot() async {
-  //   try {
-  //     // Render the content inside RepaintBoundary to a picture
-  //     RenderRepaintBoundary boundary = repaintKey.currentContext!
-  //         .findRenderObject() as RenderRepaintBoundary;
-  //     var image = await boundary.toImage(
-  //         pixelRatio: 3); // Higher pixel ratio for better quality
-  //
-  //     // Convert the image to byte data
-  //     ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
-  //     Uint8List uint8List = byteData!.buffer.asUint8List();
-  //
-  //     // Get the path to save the image
-  //     final directory = await getApplicationDocumentsDirectory();
-  //     final path = '${directory.path}/webview_screenshot.png';
-  //
-  //     // Save the file
-  //     final file = File(path);
-  //     await file.writeAsBytes(uint8List);
-  //
-  //     print('Screenshot saved at $path');
-  //   } catch (e) {
-  //     print("Error taking screenshot: $e");
-  //   }
-  // }
-
   void _dealTalkSummaryUpdate(String? summary) {
   void _dealTalkSummaryUpdate(String? summary) {
     debugPrint('mindMap summary: $summary');
     debugPrint('mindMap summary: $summary');
     talkController.webViewController
     talkController.webViewController
         .callHandler(MindUtil.functionToJsUpdateMind, args: [summary],
         .callHandler(MindUtil.functionToJsUpdateMind, args: [summary],
             handler: (value) {
             handler: (value) {
-      debugPrint('mindMap summary: $value');
+      debugPrint('mindMap update success: $value');
     });
     });
   }
   }
 
 

+ 24 - 23
lib/module/talk/mindmap/mind_util.dart

@@ -1,7 +1,9 @@
+import 'dart:convert';
 import 'dart:io';
 import 'dart:io';
 import 'dart:typed_data';
 import 'dart:typed_data';
 import 'dart:ui';
 import 'dart:ui';
 import 'package:dsbridge_flutter/dsbridge_flutter.dart';
 import 'package:dsbridge_flutter/dsbridge_flutter.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter_svg/svg.dart';
 import 'package:flutter_svg/svg.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path_provider/path_provider.dart';
@@ -28,19 +30,23 @@ class MindUtil {
     DWebViewController webViewController =
     DWebViewController webViewController =
         DWebViewController.fromPlatformCreationParams(params);
         DWebViewController.fromPlatformCreationParams(params);
     webViewController.setJavaScriptMode(JavaScriptMode.unrestricted);
     webViewController.setJavaScriptMode(JavaScriptMode.unrestricted);
+    webViewController.setBackgroundColor(ColorName.white);
     webViewController.enableZoom(false);
     webViewController.enableZoom(false);
     return webViewController;
     return webViewController;
   }
   }
 
 
   static Future<void> convertToFile(Uint8List bytes, String fileName) async {
   static Future<void> convertToFile(Uint8List bytes, String fileName) async {
     final directory = await getTemporaryDirectory();
     final directory = await getTemporaryDirectory();
-    final file = File('${directory.path}/$fileName');
+    final file = File('${directory.path}/mind/$fileName');
+    if (!file.existsSync()) {
+      file.createSync(recursive: true);
+    }
     await file.writeAsBytes(bytes);
     await file.writeAsBytes(bytes);
   }
   }
 
 
   static Future<void> convertMapDataToToFile(
   static Future<void> convertMapDataToToFile(
       dynamic data, String fileName) async {
       dynamic data, String fileName) async {
-    Uint8List? bytes = convertMapToUint8List(data);
+    Uint8List? bytes = convertToUint8List(data);
     if (bytes == null) {
     if (bytes == null) {
       return;
       return;
     }
     }
@@ -49,31 +55,18 @@ class MindUtil {
     await file.writeAsBytes(bytes);
     await file.writeAsBytes(bytes);
   }
   }
 
 
-  static Uint8List? convertMapToUint8List(dynamic map) {
-    if (map is Map) {
-      final value = map.values.toList().cast<int>();
-      return Uint8List.fromList(value);
+  static Uint8List? convertToUint8List(dynamic value) {
+    if (value is Map) {
+      final data = value.values.toList().cast<int>();
+      return Uint8List.fromList(data);
     }
     }
-    return null;
-  }
-
-  static Future<Uint8List> svgToPng(
-      String svgString, BuildContext context) async {
-    final pictureInfo =
-        await vg.loadPicture(SvgStringLoader(svgString), context);
-
-    final image = await pictureInfo.picture.toImage(100, 100);
-    final byteData = await image.toByteData(format: ImageByteFormat.png);
-
-    if (byteData == null) {
-      throw Exception('Unable to convert SVG to PNG');
+    if (value is String) {
+      return base64Decode(value.split(',').last);
     }
     }
-
-    final pngBytes = byteData.buffer.asUint8List();
-    return pngBytes;
+    return null;
   }
   }
 
 
-  static Future<Uint8List?> svgToPng2(String svgString) async {
+  static Future<Uint8List?> svgToPng(String svgString) async {
 // first parse svgString to get width and height using xml plugin
 // first parse svgString to get width and height using xml plugin
     final document = xml.XmlDocument.parse(svgString);
     final document = xml.XmlDocument.parse(svgString);
     final svgElement = document.rootElement;
     final svgElement = document.rootElement;
@@ -95,4 +88,12 @@ class MindUtil {
 
 
     return bytes?.buffer.asUint8List();
     return bytes?.buffer.asUint8List();
   }
   }
+
+  static Future<void> saveToFile(dynamic value, String fileName) async {
+    Uint8List? bytes = convertToUint8List(value);
+    if (bytes == null) {
+      throw Exception('bytes is null');
+    }
+    return convertToFile(bytes, fileName);
+  }
 }
 }

+ 35 - 1
lib/module/talk/summary/controller.dart

@@ -2,8 +2,13 @@ import 'dart:async';
 
 
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/base/base_controller.dart';
 import 'package:electronic_assistant/data/bean/agenda.dart';
 import 'package:electronic_assistant/data/bean/agenda.dart';
+import 'package:electronic_assistant/dialog/alert_dialog.dart';
 import 'package:electronic_assistant/module/talk/controller.dart';
 import 'package:electronic_assistant/module/talk/controller.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:electronic_assistant/utils/toast_util.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:get/get.dart';
 import 'package:get/get.dart';
 import '../../../data/bean/agenda_list_all_bean.dart';
 import '../../../data/bean/agenda_list_all_bean.dart';
 import '../../../data/bean/talks.dart';
 import '../../../data/bean/talks.dart';
@@ -71,7 +76,36 @@ class SummaryController extends BaseController {
     talkController.showSingleAddAgendaDialog(context);
     talkController.showSingleAddAgendaDialog(context);
   }
   }
 
 
-  void addTemplateClick() {}
+  void addTemplateClick() {
+    //判断模板是否超出数量限制
+    TalkBean? talkBean = talkController.talkBean.value;
+    int? maxTemplateCount = talkController.maxTemplateCount;
+    if (talkBean == null || maxTemplateCount == null) {
+      return;
+    }
+    _showMaxTemplateDialog();
+    if ((talkController.templateList.value?.length ?? 0) >= maxTemplateCount) {
+      _showMaxTemplateDialog();
+      return;
+    }
+  }
+
+  void _showMaxTemplateDialog() {
+    EAAlertDialog.show(
+        contentWidget: Container(
+          padding: EdgeInsets.only(top: 20.w),
+          child: Text(
+            StringName.templateTooMuch.tr,
+            style: TextStyle(
+                fontSize: 15.sp,
+                color: ColorName.primaryTextColor,
+                fontWeight: FontWeight.bold),
+          ),
+        ),
+        cancelText: StringName.cancel.tr,
+        confirmText: StringName.templateToManage.tr,
+        confirmOnTap: () {});
+  }
 
 
   @override
   @override
   void onClose() {
   void onClose() {

+ 7 - 1
lib/module/talk/view.dart

@@ -1,3 +1,4 @@
+import 'package:dsbridge_flutter/dsbridge_flutter.dart';
 import 'package:electronic_assistant/base/base_page.dart';
 import 'package:electronic_assistant/base/base_page.dart';
 import 'package:electronic_assistant/module/talk/controller.dart';
 import 'package:electronic_assistant/module/talk/controller.dart';
 import 'package:electronic_assistant/module/talk/mindmap/view.dart';
 import 'package:electronic_assistant/module/talk/mindmap/view.dart';
@@ -85,8 +86,13 @@ class TalkPage extends BasePage<TalkController> {
         length: controller.tabBeans.length,
         length: controller.tabBeans.length,
         child: Stack(
         child: Stack(
           children: [
           children: [
+            Obx(() {
+              return controller.temporaryController != null
+                  ? DWebViewWidget(controller: controller.temporaryController!)
+                  : const SizedBox.shrink();
+            }),
             _buildTalkContentView(),
             _buildTalkContentView(),
-            buildBottomView(),
+            buildBottomView()
           ],
           ],
         ),
         ),
       ),
       ),

+ 9 - 0
lib/module/template/addtemplate/controller.dart

@@ -0,0 +1,9 @@
+import 'package:electronic_assistant/base/base_controller.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+
+class AddTemplateController extends BaseController {
+  void onBack() {
+    Get.back();
+  }
+}

+ 35 - 0
lib/module/template/addtemplate/view.dart

@@ -0,0 +1,35 @@
+import 'package:electronic_assistant/base/base_page.dart';
+import 'package:electronic_assistant/resource/colors.gen.dart';
+import 'package:electronic_assistant/resource/string.gen.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+
+import '../../../resource/assets.gen.dart';
+import 'controller.dart';
+
+class AddTemplatePage extends BasePage<AddTemplateController> {
+  const AddTemplatePage({super.key});
+
+  @override
+  Widget buildBody(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(
+          StringName.templateAddTitle.tr,
+          style: TextStyle(fontSize: 17.sp, color: ColorName.primaryTextColor),
+        ),
+        centerTitle: true,
+        leading: IconButton(
+            onPressed: () {
+              controller.onBack();
+            },
+            icon: SizedBox(
+                width: 24.w,
+                height: 24.w,
+                child: Assets.images.iconBack.image())),
+      ),
+    );
+  }
+}

+ 6 - 1
lib/router/app_pages.dart

@@ -15,6 +15,8 @@ import 'package:electronic_assistant/module/splash/controller.dart';
 import 'package:electronic_assistant/module/store/controller.dart';
 import 'package:electronic_assistant/module/store/controller.dart';
 import 'package:electronic_assistant/module/store/view.dart';
 import 'package:electronic_assistant/module/store/view.dart';
 import 'package:electronic_assistant/module/talk/view.dart';
 import 'package:electronic_assistant/module/talk/view.dart';
+import 'package:electronic_assistant/module/template/addtemplate/controller.dart';
+import 'package:electronic_assistant/module/template/addtemplate/view.dart';
 import 'package:get/get.dart';
 import 'package:get/get.dart';
 
 
 import '../module/agenda/controller.dart';
 import '../module/agenda/controller.dart';
@@ -73,6 +75,8 @@ abstract class RoutePath {
   static const complaintOpinion = '/complaintOpinion';
   static const complaintOpinion = '/complaintOpinion';
 
 
   static const modelExplain = '/modelExplain';
   static const modelExplain = '/modelExplain';
+
+  static const addTemplate = '/addTemplate';
 }
 }
 
 
 class AppBinding extends Bindings {
 class AppBinding extends Bindings {
@@ -96,7 +100,7 @@ class AppBinding extends Bindings {
     lazyPut(() => AudioPickerController());
     lazyPut(() => AudioPickerController());
     lazyPut(() => ComplaintOpinionController());
     lazyPut(() => ComplaintOpinionController());
     lazyPut(() => ModelExplainController());
     lazyPut(() => ModelExplainController());
-    lazyPut(() => MindViewController());
+    lazyPut(() => AddTemplateController());
   }
   }
 
 
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
   void lazyPut<S>(InstanceBuilderCallback<S> builder) {
@@ -126,4 +130,5 @@ final generalPages = [
       name: RoutePath.complaintOpinion,
       name: RoutePath.complaintOpinion,
       page: () => const ComplaintOpinionPage()),
       page: () => const ComplaintOpinionPage()),
   GetPage(name: RoutePath.modelExplain, page: () => const ModelExplainPage()),
   GetPage(name: RoutePath.modelExplain, page: () => const ModelExplainPage()),
+  GetPage(name: RoutePath.addTemplate, page: () => const AddTemplatePage()),
 ];
 ];