Files
Leantime/public/assets/js/app/onebot-sheet.js

252 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createUniver } from '@univerjs/presets';
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core';
import { UniverSheetsDrawingPreset } from '@univerjs/preset-sheets-drawing';
import { LocaleType, ImageSourceType } from '@univerjs/core';
import zhCN from '@univerjs/preset-sheets-core/locales/zh-CN';
// Univer 表格 UI 需要显式引入 CSSpreset 的 es 入口只 import 逻辑,不 import 样式)
import '@univerjs/design/lib/index.css';
import '@univerjs/ui/lib/index.css';
import '@univerjs/docs-ui/lib/index.css';
import '@univerjs/sheets-ui/lib/index.css';
import '@univerjs/sheets-formula-ui/lib/index.css';
import '@univerjs/sheets-numfmt-ui/lib/index.css';
// drawing浮动图片/贴图)相关 CSS
import '@univerjs/drawing-ui/lib/index.css';
import '@univerjs/sheets-drawing-ui/lib/index.css';
import '@univerjs/preset-sheets-drawing/lib/index.css';
const DEFAULT_COL_WIDTH = 110;
/**
* 根据列中文标签推断合理列宽,避免所有列一样宽。
*/
function guessColWidth(label) {
label = (label || '').toString();
if (label.includes('序号')) return 56;
if (label.includes('备注') || label.includes('描述')) return 180;
if (label.includes('名称') || label.includes('图号') || label.includes('材质')) return 150;
if (label.includes('规格') || label.includes('工序') || label.includes('编号')) return 120;
if (label.includes('用量') || label.includes('数量') || label.includes('单位')) return 80;
return DEFAULT_COL_WIDTH;
}
/**
* OneBot BOM 明细表 —— Univer 表格引擎入口。
* 走 ESM 入口icons 包的 cjs 构建 require 路径坏掉esm 构建正常)。
*
* 数据布局:第 0 行是表头,第 1..n 行是数据。
* 返回 { univer, univerAPI, sheet, workbook, endEditing, getActiveCell, insertImage,
* insertImageAt, fitToContainer },供页面做编辑/保存/附件/自适应等操作。
*/
function createOneBotSheet(opts) {
const {
container,
data, // 原始数据行数组(已含 _id 和所有列 key
columns, // 全部列 key 数组fixed + dynamic
fixedLabels, // { key: 中文标签 }
imageColumns, // 图片列 key 数组(可选)
fileColumns, // 附件列 key 数组(可选)
onCellChange, // (row, col) => void row 是 0-based 数据行索引,即 sheet 行号-1
onCellClick, // (row, col) => void 点击单元格row 是 0-based sheet 行号)
} = opts;
const imageColSet = new Set(imageColumns || []);
const fileColSet = new Set(fileColumns || []);
// 图片列宽一些,附件列次之
const colIndexByKey = {};
columns.forEach(function (key, c) { colIndexByKey[key] = c; });
const { univer, univerAPI } = createUniver({
locale: LocaleType.ZH_CN,
locales: { [LocaleType.ZH_CN]: zhCN },
presets: [
UniverSheetsCorePreset({
container,
}),
UniverSheetsDrawingPreset(),
],
});
// Univer 0.25 不会自动创建 workbook。传空对象 {} 让 Workbook 构造走 getEmptySnapshot() 分支
let workbook = univerAPI.getActiveWorkbook();
if (!workbook) {
workbook = univerAPI.createWorkbook({});
}
// 空 workbook 没有 sheet手动插入一个
let sheet = workbook.getActiveSheet();
if (!sheet) {
sheet = workbook.insertSheet('Sheet1');
}
// 用 setColumnCount/setRowCount 设置行列数;空表也预置若干空数据行,保证可编辑可保存
sheet.setColumnCount(columns.length + 1);
sheet.setRowCount(Math.max(data.length, 8) + 2);
// 表头(第 0 行)
columns.forEach(function (key, c) {
const label = fixedLabels[key] || key;
sheet.getRange(0, c).setValue(label);
});
// 列宽:按列类型分配,图片列更宽
columns.forEach(function (key, c) {
let w = guessColWidth(fixedLabels[key] || key);
if (imageColSet.has(key)) w = 170;
else if (fileColSet.has(key)) w = 140;
sheet.setColumnWidth(c, w);
});
// 末尾多余列保持默认窄列
sheet.setColumnWidth(columns.length, DEFAULT_COL_WIDTH);
// 数据行(第 1 行起)
data.forEach(function (rowData, r) {
columns.forEach(function (key, c) {
const v = rowData[key];
sheet.getRange(r + 1, c).setValue(v === undefined || v === null ? '' : v);
});
});
// 监听值变化(排除表头第 0 行)。包 try-catch 防止事件 API 异常中断整个初始化
try {
univerAPI.addEvent(univerAPI.Event.SheetValueChanged, function (params) {
if (params && params.effectedRanges) {
params.effectedRanges.forEach(function (rng) {
const row = rng.getRow();
const col = rng.getColumn();
if (row > 0 && typeof onCellChange === 'function') {
let val = null;
try { val = rng.getValue(); } catch (e) {}
// 传 (dataRowIndex, colIndex, newValue)
onCellChange(row - 1, col, val);
}
});
}
});
} catch (e) {
// 事件监听失败不致命,保存时走全量对比兜底
}
// 单元格点击事件(供附件 PDF 弹窗预览/下载等场景使用)
if (typeof onCellClick === 'function') {
try {
univerAPI.addEvent(univerAPI.Event.CellClicked, function (params) {
if (params && typeof params.row === 'number' && typeof params.column === 'number') {
onCellClick(params.row, params.column);
}
});
} catch (e) {}
}
// 读取图片原始尺寸URL 或 base64失败返回 null
function loadImageSize(source) {
return new Promise(function (resolve) {
var img = new Image();
img.onload = function () { resolve({ w: img.naturalWidth || 1, h: img.naturalHeight || 1 }); };
img.onerror = function () { resolve(null); };
img.src = source;
});
}
// 按 URL 源插入浮动图片到指定 cell 位置。尺寸自适应:宽度跟随列宽(默认 max(colW-8, 160)
// 高度按原图比例计算(读不到原图尺寸时按 4:3 兜底。opts 可覆盖 width/height。
async function insertImageAt(row, col, source, sourceType, offsetX, opts) {
opts = opts || {};
offsetX = offsetX || 0;
let colW = DEFAULT_COL_WIDTH;
try { colW = sheet.getColumnWidth(col) || DEFAULT_COL_WIDTH; } catch (e) {}
const width = opts.width || Math.min(Math.max(colW - 8, 60), 160);
let height = opts.height || 0;
if (!height) {
const sz = await loadImageSize(source);
height = sz ? Math.round(width * sz.h / sz.w) : Math.round(width * 0.75);
}
try {
sheet.setRowHeight(row, Math.max(sheet.getRowHeight(row), height + 6));
} catch (e) {}
const image = await sheet.newOverGridImage()
.setSource(source, sourceType || ImageSourceType.URL)
.setColumn(col)
.setRow(row)
.setColumnOffset(4 + offsetX)
.setRowOffset(4)
.setWidth(width)
.setHeight(height)
.buildAsync();
sheet.insertImages([image]);
return image;
}
// 渲染图片列:解析 JSON [{url,name,image}] 并插入浮动缩略图(异步,不阻塞主流程)
if (imageColumns && imageColumns.length) {
(async function renderImages() {
for (let c = 0; c < columns.length; c++) {
const key = columns[c];
if (!imageColSet.has(key)) continue;
for (let r = 0; r < data.length; r++) {
const v = data[r][key];
if (typeof v !== 'string' || v === '' || v[0] !== '[') continue;
let arr = null;
try { arr = JSON.parse(v); } catch (e) { continue; }
if (!Array.isArray(arr)) continue;
let n = 0;
for (const a of arr) {
if (a && a.url) {
try {
await insertImageAt(r + 1, c, a.url, ImageSourceType.URL, n * 48);
n++;
} catch (e) {}
}
}
}
}
})();
}
const inst = {
univer, univerAPI, sheet, workbook,
endEditing: function () { return workbook.endEditing(true); },
// 获取当前选中单元格(无选中时返回 null
getActiveCell: function () { return sheet.getActiveCell(); },
// 可靠的行/列数FWorksheet facade 没有 getRowCount/getColumnCount只有 getMaxRows/getMaxColumns
getRowCount: function () {
try { return sheet.getMaxRows(); } catch (e) { return data.length + 2; }
},
getColumnCount: function () {
try { return sheet.getMaxColumns(); } catch (e) { return columns.length + 1; }
},
// 插入浮动图片base64 或 URL定位到指定单元格向后兼容旧签名
insertImage: function (source, sourceType, row, col) {
return insertImageAt(row, col, source, sourceType, 0);
},
insertImageAt,
// 按容器宽度自适应缩放让所有列宽之和铺满容器clamp 到 [0.5, 1.5]
fitToContainer: function () {
try {
const w = container.clientWidth;
if (!w) return;
let total = 0;
const colCount = inst.getColumnCount();
for (let c = 0; c < colCount; c++) {
try { total += sheet.getColumnWidth(c) || DEFAULT_COL_WIDTH; } catch (e) { total += DEFAULT_COL_WIDTH; }
}
if (total <= 0) return;
let zoom = w / total;
zoom = Math.max(0.5, Math.min(1.5, zoom));
sheet.zoom(zoom);
} catch (e) {}
},
// 重置为 100%
resetZoom: function () { try { sheet.zoom(1); } catch (e) {} },
};
window.__onebotInst = inst; // 供调试/自动化测试直接调 API
// 初次自适应(等容器渲染后)
setTimeout(function () { inst.fitToContainer(); }, 60);
return inst;
}
window.createOneBotSheet = createOneBotSheet;