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