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();
    }

}
相关推荐
五仁烧饼5 小时前
Unity Addressables 静默资源策略
游戏·unity·addressables
ue星空11 小时前
【Godot】更换编辑器外观主题
编辑器·游戏引擎·godot
fanfan_hongyun13 小时前
unity 与cad 坐标系映射
unity·游戏引擎
supermapsupport14 小时前
SuperMap GIS 基础产品 FAQ 集锦(20260807)
webgl·supermap·idesktopx
supermapsupport15 小时前
SuperMap iClient3D for WebGL 如何开启实体对象深度检测
webgl
神码编程15 小时前
【Unity】 HTFramework框架(七十)MultiChoiceDropdown可多选的下拉菜单
unity·游戏引擎·ugui·dropdown·下拉菜单
IT小白杨16 小时前
2026Shopee多站点运营的浏览器隔离与移动端云手机方案
经验分享·社交电子·webgl·安全架构·指纹浏览器
ue星空16 小时前
Godot 2D像素游戏移动抖动、模糊问题解决方案
游戏·游戏引擎·godot
淡海水16 小时前
15-07-YooAsset面试篇-Unity性能优化与内存管理
java·unity·面试·性能优化·c#·游戏引擎
ue星空16 小时前
【Godot】AudioStreamPlayer2D音频播放
游戏引擎·音视频·godot