将excel里的某列的文本和某列的多图片组合成html

206 阅读4分钟

最近一个需求是将 excel 里的某列的文本和某列的多图片组合成 html,然后后端批量将这些 html 插入到数据库中,从而在前端展示。

excel 里的大致数据如下:

remark_1.png

需要将这些数据组合成 html,如下:

<p>文本</p>
<p><img src="xx1" /><img src="xx2" /></p>

这边 js 拿到 excel 里的图片数据且与行号挂钩,有点麻烦,后端用行号命名文件夹,然后存放相应行的图片,所以我这边直接拿到图片文件夹。 remark_2.png

1. 将 excel 里数据存储为 json数组

将 excel 里的数据存储为数组,这里使用了 xlsx 库。

npm install xlsx

拿到 excel 文件路径,然后将 excel 里的数据转换成数组。

/**
 * @description: 将excel里的数据转换成json
 * @param {string} excelFilePath excel文件路径
 * @param {number} sheetIndex 工作表的索引,默认为0
 * @return {array} 返回json数据数组
 * @example
 * transSheetToJSON('D:/示例.xlsx', 0) => [{ '列1': '文本1', '列2': '1' }, { '列1': '文本2', '列2': '2' }]
 * 注意图片列不能识别,所有图片列的数据都是空的。
 */
function transSheetToJSON(excelFilePath, sheetIndex = 0) {
  const XLSX = require('xlsx');
  // 读取Excel文件
  const workbook = XLSX.readFile(excelFilePath);
  // 获取工作表(sheet)的名字
  const sheetName = workbook.SheetNames[sheetIndex];
  // 读取工作表的数据
  const worksheet = workbook.Sheets[sheetName];
  // 将工作表转换为JSON数据
  const jsonData = XLSX.utils.sheet_to_json(worksheet);
  // 输出JSON数据
  return jsonData;
}

这样图中所示的 excel 数据就转换成了 数组数据

[
  { '评价文本': `1. 部分学习框没名\r\n 2. 讲解一般而是时间段\r\n3. 讲解完动词过told`, },
  { '评价文本': `1. 没有针对性命名\r\n2. 时刻而是时间段\r\n3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told`, },
];

2. 上传所有图片,得到对象 { 行号: url 数组 }

  • 写函数-上传单张图片到 cos,返回 url
  • 写函数-上传文件夹里的所有图片到 cos,返回 url 数组
  • 写函数-上传多个文件夹里的所有图片到 cos,返回对象数组 { 文件夹名: url 数组 }

因为这里的文件夹名是行号,所以这两是等同的。

这样图中所示的 excel 数据就转换成了 对象数据:

{
  '2': [
    'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png'
  ],
  '3': [
    'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png'
  ]
}

实现代码如下:

提前安装 cos-nodejs-sdk-v5

npm install cos-nodejs-sdk-v5
/**
 * @description: 上传文件到cos
 * @param {string} localFilepath 本地文件路径
 * @param {function} getConfig 获取cos配置的函数
 * @return {Promise} 返回上传成功后的url
 * @example
 * uploadFileToCos('D:/示例.jpg', getConfig) => 'https://.....示例_232.jpg'
 */
function uploadFileToCos(localFilepath, getConfig) {
  const COS = require('cos-nodejs-sdk-v5');
  const fs = require('fs');
  const path = require('path');
  return new Promise(async (resolve, reject) => {
    /** 防止请求太快,所以加了延迟 */
    await sleep(100);
    // 获取cos配置  这里是一个异步函数 { bucketName, region, path, SecretId, SecretKey }
    const cosConfig = await getConfig();
    const cos = new COS(cosConfig);
    const file = await fs.readFileSync(path.resolve(localFilepath));
    const params = {
      Bucket: cosConfig.bucketName,
      Region: cosConfig.region,
      // Key 可以理解是文件的路径,这里我使用了一个带有uuid的文件名
      Key: `${cosConfig.path}/${addUuidToName(localFilepath)}`,
      Body: file,
      onProgress: function (progressData) {
        const { loaded, total } = progressData;
        console.log('上传进度', { loaded, total }); // 临时
      },
    };
    cos.putObject(params, function (err, data) {
      if (err) {
        console.error('上传失败', err);
        reject(err);
        return;
      }
      console.log('上传成功', data.Location);
      const url = `https://${data.Location}`;
      resolve(url);
      return Promise.resolve(url);
    });
  });
}

/**
 *
 * @param {*} dirPath 文件夹路径,里面是多个文件
 * @param {*} getConfig
 * @returns 字符串数组,每个元素是一个文件的url
 * @example
 * 示例文件夹里有 示例1.jpg, 示例2.jpg
 * uploadDirFilesToCos('D:/示例文件夹', getConfig) => ['https://.....示例1_232.jpg', 'https://.....示例2_233.jpg']
 */
