@focus-teach/ui 1.0.54 → 1.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@focus-teach/ui",
3
3
  "packageName": "ui",
4
4
  "publishName": "@focus-teach/ui",
5
- "version": "1.0.54",
5
+ "version": "1.0.55",
6
6
  "private": false,
7
7
  "main": "lib/ui.umd.min.js",
8
8
  "scripts": {
@@ -22,7 +22,9 @@
22
22
  "babel-loader": "^8.0.0",
23
23
  "core-js": "^3.6.5",
24
24
  "element-ui": "^2.15.1",
25
+ "html2canvas": "^1.4.1",
25
26
  "lodash": "^4.17.21",
27
+ "md5": "^2.3.0",
26
28
  "qs": "^6.10.1",
27
29
  "sass": "^1.26.5",
28
30
  "sass-loader": "^10.1.1",
package/utils/common.js CHANGED
@@ -132,7 +132,7 @@ export function getFirstNumber(itemNumber, type) {
132
132
  let match = itemNumber.match(/(\d+)(?:-(\d+))?/);
133
133
  let num1 = match[1];
134
134
  let num2 = match[2];
135
- console.log(resultmatchchineseNum, num1, num2)
135
+ // console.log(resultmatchchineseNum, num1, num2)
136
136
  return resultmatchchineseNum ? num1 == num2 ? resultmatchchineseNum + num1 : itemNumber : num1 == num2 ? num1 : itemNumber
137
137
  }
138
138
  }
