unity 的webgl生成.docx文件

webgl下生成.docx文件,.jslib的代码如下

javascript 复制代码
var callbackMap = {};
var blobData= null;      // Uint8Array 类型的二进制数据
var blobTotalLength=0;   // Blob 总字节数
var blobReadOffset= 0;    // 当前读取偏移量
var blobDic=null;
// 完全兼容ES5的DocxGenerator.jslib
mergeInto(LibraryManager.library, {

    // 全局变量:存储待传输的 Blob 对应的 ArrayBuffer 和长度
    UnityinstanceId: 0,
    // 全局变量:存储 Blob 解析后的二进制数据和元信息

    // 存储回调映射:{ instanceId: 回调指针 }
    // ======================
    // 3. 核心:触发 C# 回调的通用函数
    // ======================
    //委托签名匹配:
    //jslib 中 Runtime.dynCall 的签名字符串需与 C# 委托一致:
    //'visi' → v=void 返回,i=int,s=string,i=int;
    //更多签名规则:i=int,f=float,d=double,p= 指针。
    TriggerCSharpCallback: function (instanceId, code) {
        // 从映射中获取回调指针
        const callbackPtr = Module.callbackMap[instanceId];
        if (!callbackPtr) {
            console.error('未找到实例的回调指针', instanceId);
            return;
javascript 复制代码
<!DOCTYPE html>
<html lang="en-us">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Unity WebGL Player | JLIT_IntelligentCIMS</title>
  <link rel="shortcut icon" href="TemplateData/favicon.ico">
  <link rel="stylesheet" href="TemplateData/style.css">
  <script src="TemplateData/UnityProgress.js"></script>
  <script src="Build/UnityLoader.js"></script>
  <script src="TemplateData/jszip.min.js"></script>
  <script>
    var unityInstance = UnityLoader.instantiate("unityContainer", "Build/JLIT_IntelligentCIMS.json", { onProgress: UnityProgress });
  </script>
</head>

<body>
  <div class="webgl-content" style="width: 100%; height: 100%;overflow-y: hidden;">
    <div id="unityContainer" style="width: 100%; height: 100%"></div>
  </div>
  <script>


    let webglMsg = {
      ticket: "",
      appid: "",
      accessToken: ""
    }
    let needSendMsg = "";
    window.onload = function () {
      // 假设当前URL为:http://xxx.com/xxx.html?ticket=xxx123&appid=app456&access_token=token789
      const urlParams = new URLSearchParams(window.location.search);
      // 获取指定参数值
      const ticket = urlParams.get('ticket'); // 结果:xxx123
      const appid = urlParams.get('appId'); // 结果:app456
      const accessToken = urlParams.get('access_token'); // 结果:token789
      webglMsg.ticket = ticket;
      webglMsg.appid = appid;
      webglMsg.accessToken = accessToken;
      needSendMsg = JSON.stringify(webglMsg);
      console.log(needSendMsg);

      //setTimeout(() => {
        // 格式:unityInstance.SendMessage("GameObject名称", "方法名", 参数)
      //  window.unityInstance.SendMessage("ReceiveWebglMsg", "OnRecWebMSG", needSendMsg);
      //}, 16000); // 3000 毫秒 = 3 秒
    };


    function ReceiveUnityMessage(msg) {
      console.log(msg);
      window.unityInstance.SendMessage("ReceiveWebglMsg", "OnRecWebMSG", needSendMsg);
    }
    // 全局变量存储配置数据
    let appConfig = null;

    // 加载配置文件的函数
    async function loadConfig() {
      try {
        // 发起请求加载 config.json(同目录下)
        const response = await fetch('config.json');
        if (!response.ok) {
          throw new Error(`加载失败,状态码:${response.status}`);
        }
        // 解析为JSON对象(如果是纯文本则用 response.text())
        appConfig = await response.json();
        console.log('配置文件加载成功:', appConfig);

        // 配置加载完成后执行初始化操作(如初始化页面、传递给Unity等)
        initApp();
      } catch (error) {
        console.error('配置文件加载失败:', error);
        // 加载失败时使用默认配置
        appConfig = { defaultUrl: 'https://example.com', debug: false };
        initApp(); // 即使失败也继续初始化
      }
    }
    function GetConfigJs() {

      return JSON.stringify(appConfig);
    }
    // 配置加载后的初始化函数
    function initApp() {
      console.log('开始初始化应用,使用配置:', appConfig);
      // 这里可以写依赖配置的逻辑,例如:
      // - 设置页面参数
      // - 传递配置给Unity WebGL(如 unityInstance.SendMessage(...))
      // - 初始化其他组件
    }

    // 页面DOM加载完成后立即加载配置文件
    document.addEventListener('DOMContentLoaded', loadConfig);
  </script>
</body>

</html>

js通信

javascript 复制代码
mergeInto(LibraryManager.library, {

    SendMessageToWeb: function (str) {
        ReceiveUnityMessage(Pointer_stringify(str));
    },

    Alert2Web: function (str) {
        window.alert(Pointer_stringify(str));
    },

    Log2Web: function (str) {
        console.log(Pointer_stringify(str));
    },

    GetConfig: function () {
        var returnStr = GetConfigJs();
        var bufferSize = lengthBytesUTF8(returnStr) + 1;
        var buffer = _malloc(bufferSize);
        stringToUTF8(returnStr, buffer, bufferSize);
        return buffer;
    }
});

c# 端,调用js生成.docx,并添加对应的生成回调函数

csharp 复制代码
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using System.Text;
using System.IO;
using System.Collections.Generic;
using AOT;
using System.Runtime.CompilerServices;
using System.Collections;
using UMP.Services.Helpers;

// 文档段落数据(支持自定义字号)
[Serializable]
public class DocxParagraph
{
    public string text;
    public int fontSize = 24; // 磅值
}

// 表格单元格数据
[Serializable]
public class DocxTableCell
{
    public string text;
    public string align = "center"; // left/center/right
}

// 表格行数据
[Serializable]
public class DocxTableRow
{
    public DocxTableCell[] cells;
}

// 表格数据
[Serializable]
public class DocxTable
{
    public DocxTableRow[] rows;
}

//3. 图片尺寸换算
//OpenXML 中图片尺寸单位为 EMU(English Metric Unit),换算公式:
//1 厘米 = 914400 EMU;
//1 英寸 = 914400 × 2.54 = 2359656 EMU;
//示例:4cm × 3cm → cx="3657600" cy="2743200"。
// 图片数据(Base64编码)
[Serializable]
public class DocxImage
{
    public string base64;
    public long cx = 3657600; // 宽度(EMU)
    public long cy = 2743200; // 高度(EMU)

    public void SetPixCX(int px, int py) 
    {
        cx = (long)((int)(px * 2.54 / 96*914400));
        cy = (long)((int)(py * 2.54 / 96 * 914400));
    }

}

// 完整DOCX配置数据
[Serializable]
public class DocxData
{
    public string title = "WebGL 复杂格式报告";
    public string titleColor = "FF0000"; // 红色
    public DocxParagraph[] paragraphs;
    public DocxTable table;
    public DocxImage[] images;
    public string fileName = "WebGL复杂格式报告.docx";
}

public class RichDocxGenerator : MonoBehaviour
{
    // 导入JS方法
    [DllImport("__Internal")]
    private static extern void GenerateRichDocx(string docxDataJson);
    [DllImport("__Internal")]
    private static extern void RegisterDocxCallback(IntPtr callback);

    [DllImport("__Internal")]
    private static extern void TriggerDocxGenerateResult(IntPtr callback);

#if UNITY_WEBGL && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void InitCallbackMap();

    [DllImport("__Internal")]
    private static extern void RegisterCallback(int instanceId, IntPtr callbackPtr);

    [DllImport("__Internal")]
    private static extern void GenerateBlobAndTriggerCallback(int instanceId);

    [DllImport("__Internal")]
    private static extern void UnregisterCallback(int instanceId);
#endif

    private Action<int,int> _docxCallback;
    // 2. 静态字典:管理「实例ID → 实例对象」映射(解决静态方法访问实例成员)
    private static Dictionary<int, RichDocxGenerator> _instanceMap = new Dictionary<int, RichDocxGenerator>();

    // 实例唯一标识(用 GetInstanceID() 自动生成)
    private int _instanceId;
    private static RichDocxGenerator _instance; // 单例
    public delegate void DocxGenerateResultDelegate(int result, int code);



    private void OnDocxGenerateResult(string result, int code)
    {
        // 业务逻辑
    }
    private void Awake()
    {
        _instance = this; // 单例赋值

        _instanceId = GetInstanceID(); // 获取实例唯一ID
        // 将当前实例注册到全局字典
        _instanceMap[_instanceId] = this;
    }
    void OnDestroy()
    {
        if (_instanceMap.ContainsKey(_instanceId))
        {
            _instanceMap.Remove(_instanceId);
        }
#if UNITY_WEBGL && !UNITY_EDITOR
        UnregisterCallback(_instanceId);
#endif
    }
    void Start()
    {

#if UNITY_WEBGL && !UNITY_EDITOR
        InitCallbackMap();
        // 关键:封送「静态方法」的委托(而非实例方法)
        DocxGenerateResultDelegate staticDelegate = StaticOnDocxGenerateResult;
        // 获取静态方法的函数指针(IL2CPP 允许)
        IntPtr callbackPtr = Marshal.GetFunctionPointerForDelegate(staticDelegate);
        // 将实例ID和回调指针传入jslib(jslib通过instanceId区分不同实例)
        RegisterCallback(_instanceId, callbackPtr);
#endif
    }
    [MonoPInvokeCallback(typeof(DocxGenerateResultDelegate))] // 必须指定委托类型
    [MethodImpl(MethodImplOptions.NoInlining)] // 防止内联
    private static void StaticOnDocxGenerateResult(int result, int code)
    {
        Debug.Log("StaticOnDocxGenerateResult");
        // 拷贝字符串到托管内存
        //string safeResult = string.Copy(result);
        // 后续使用 safeResult 而非原始 result
        _instance?.OnDocxGenerateResult(result, code);
    }
    // DOCX生成结果回调
    private void OnDocxGenerateResult(int result, int code)
    {
        Debug.Log("OnDocxGenerateResult");
        if (_docxCallback != null)
        {
            _docxCallback.Invoke(result,code);
            _docxCallback = null;
        }
    }

    /// <summary>
    /// 生成带复杂格式的DOCX文件
    /// </summary>
    /// <param name="docxData">文档配置数据</param>
    /// <param name="callback">生成结果回调</param>
    public void GenerateRichDocxFile(DocxData docxData, Action<int,int> callback = null)
    {
        _docxCallback = callback;

#if UNITY_WEBGL && !UNITY_EDITOR
        // 将数据转为JSON传给JS
        string json = JsonUtility.ToJson(docxData);
        GenerateRichDocx(json);
#else
        // 编辑器测试:打印JSON
        Debug.Log("编辑器模式,DOCX配置JSON:\n" + JsonUtility.ToJson(docxData, true));
        callback?.Invoke(1,1);
#endif
    }

    /// <summary>
    /// 将Unity图片转为Base64字符串(用于插入DOCX)
    /// </summary>
    public string ImageToBase64(Texture2D texture)
    {
        if (texture == null) return "";
        byte[] pngBytes = texture.EncodeToPNG();
        return Convert.ToBase64String(pngBytes);
    }
    public void GenerateRichDocxUnity(string fileName, DocxParagraph[] paras, DocxTable tb,Action changeColorA=null)
    {
        // 1. 构建文档数据
        DocxData docxData = new DocxData
        {
            title = "实验报告",
            titleColor = "0066CC", // 蓝色
            fileName = fileName,
            // 正文段落
            paragraphs = paras,
            table = tb

        };
        // 2. 生成DOCX
        GenerateRichDocxFile(docxData, (instanceId,success) =>
        {
            if (success==1)
            {
                StartCoroutine(DelayRead(changeColorA));
                Debug.Log("复杂格式DOCX生成并下载成功!");
            }
            else
            {
                changeColorA?.Invoke();
                Debug.LogError("复杂格式DOCX生成失败!");
            }
        });
    }
    IEnumerator DelayRead(Action aa) 
    {
        yield return new WaitForEndOfFrame();
        BlobReceiver br = GameObject.FindObjectOfType<BlobReceiver>();
        br.ReadBlobFromJS(aa);
    }
    // 测试:生成带标题、段落、表格、图片的复杂DOCX
    [ContextMenu("测试生成复杂DOCX")]
    public void TestGenerateRichDocxUnity(DocxImage base64Tex)
    {
        Debug.Log("图片Base64长度:" + base64Tex.base64.Length); // 正常小图>1000,大图>10000
        // 1. 构建文档数据
        DocxData docxData = new DocxData
        {
            title = "Unity WebGL 项目报告",
            titleColor = "0066CC", // 蓝色
            fileName = "项目交付报告.docx",
            // 正文段落
            paragraphs = new[]
            {
                new DocxParagraph { text = "1. 项目概述:本项目基于Unity WebGL开发,实现了浏览器端DOCX文件生成功能,支持复杂格式定制。", fontSize = 24 },
                new DocxParagraph { text = "2. 功能亮点:支持字体样式、表格合并、图片插入等进阶功能,无后端依赖,纯前端生成。", fontSize = 24 }
            },
            // 表格(含合并单元格)
            //table = new DocxTable
            //{
            //    rows = new[]
            //    {
            //        // 第一行(表头,第一列跨2列)
            //        new DocxTableRow
            //        {
            //            cells = new[]
            //            {
            //                new DocxTableCell { text = "项目信息汇总", align = "center" },
            //                new DocxTableCell { text = "" } // 占位(被合并)
            //            }
            //        },
            //        // 第二行
            //        new DocxTableRow
            //        {
            //            cells = new[]
            //            {
            //                new DocxTableCell { text = "开发引擎", align = "left" },
            //                new DocxTableCell { text = "Unity 2022.3 LTS", align = "left" }
            //            }
            //        },
            //        // 第三行
            //        new DocxTableRow
            //        {
            //            cells = new[]
            //            {
            //                new DocxTableCell { text = "目标平台", align = "left" },
            //                new DocxTableCell { text = "WebGL", align = "left" }
            //            }
            //        }
            //    }
            //},
            // 图片(可选:插入Unity截图)
            //images = new[]
            //{
            //    new DocxImage { base64 = base64Tex.base64.Replace("\r", "").Replace("\n", ""),cx=base64Tex.cx,cy=base64Tex.cy},
            //    //new DocxImage { base64 = base64Tex .Replace("\r", "").Replace("\n", "")},
            //    //new DocxImage { base64 = base64Tex .Replace("\r", "").Replace("\n", "").Replace(" ", "") }
            //}
        };

        // 2. 生成DOCX
        //GenerateRichDocxFile(docxData, (instanceId,success) =>
        //{
        //    if (success==1)
        //        Debug.Log("复杂格式DOCX生成并下载成功!");
        //    else
        //        Debug.LogError("复杂格式DOCX生成失败!");
        //});
    }

    /// <summary>
    /// 捕获屏幕截图并转为Texture2D
    /// </summary>
    private Texture2D CaptureScreen()
    {
        Rect rect = new Rect(0, 0, Screen.width, Screen.height);
        Texture2D texture = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24, false);
        texture.ReadPixels(rect, 0, 0);
        texture.Apply();
        return texture;
    }
}

C#端读取

csharp 复制代码
using UnityEngine;
using System;
using System.Runtime.InteropServices;
using System.Text;
using IntelligentCIMS;

public class BlobReceiver : MonoBehaviour
{
    // 仅在 WebGL 平台调用 jslib
#if UNITY_WEBGL && !UNITY_EDITOR

    [DllImport("__Internal")]
    private static extern int GetBlobTotalLength();

    [DllImport("__Internal")]
    private static extern int ReadBlobChunk(int chunkSize, IntPtr outBuffer);

    [DllImport("__Internal")]
    private static extern void ReleaseBlob();
#endif

    // 分段读取的块大小(建议 40KB~100KB,平衡性能和内存)
    private const int CHUNK_SIZE = 4096 * 10; // 40KB

    public Action upLoadSuccess;
    // 测试按钮:触发 Blob 读取(可绑定到 UI 按钮)
    public void ReadBlobFromJS(Action successA)
    {
        upLoadSuccess = successA;
#if UNITY_WEBGL && !UNITY_EDITOR
        StartCoroutine(ReadBlobCoroutine());
#else
        Debug.LogWarning("仅 WebGL 平台支持 jslib 调用");
        upLoadSuccess?.Invoke();
#endif
    }
    [SerializeField]
    private int totalLength = 0;
    byte[] blobBytes;
    // 协程读取:避免主线程阻塞(大文件必备)
    private System.Collections.IEnumerator ReadBlobCoroutine()
    {
        yield return new WaitForEndOfFrame();
#if UNITY_WEBGL && !UNITY_EDITOR
        // 步骤1:等待 Blob 初始化(前端需先调用 Unity_SetBlob 传入 Blob
        int waitCount = 0;
        while (true)
        {
            totalLength = GetBlobTotalLength();
            if (totalLength > 0) break;          // 初始化成功
            if (totalLength == -1 || waitCount > 100) // 失败/超时(10秒)
            {
                Debug.LogError("Blob 初始化失败或超时");
                yield break;
            }
            waitCount++;
            yield return new WaitForSeconds(0.1f); // 每0.1秒检查一次
        }
        Debug.Log($"Blob 总长度:{totalLength} 字节");

        // 步骤2:初始化缓冲区,存储完整 Blob 数据
        blobBytes = new byte[totalLength];
        int totalRead = 0;
#region 
        // 步骤3:分段读取 Blob 数据
        while (totalRead < totalLength)
        {
            // 分配临时内存缓冲区(存储当前分段)
            IntPtr chunkBuffer = Marshal.AllocHGlobal(CHUNK_SIZE);
            if (chunkBuffer == IntPtr.Zero)
            {
                Debug.LogError("分配内存缓冲区失败");
                ReleaseBlob();
                yield break;
            }

            // 调用 jslib 读取分段数据
            int readLength = ReadBlobChunk(CHUNK_SIZE, chunkBuffer);
            if (readLength < 0)
            {
                Debug.LogError("读取 Blob 分段失败");
                Marshal.FreeHGlobal(chunkBuffer);
                ReleaseBlob();
                yield break;
            }
            if (readLength == 0) break; // 读取完毕

            // 将分段数据拷贝到完整缓冲区
            Marshal.Copy(chunkBuffer, blobBytes, totalRead, readLength);
            Marshal.FreeHGlobal(chunkBuffer); // 释放临时缓冲区

            // 更新进度
            totalRead += readLength;
            float progress = (float)totalRead / totalLength * 100f;
            Debug.Log($"读取进度:{progress:F2}%({totalRead}/{totalLength} 字节)");

            // 让出主线程(避免卡顿)
            yield return null;
        }
#endregion
        // 步骤4:验证并处理完整 Blob 数据
        if (totalRead == totalLength)
        {
            Debug.Log("Blob 读取完成!");
            
            // 示例1:转为字符串(文本类 Blob)
            //string textContent = Encoding.UTF8.GetString(blobBytes);
            //Debug.Log("Blob 文本内容:" + textContent);

            // 示例2:保存为文件(WebGL 仅能写入 PersistentDataPath)
            //string savePath = System.IO.Path.Combine(Application.persistentDataPath, "received_blob.bin");
            //System.IO.File.WriteAllBytes(savePath, blobBytes);
            //Debug.Log("Blob 已保存到:" + savePath);

            // 示例3:直接使用二进制数据(如解析图片/DOCX)
            // ParseImageFromBytes(blobBytes);
        }
        else
        {
            Debug.LogError($"Blob 读取不完整!已读 {totalRead} 字节,预期 {totalLength} 字节");
        }

        // 步骤5:释放 Blob 资源
        ReleaseBlob();
#endif


#if UNITY_WEBGL
        Run.LoglCtr.doc_Bytes = blobBytes;
        yield return new WaitForEndOfFrame();
        GameObject.FindObjectOfType<FormDataPostManager>().
            SubmitExperimentReportV(blobBytes, UrlEncodeTool.UrlEncode(FormDataPostManager.DocFileName));
#endif 
        upLoadSuccess?.Invoke();
    }

}
相关推荐
AT~2 小时前
Monotable 一个轻量Unity插件;按规则自动收集Gameobject下MonoScripts
unity·游戏引擎
nnsix2 小时前
Unity 使用SQLite
unity
小蜗 strong2 小时前
unity中利用MRTK添加全息面板并部署到HoloLens 2中
unity·游戏引擎·hololens
雪下的新火2 小时前
AI工具-腾讯混元3D使用简述:
人工智能·游戏引擎·aigc·blender·ai工具·笔记分享
lebornjose17 小时前
javascript - webgl中绑定(bind)缓冲区的逻辑是什么?
前端·webgl
龚子亦1 天前
【Unity开发】热更新学习——HybridCLR框架
学习·unity·游戏引擎·热更新
IMPYLH1 天前
Lua 的 OS(操作系统) 模块
开发语言·笔记·后端·游戏引擎·lua
龚子亦1 天前
【GameFramework框架】FSM使用
学习·unity·游戏开发·游戏框架
龚子亦1 天前
【Unity开发】安卓应用开发中,用户进行权限请求
android·unity·安卓权限