async function uploadDirFilesToCos(dirPath, getConfig) {
  const fs = require('fs');
  const path = require('path');
  const files = fs.readdirSync(dirPath);
  const urls = [];
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    await sleep(100);
    try {
      const url = await uploadFileToCos(path.resolve(dirPath, file), getConfig);
      urls[i] = url;
    } catch (error) {
      urls[i] = '';
    }
  }
  return urls;
}
/**
 *
 * @param {*} baseDirPath 有多个文件夹的文件夹路径
 * @param {*} getConfig
 * @returns 对象数组,每个对象的key是文件夹名,value是一个字符串数组,每个元素是一个文件的url
 */
async function uploadBaseDir(baseDirPath, getConfig) {
  const fs = require('fs');
  const path = require('path');
  const dirList = fs.readdirSync(baseDirPath);
  const res = {};
  for (let i = 0; i < dirList.length; i++) {
    const dir = dirList[i];
    const dirPath = path.resolve(baseDirPath, dir);
    try {
      const urlList = await uploadDirFilesToCos(dirPath, getConfig);
      res[dir] = urlList;
    } catch (error) {
      res[dir] = [];
    }
  }
  console.log(`${baseDirPath}上传完成`, res);
  return res;
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 *
 * @param {*} filenameOrPath
 * @returns string
 * @example
 * addUuidToName('test.png') // test_6e1fa314-96ef-d686-104c-4b6a67649483.png
 */
function addUuidToName(filenameOrPath) {
  const uuid = require('react-uuid');
  const path = require('path');
  const filename = path.basename(filenameOrPath);
  const dotIndex = filename.lastIndexOf('.');
  if (dotIndex === -1) {
    return `${filename}_${uuid()}`;
  }
  const base = filename.slice(0, dotIndex);
  const suffix = filename.slice(dotIndex);
  return `${base}_${uuid()}${suffix}`;
}

3. 将文本和图片组合成 html

获取表格里的文本数据,然后将图片的 url 一起组合成 html。

首先看下索引和行号的对应关系,第二行其实是对应数组索引 0,第三行对应数组索引 1。

const jsonArr = [
  { '评价文本': `1. 部分学习框没名\r\n 2. 讲解一般而是时间段\r\n3. 讲解完动词过told`, },
  { '评价文本': `1. 没有针对性命名\r\n2. 时刻而是时间段\r\n3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told`, },
];
const imgObj = {
  2: [
    'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png',
  ],
  3: [
    'https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png',
  ],
};

然后将文本和图片组合成 html。

function mergeTextAndImgToHtml(jsonArr, textKey, imgObj) {
  const htmlArr = [];
  for (let i = 0; i < jsonArr.length; i++) {
    const item = jsonArr[i];
    const imgArr = imgObj[i + 2] || [];
    const imgHtml = imgArr.map((img) => `<img src="${img}" />`).join('');
    // 将\r\n替换成</p><p>
    const textHtml = `<p>${item[textKey].replaceAll('\r\n', '</p><p>')}</p>`;
    const html = `${textHtml}<p>${imgHtml}</p>`;
    htmlArr.push(html);
  }
  return htmlArr;
}

这样就得到了 html 数组。

[
  `<p>1. 部分学习名</p>
  <p>2. 讲解一 1950不是时刻而是时间段</p>
  <p>3. 讲解完动词题的t成told</p>
  <p><img src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos1.png" />
  </p>`,

  `<p>1. 没有针对性命名</p>
  <p>2. 时刻而是时间段</p>
  <p>3. 目都考查的是动词的不规则变化,需要换题,比如第一题的see变成saw;第二题的tell变成told</p>
  <p><img src="https://blog-huahua.oss-cn-beijing.aliyuncs.com/blog/code/upload_cos2.png" />
  </p>`,
];

执行的代码如下:

安装 axios

npm install axios

其他代码如下:

const path = require('path');
const uploadConfig = async () => {
  const axios = require('axios');
  const res = await axios.post('https://xx/config');
  if (!res.data.success) {
    console.error('请求配置失败');
    return;
  }
  console.log('成功请求到配置');
  const cosConfig = res.data.data;
  const result = {
    bucketName: cosConfig.bucketName,
    region: cosConfig.region,
    path: cosConfig.path || '/blog/code',
    /** 验证信息 */
    SecretId: cosConfig.tmpSecretId,
    SecretKey: cosConfig.tmpSecretKey,
    SecurityToken: cosConfig.sessionToken,
  };
  return result;
};

async function getRes() {
  const jsonArr = transSheetToJSON(path.resolve(__dirname, './info.xlsx'));
  const imgObj = await uploadBaseDir(
    path.resolve(__dirname, './images'),
    uploadConfig
  );
  return mergeTextAndImgToHtml(jsonArr, '评价文本', imgObj);
}
getRes()