@@ -263,7 +263,7 @@ function loadImage({child,maxHeight,maxWidth}){//等待图片加载完成
263
263
  }else{
264
264
  try{
265
265
  let res = await Promise.all(promiseArr)
266
- console.log('res is...',res)
266
+ // console.log('res is...',res)
267
267
  resolve({width:res?.[0]?.width,height:res?.[0]?.height})
268
268
  }catch(err){
269
269
  setTimeout(()=>{
@@ -301,7 +301,7 @@ async function getCols(optionArr,TABLE_WIDTH,className){
301
301
  let childNode = div.childNodes[j]
302
302
  if(childNode.nodeName == 'IMG'){
303
303
  let tempWidth = childNode?.style?.width || childNode.width
304
- console.log(tempWidth,className,'图片宽度');
304
+ // console.log(tempWidth,className,'图片宽度');
305
305
  let realWidth = String(tempWidth || '').includes('pt') ? ptToPx(parseFloat(tempWidth)) : parseFloat(tempWidth)
306
306
  // 若图片不存在宽度,则随便给一个值,超过编辑器宽度即可,这里给了1000
307
307
  if(!realWidth) {
@@ -312,15 +312,27 @@ async function getCols(optionArr,TABLE_WIDTH,className){
312
312
  }else{
313
313
  if(childNode.nodeName == '#text'){
314
314
  width += getStringWidthNoImage(childNode.textContent,'text',className == 'options-mistake-print' ? '16px' : '14px',className == 'options-mistake-print' ? 'Avenir, Helvetica, Arial, sans-serif' : undefined)
315
- console.log(width,'文本节点宽度');
316
- }else{
315
+ // console.log(width,'文本节点宽度');
316
+ }else{//对应其他标签 优先处理是否里面有内嵌的图片
317
+ let imgList = childNode.querySelectorAll('img')
318
+ for(let k = 0; k < imgList.length; k++){
319
+ let img = imgList[k]
320
+ let tempWidth = img?.style?.width || img.width
321
+ let realWidth = String(tempWidth || '').includes('pt') ? ptToPx(parseFloat(tempWidth)) : parseFloat(tempWidth)
322
+ // 若图片不存在宽度,则随便给一个值,超过编辑器宽度即可,这里给了1000
323
+ if(!realWidth) {
324
+ let { width,height } = await loadImage({child:img,maxHeight:MAX_HEIGHT,maxWidth:TABLE_WIDTH})
325
+ realWidth = width
326
+ }
327
+ width += realWidth || MAX_HEIGHT
328
+ }
317
329
  width += getStringWidthNoImage(childNode.outerHTML,'',className == 'options-mistake-print' ? '16px' : '14px',className == 'options-mistake-print' ? 'Avenir, Helvetica, Arial, sans-serif' : undefined)
318
- console.log(width,'其他标签宽度');
330
+ // console.log(width,'其他标签宽度');
319
331
  }
320
332
  }
321
333
  }
322
334
  div?.remove?.()
323
- console.log("带图片识别出来的宽",className,width);
335
+ // console.log("带图片识别出来的宽",className,width);
324
336
  // return width
325
337
  widthList.push(width)
326
338
  }else{
@@ -333,19 +345,22 @@ async function getCols(optionArr,TABLE_WIDTH,className){
333
345
 
334
346
  let find = factors.find(item=> maxWidth <= TABLE_WIDTH / item)
335
347
  find && (cols = find);
336
- console.log('计算出来的表格列数=====>',cols,maxWidth);
348
+ // console.log('计算出来的表格列数=====>',cols,maxWidth);
337
349
  return cols
338
350
  }
339
351
 
340
352
 
341
- async function getOptionArr(optionArr,TABLE_WIDTH,className){
353
+ async function getOptionArr(optionArr,TABLE_WIDTH,className,optionCount){
342
354
  let cols = await getCols(optionArr,TABLE_WIDTH,className);
355
+ if(optionCount){
356
+ cols = Math.min(cols,optionCount)
357
+ }
343
358
  let rows = Math.ceil(optionArr.length / cols);
344
359
  optionArr = Array.from({ length: rows }, (_, i) => optionArr.slice(i * cols, i * cols + cols));
345
360
  return optionArr
346
361
  }
347
362
 
348
- export async function generateTable(optionArr,option) {
363
+ export async function generateTable(optionArr,optionCount) {
349
364
 
350
365
  let table = '<table border="0" class="ckeditor-table cke_show_border option-table" style="border-collapse: collapse; width: 100%;;border:none"><tbody>';
351
366
  // 后续有新增类型只需要添加宽度和对应的类名即可
@@ -394,7 +409,7 @@ export async function generateTable(optionArr,option) {
394
409
  // [[A,B],[C,D]]
395
410
  for(let i = 0; i < tableList.length; i++){
396
411
  let item = tableList[i]
397
- let optionsTemp = await getOptionArr(optionArr,item.width,item.className);
412
+ let optionsTemp = await getOptionArr(optionArr,item.width,item.className,optionCount);
398
413
  optionsTemp.forEach((optionCols)=>{
399
414
  table += `<tr class="options-tr ${item.className}">`
400
415
  optionCols.forEach((item)=>{
@@ -438,7 +453,7 @@ function optionToTable(stem) {
438
453
  }
439
454
  })
440
455
  }
441
- console.log(option,'option======');
456
+ // console.log(option,'option======');
442
457
  if(!option) return stem
443
458
  const reg = /([A-H])[.|、|.]\s*(.+?)(?=[A-H][.|、|.]|\s*$)/g;
444
459
  let optionArr = option.match(reg);
@@ -457,4 +472,193 @@ export function getUpdateTime(timeStr) {
457
472
  var oneTime = new Date(timeStr).getTime();
458
473
  var date = new Date(oneTime);
459
474
  return formatDate(date, "yyyy-MM-dd hh:mm");
460
- }
475
+ }
476
+
477
+ function extractTextAndImages(html) {
478
+ // 第一步:将img标签替换为特殊标记
479
+ const imgPlaceholder = '___IMG___';
480
+ const imgTags = [];
481
+
482
+ const htmlWithPlaceholders = html.replace(/<img[^>]+>/gi, match => {
483
+ imgTags.push(match);
484
+ return imgPlaceholder;
485
+ });
486
+
487
+ // 第二步:移除其他HTML标签
488
+ const textWithPlaceholders = htmlWithPlaceholders
489
+ .replace(/<[^>]+>/g, '') // 移除所有HTML标签
490
+ .replace(/&nbsp;/g, ' ') // 替换HTML实体
491
+ .replace(/&lt;/g, '<')
492
+ .replace(/&gt;/g, '>')
493
+ .replace(/&amp;/g, '&')
494
+ .replace(/&quot;/g, '"')
495
+ .replace(/\s+/g, ' '); // 合并多个空格
496
+
497
+ // 第三步:还原img标签
498
+ let index = 0;
499
+ const result = textWithPlaceholders.replace(new RegExp(imgPlaceholder, 'g'), () => {
500
+ return imgTags[index++] || '';
501
+ });
502
+ let dom = new DOMParser().parseFromString(result,'text/html')
503
+ let contentList = []
504
+ Array.from(dom?.body?.childNodes).map(item=>{
505
+ if(item.nodeName == 'IMG'){
506
+ contentList.push({type:'image',content:item.outerHTML,url:item.src})
507
+ }else{
508
+ contentList.push({type:'text',content:item.textContent})
509
+ }
510
+ })
511
+ return contentList;
512
+ }
513
+
514
+ export async function textWithImageToBase64(text, options = {}) {
515
+ let contentList = extractTextAndImages(text)
516
+ // 默认配置
517
+ const config = {
518
+ width: 762,
519
+ fontSize: 14,
520
+ fontFamily: 'Arial',
521
+ textColor: '#000000',
522
+ backgroundColor: '#ffffff',
523
+ padding: 16,
524
+ lineHeight: 1.5,
525
+ imageHeight: 100, // 默认图片高度
526
+ imageWidth: 100, // 默认图片宽度
527
+ imageMargin: 10, // 图片之间的间距
528
+ ...options
529
+ };
530
+
531
+ // 创建canvas
532
+ const canvas = document.createElement('canvas');
533
+ const ctx = canvas.getContext('2d');
534
+
535
+ // 加载图片函数
536
+ const loadImage = (src) => {
537
+ return new Promise((resolve, reject) => {
538
+ const img = new Image();
539
+ img.crossOrigin = 'anonymous'; // 处理跨域
540
+ img.onload = () => resolve(img);
541
+ img.onerror = reject;
542
+ img.src = src;
543
+ });
544
+ };
545
+
546
+ // 计算文本换行
547
+ function getTextLines(text, maxWidth) {
548
+ ctx.font = `${config.fontSize}px ${config.fontFamily}`;
549
+ const words = text.split('');
550
+ const lines = [];
551
+ let currentLine = '';
552
+
553
+ for (let i = 0; i < words.length; i++) {
554
+ const testLine = currentLine + words[i];
555
+ const metrics = ctx.measureText(testLine);
556
+
557
+ if (metrics.width > maxWidth && i > 0) {
558
+ lines.push(currentLine);
559
+ currentLine = words[i];
560
+ } else {
561
+ currentLine = testLine;
562
+ }
563
+ }
564
+ lines.push(currentLine);
565
+ return lines;
566
+ }
567
+
568
+ // 计算总高度
569
+ async function calculateTotalHeight(content) {
570
+ let totalHeight = config.padding;
571
+ const maxWidth = config.width - (config.padding * 2);
572
+
573
+ for (const item of content) {
574
+ if (item.type === 'text') {
575
+ const lines = getTextLines(item.content, maxWidth);
576
+ totalHeight += lines.length * config.fontSize * config.lineHeight;
577
+ } else if (item.type === 'image') {
578
+ const img = await loadImage(item.url)
579
+ totalHeight += img.height + config.imageMargin;
580
+ }
581
+ }
582
+
583
+ return totalHeight + config.padding;
584
+ }
585
+
586
+ // 绘制内容
587
+ async function drawContent() {
588
+ let currentY = config.padding;
589
+ const maxWidth = config.width - (config.padding * 2);
590
+
591
+ for (const item of contentList) {
592
+ if (item.type === 'text') {
593
+ ctx.font = `${config.fontSize}px ${config.fontFamily}`;
594
+ ctx.fillStyle = config.textColor;
595
+
596
+ const lines = getTextLines(item.content, maxWidth);
597
+ for (const line of lines) {
598
+ ctx.fillText(line, config.padding, currentY);
599
+ currentY += config.fontSize * config.lineHeight;
600
+ }
601
+ } else if (item.type === 'image') {
602
+ try {
603
+ const img = await loadImage(item.url);
604
+ // 居中显示图片
605
+ const x = (config.width - img.width) / 2;
606
+ ctx.drawImage(img, config.imageMargin, currentY, img.width, img.height);
607
+ currentY += img.height + config.imageMargin;
608
+ } catch (error) {
609
+ console.error('Failed to load image:', error);
610
+ }
611
+ }
612
+ }
613
+ }
614
+
615
+ // 计算画布高度
616
+ const totalHeight = await calculateTotalHeight(contentList);
617
+
618
+ // 设置画布尺寸
619
+ canvas.width = config.width;
620
+ canvas.height = totalHeight;
621
+
622
+ // 绘制背景
623
+ ctx.fillStyle = config.backgroundColor;
624
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
625
+
626
+ // 绘制内容
627
+ await drawContent();
628
+
629
+ return {
630
+ base64: canvas.toDataURL('image/png'),
631
+ size: Math.ceil((canvas.toDataURL('image/png').length * 3/4) / (1024 * 1024))
632
+ };
633
+ }
634
+ /**
635
+ * 计算 base64 字符串大小
636
+ * @param {string} base64String base64字符串
637
+ * @returns {object} 包含字节数和格式化后大小的对象
638
+ */
639
+ export function getBase64Size(base64String,unit = 'MB') {
640
+ // 去掉 base64 字符串头部标识和空格
641
+ const strLength = base64String.replace(/^data:image\/\w+;base64,/, '').length;
642
+
643
+ // base64 字符串解码后的字节数,大约为原长度的 3/4
644
+ const fileSize = Math.floor((strLength - (strLength / 8) * 2) / 4) * 3;
645
+ // 格式化文件大小
646
+ const formatSize = (size) => {
647
+ if (size < 1024 && unit == 'B') {
648
+ return Math.ceil(size);
649
+ } else if (size < 1024 * 1024 && unit == 'KB') {
650
+ return Math.ceil(size / 1024);
651
+ } else if (size < 1024 * 1024 && unit == 'MB') {
652
+ return Math.ceil((size / (1024 * 1024)));
653
+ }
654
+ };
655
+
656
+ return formatSize(fileSize);
657
+ }
658
+
659
+
660
+
661
+
662
+
663
+
664
+