Browse Source

feat: preview and export

Carson 2 months ago
parent
commit
1332d10e66

+ 7 - 7
app/run-agent-flow/components/NodeRender.tsx

@@ -1,6 +1,6 @@
 'use client';
 
-import React, { useEffect, useMemo } from 'react'
+import React, { useEffect, useMemo, useState } from 'react'
 import Agent from './NodeType/Agent';
 import Form from './NodeType/Form';
 import Unsupport from './NodeType/Unsupport';
@@ -8,6 +8,7 @@ import * as R from 'ramda'
 import { curNodeAtom, curStepAtom, arrowStateAtom, cardInstantAtomFamily, cardInstantAtomsAtom, instantDataAtom, stepsNodesAtom } from '../store';
 import { useAtom, useAtomValue } from 'jotai';
 import { exportFlowToDocx } from '../export';
+import PreviewModal from './PreviewModal';
 
 const NodeRender = () => {
   const [curStep, setCurStep] = useAtom(curStepAtom)
@@ -45,12 +46,10 @@ const NodeRender = () => {
     setCurStep(prev => prev - 1)
   }
 
-  const instantData = useAtomValue(instantDataAtom)
-  const stepsNodes = useAtomValue(stepsNodesAtom)
+  const [isPreviewModalOpen, setPreviewModalOpen] = useState(false)
 
-  const onExport = () => {
-    const stepsInstantData = stepsNodes.map(node => instantData[node.id])
-    exportFlowToDocx(stepsInstantData)
+  const onPreview = () => {
+    setPreviewModalOpen(true)
   }
 
   return (
@@ -65,13 +64,14 @@ const NodeRender = () => {
           }
           {arrowState.next
             ? <button className='btn btn-sm' onClick={onNextStep}>下一步</button>
-            : <button className='btn btn-sm btn-neutral' onClick={onExport}>导出</button>
+            : <button className='btn btn-sm btn-neutral' onClick={onPreview}>预览</button>
           }
         </div>
         <div className="flex-1 flex flex-col items-stretch overflow-auto">
           <Comp key={node?.id} node={node} cardInstantAtom={cardInstantAtom}></Comp>
         </div>
       </div>
+      <PreviewModal open={isPreviewModalOpen} onClose={() => setPreviewModalOpen(false)} />
     </div>
   )
 }

+ 52 - 0
app/run-agent-flow/components/PreviewModal.tsx

@@ -0,0 +1,52 @@
+'use client'
+
+import { useAtomValue } from "jotai"
+import { exportFlowToDocx } from "../export"
+import { instantDataAtom, stepsNodesAtom } from "../store"
+import { twMerge } from "tailwind-merge"
+import { renderAsync } from 'docx-preview'
+import { useEffect, useRef, useState } from "react"
+import saveAs from "file-saver"
+
+export default function PreviewModal({ open, onClose }: { open: boolean, onClose: () => void }) {
+  const instantData = useAtomValue(instantDataAtom)
+  const stepsNodes = useAtomValue(stepsNodesAtom)
+  const [blob, setBlob] = useState(null)
+  const previewRef = useRef()
+
+
+  useEffect(() => {
+    if (open) {
+      (async () => {
+        const stepsInstantData = stepsNodes.map(node => instantData[node.id])
+        const blob = await exportFlowToDocx(stepsInstantData)
+        setBlob(blob)
+        await renderAsync(blob, previewRef.current)
+      })()
+    } else {
+      setBlob(null)
+    }
+  }, [open])
+
+  const onExport = async () => {
+    blob && saveAs(blob, "课程设计.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" })
+  }
+
+  return (
+    <dialog className={twMerge("modal", open ? "modal-open" : "")}>
+      <div className="modal-box w-11/12 max-w-5xl pb-5">
+        <div ref={previewRef}></div>
+      </div>
+      <div className="modal-action mb-6">
+        <form method="dialog" className="flex gap-4">
+          <button className="btn btn-wide" onClick={onClose}>关闭</button>
+          <button className="btn btn-wide btn-primary" disabled={!blob} onClick={onExport}>
+            {blob
+              ? '导出'
+              : <span className="loading loading-spinner"></span>}
+          </button>
+        </form>
+      </div>
+    </dialog>
+  )
+}

+ 7 - 7
app/run-agent-flow/export.ts

@@ -162,7 +162,7 @@ const buildRows = (nodes) => {
   return _.flatten(nodes.map(sectionBuilder));
 };
 
-export const exportFlowToDocx = (nodes) => {
+export const exportFlowToDocx = async (nodes) => {
   const children: unknown[] = [
     new Paragraph({
       text: "课程设计",
@@ -188,10 +188,10 @@ export const exportFlowToDocx = (nodes) => {
   });
 
   // 将文档打包成Buffer并保存为文件
-  Packer.toBlob(doc).then((blob) => {
-    const mimeType =
-      "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
-    saveAs(blob, "课程设计.docx", { type: mimeType });
-  });
-  return;
+  return await Packer.toBlob(doc)
+//  (blob) => {
+//     const mimeType =
+//       "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+//     saveAs(blob, "课程设计.docx", { type: mimeType });
+//   }
 };

+ 9 - 0
package-lock.json

@@ -19,6 +19,7 @@
         "@trpc/server": "^11.0.0-rc.482",
         "@types/file-saver": "^2.0.7",
         "docx": "^8.5.0",
+        "docx-preview": "^0.3.2",
         "file-saver": "^2.0.5",
         "jotai": "^2.9.3",
         "jotai-devtools": "^0.10.1",
@@ -2343,6 +2344,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/docx-preview": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.2.tgz",
+      "integrity": "sha512-YRsyiiejdauCQ2boKNHKjJMiIhOCXs643+NCHnmbCM31e7JWqmPiobtzlmHOnv4i+ft9w+ajPEK1hK7VymyRXQ==",
+      "dependencies": {
+        "jszip": ">=3.0.0"
+      }
+    },
     "node_modules/docx/node_modules/nanoid": {
       "version": "5.0.7",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",

+ 1 - 0
package.json

@@ -20,6 +20,7 @@
     "@trpc/server": "^11.0.0-rc.482",
     "@types/file-saver": "^2.0.7",
     "docx": "^8.5.0",
+    "docx-preview": "^0.3.2",
     "file-saver": "^2.0.5",
     "jotai": "^2.9.3",
     "jotai-devtools": "^0.10.1",